diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 7c759a25..ab737ba8 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -53,7 +53,7 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted diff --git a/res/layout/activity_run_command.xml b/res/layout/activity_run_command.xml index 5910247c..1f6955ff 100644 --- a/res/layout/activity_run_command.xml +++ b/res/layout/activity_run_command.xml @@ -43,7 +43,7 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted app:layout_anchorGravity="bottom|end" /> + tools:title="@string/kde_connect"/> diff --git a/res/layout/widget_remotecommandplugin.xml b/res/layout/widget_remotecommandplugin.xml index 5e39411a..e6355bad 100644 --- a/res/layout/widget_remotecommandplugin.xml +++ b/res/layout/widget_remotecommandplugin.xml @@ -16,13 +16,14 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted tools:ignore="RtlSymmetry"> Stop the current player Copy URL to clipboard Copied to clipboard + Device is not reachable Device is not paired There is no such device This device does not have the Run Command Plugin enabled + Device Controls + runcommand_category_device_controls + If your device supports Device Controls, commands you have configured will appear there. + set_runcommand_name_as_title + Show name as title + Find remote device Ring your remote device Ring diff --git a/res/xml/runcommand_preferences.xml b/res/xml/runcommand_preferences.xml new file mode 100644 index 00000000..b72e9de2 --- /dev/null +++ b/res/xml/runcommand_preferences.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + diff --git a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/CommandEntry.java b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/CommandEntry.java index 2dab2572..07c52f8a 100644 --- a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/CommandEntry.java +++ b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/CommandEntry.java @@ -6,11 +6,19 @@ package org.kde.kdeconnect.Plugins.RunCommandPlugin; +import androidx.annotation.NonNull; + +import org.json.JSONException; +import org.json.JSONObject; import org.kde.kdeconnect.UserInterface.List.EntryItem; class CommandEntry extends EntryItem { private final String key; + public CommandEntry(@NonNull JSONObject o) throws JSONException { + this(o.getString("name"), o.getString("command"), o.getString("key")); + } + public CommandEntry(String name, String cmd, String key) { super(name, cmd); this.key = key; diff --git a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandActivity.java b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandActivity.java index df37fe4b..4ff50eff 100644 --- a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandActivity.java +++ b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandActivity.java @@ -23,6 +23,7 @@ import androidx.core.content.ContextCompat; import org.json.JSONException; import org.json.JSONObject; +import org.kde.kdeconnect.Device; import org.kde.kdeconnect.KdeConnect; import org.kde.kdeconnect.UserInterface.List.ListAdapter; import org.kde.kdeconnect_tp.R; @@ -52,8 +53,7 @@ public class RunCommandActivity extends AppCompatActivity { commandItems = new ArrayList<>(); for (JSONObject obj : plugin.getCommandList()) { try { - commandItems.add(new CommandEntry(obj.getString("name"), - obj.getString("command"), obj.getString("key"))); + commandItems.add(new CommandEntry(obj)); } catch (JSONException e) { Log.e("RunCommand", "Error parsing JSON", e); } @@ -71,8 +71,8 @@ public class RunCommandActivity extends AppCompatActivity { if (!plugin.canAddCommand()) { text += "\n" + getString(R.string.addcommand_explanation2); } - binding.addComandExplanation.setText(text); - binding.addComandExplanation.setVisibility(commandItems.isEmpty() ? View.VISIBLE : View.GONE); + binding.addCommandExplanation.setText(text); + binding.addCommandExplanation.setVisibility(commandItems.isEmpty() ? View.VISIBLE : View.GONE); } @Override @@ -87,21 +87,25 @@ public class RunCommandActivity extends AppCompatActivity { getSupportActionBar().setDisplayShowHomeEnabled(true); deviceId = getIntent().getStringExtra("deviceId"); - RunCommandPlugin plugin = KdeConnect.getInstance().getDevicePlugin(deviceId,RunCommandPlugin.class); - if (plugin != null) { - if (plugin.canAddCommand()) { - binding.addCommandButton.show(); - } else { - binding.addCommandButton.hide(); + Device device = KdeConnect.getInstance().getDevice(deviceId); + if (device != null) { + getSupportActionBar().setSubtitle(device.getName()); + RunCommandPlugin plugin = device.getPlugin(RunCommandPlugin.class); + if (plugin != null) { + if (plugin.canAddCommand()) { + binding.addCommandButton.show(); + } else { + binding.addCommandButton.hide(); + } + binding.addCommandButton.setOnClickListener(v -> { + plugin.sendSetupPacket(); + new AlertDialog.Builder(RunCommandActivity.this) + .setTitle(R.string.add_command) + .setMessage(R.string.add_command_description) + .setPositiveButton(R.string.ok, null) + .show(); + }); } - binding.addCommandButton.setOnClickListener(v -> { - plugin.sendSetupPacket(); - new AlertDialog.Builder(RunCommandActivity.this) - .setTitle(R.string.add_command) - .setMessage(R.string.add_command_description) - .setPositiveButton(R.string.ok, null) - .show(); - }); } updateView(); } diff --git a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandControlsProviderService.kt b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandControlsProviderService.kt index 73cb74b8..43061d60 100644 --- a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandControlsProviderService.kt +++ b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandControlsProviderService.kt @@ -13,6 +13,7 @@ import android.graphics.drawable.Icon import android.os.Build import android.service.controls.Control import android.service.controls.ControlsProviderService +import android.service.controls.DeviceTypes import android.service.controls.actions.CommandAction import android.service.controls.actions.ControlAction import android.service.controls.templates.StatelessTemplate @@ -23,16 +24,16 @@ import io.reactivex.Flowable import io.reactivex.processors.ReplayProcessor import org.json.JSONArray import org.json.JSONException +import org.json.JSONObject import org.kde.kdeconnect.Device import org.kde.kdeconnect.KdeConnect import org.kde.kdeconnect.UserInterface.MainActivity import org.kde.kdeconnect_tp.R import org.reactivestreams.FlowAdapters -import java.util.* import java.util.concurrent.Flow import java.util.function.Consumer -private class CommandEntryWithDevice(name: String, cmd: String, key: String, val device: Device) : CommandEntry(name, cmd, key) +private class CommandEntryWithDevice(o: JSONObject, val device: Device) : CommandEntry(o) @RequiresApi(Build.VERSION_CODES.R) class RunCommandControlsProviderService : ControlsProviderService() { @@ -56,23 +57,13 @@ class RunCommandControlsProviderService : ControlsProviderService() { for (controlId in controlIds) { val commandEntry = getCommandByControlId(controlId) if (commandEntry != null && commandEntry.device.isReachable) { - updatePublisher.onNext(Control.StatefulBuilder(controlId, getIntent(commandEntry.device)) - .setTitle(commandEntry.name) - .setSubtitle(commandEntry.command) - .setStructure(commandEntry.device.name) + updatePublisher.onNext(createStatefulBuilder(commandEntry, controlId) .setStatus(Control.STATUS_OK) .setStatusText(getString(R.string.tap_to_execute)) - .setControlTemplate(StatelessTemplate(commandEntry.key)) - .setCustomIcon(Icon.createWithResource(this, R.drawable.run_command_plugin_icon_24dp)) .build()) } else if (commandEntry != null && commandEntry.device.isPaired && !commandEntry.device.isReachable) { - updatePublisher.onNext(Control.StatefulBuilder(controlId, getIntent(commandEntry.device)) - .setTitle(commandEntry.name) - .setSubtitle(commandEntry.command) - .setStructure(commandEntry.device.name) + updatePublisher.onNext(createStatefulBuilder(commandEntry, controlId) .setStatus(Control.STATUS_DISABLED) - .setControlTemplate(StatelessTemplate(commandEntry.key)) - .setCustomIcon(Icon.createWithResource(this, R.drawable.run_command_plugin_icon_24dp)) .build()) } else { updatePublisher.onNext(Control.StatefulBuilder(controlId, getIntent(commandEntry?.device)) @@ -101,14 +92,9 @@ class RunCommandControlsProviderService : ControlsProviderService() { consumer.accept(ControlAction.RESPONSE_FAIL) } - updatePublisher.onNext(Control.StatefulBuilder(controlId, getIntent(commandEntry.device)) - .setTitle(commandEntry.name) - .setSubtitle(commandEntry.command) - .setStructure(commandEntry.device.name) + updatePublisher.onNext(createStatefulBuilder(commandEntry, controlId) .setStatus(Control.STATUS_OK) .setStatusText(getString(R.string.tap_to_execute)) - .setControlTemplate(StatelessTemplate(commandEntry.key)) - .setCustomIcon(Icon.createWithResource(this, R.drawable.run_command_plugin_icon_24dp)) .build()) } } @@ -126,7 +112,7 @@ class RunCommandControlsProviderService : ControlsProviderService() { for (index in 0 until jsonArray.length()) { val jsonObject = jsonArray.getJSONObject(index) - commandList.add(CommandEntryWithDevice(jsonObject.getString("name"), jsonObject.getString("command"), jsonObject.getString("key"), device)) + commandList.add(CommandEntryWithDevice(jsonObject, device)) } commandList @@ -151,7 +137,7 @@ class RunCommandControlsProviderService : ControlsProviderService() { if (plugin != null) { for (jsonObject in plugin.commandList) { try { - commandList.add(CommandEntryWithDevice(jsonObject.getString("name"), jsonObject.getString("command"), jsonObject.getString("key"), device)) + commandList.add(CommandEntryWithDevice(jsonObject, device)) } catch (error: JSONException) { Log.e("RunCommand", "Error parsing JSON", error) } @@ -171,7 +157,7 @@ class RunCommandControlsProviderService : ControlsProviderService() { val commandList = if (device.isReachable) { device.getPlugin(RunCommandPlugin::class.java)?.commandList?.map { jsonObject -> - CommandEntryWithDevice(jsonObject.getString("name"), jsonObject.getString("command"), jsonObject.getString("key"), device) + CommandEntryWithDevice(jsonObject, device) } } else { getSavedCommandsList(device) @@ -187,9 +173,29 @@ class RunCommandControlsProviderService : ControlsProviderService() { } } + private fun createStatefulBuilder(commandEntry: CommandEntryWithDevice, controlId: String): Control.StatefulBuilder { + if (!this::sharedPreferences.isInitialized) { + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this) + } + val useNameForTitle = sharedPreferences.getBoolean(getString(R.string.set_runcommand_name_as_title), true) + + return Control.StatefulBuilder(controlId, getIntent(commandEntry.device)) + .setTitle(if (useNameForTitle) commandEntry.name else commandEntry.command) + .setSubtitle(if (useNameForTitle) commandEntry.command else commandEntry.name) + .setStructure(commandEntry.device.name) + .setControlTemplate(StatelessTemplate(commandEntry.key)) + .setDeviceType(DeviceTypes.TYPE_ROUTINE) + .setCustomIcon(Icon.createWithResource(this, R.drawable.run_command_plugin_icon_24dp)) + } + private fun getIntent(device: Device?): PendingIntent { - val intent = Intent(Intent.ACTION_MAIN).setClass(this, MainActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - intent.putExtra(MainActivity.EXTRA_DEVICE_ID, device?.deviceId) + val target = if (device?.isReachable == true) RunCommandActivity::class else MainActivity::class + + val intent = Intent(Intent.ACTION_MAIN) + .setClass(this, target.java) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + .putExtra(MainActivity.EXTRA_DEVICE_ID, device?.deviceId) + return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) } } \ No newline at end of file diff --git a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandPlugin.java b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandPlugin.java index e10ed648..ab59d81f 100644 --- a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandPlugin.java +++ b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandPlugin.java @@ -18,20 +18,22 @@ import android.util.Log; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.preference.PreferenceManager; +import org.apache.commons.collections4.iterators.IteratorIterable; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.kde.kdeconnect.NetworkPacket; import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.Plugins.PluginFactory; +import org.kde.kdeconnect.UserInterface.PluginSettingsFragment; import org.kde.kdeconnect_tp.R; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import java.util.Iterator; @PluginFactory.LoadablePlugin public class RunCommandPlugin extends Plugin { @@ -78,6 +80,17 @@ public class RunCommandPlugin extends Plugin { return context.getResources().getString(R.string.pref_plugin_runcommand_desc); } + @Override + public boolean hasSettings() { + return true; + } + + @Nullable + @Override + public PluginSettingsFragment getSettingsFragment(Activity activity) { + return PluginSettingsFragment.newInstance(getPluginKey(), R.xml.runcommand_preferences); + } + @Override public @DrawableRes int getIcon() { return R.drawable.run_command_plugin_icon_24dp; @@ -98,20 +111,14 @@ public class RunCommandPlugin extends Plugin { try { commandItems.clear(); JSONObject obj = new JSONObject(np.getString("commandList")); - Iterator keys = obj.keys(); - while (keys.hasNext()) { - String s = keys.next(); + for (String s : new IteratorIterable<>(obj.keys())) { JSONObject o = obj.getJSONObject(s); o.put("key", s); commandList.add(o); try { commandItems.add( - new CommandEntry( - o.getString("name"), - o.getString("command"), - o.getString("key") - ) + new CommandEntry(o) ); } catch (JSONException e) { Log.e("RunCommand", "Error parsing JSON", e); @@ -128,7 +135,9 @@ public class RunCommandPlugin extends Plugin { array.put(command); } - sharedPreferences.edit().putString(KEY_COMMANDS_PREFERENCE + device.getDeviceId(), array.toString()).apply(); + sharedPreferences.edit() + .putString(KEY_COMMANDS_PREFERENCE + device.getDeviceId(), array.toString()) + .apply(); } forceRefreshWidgets(context); @@ -189,7 +198,7 @@ public class RunCommandPlugin extends Plugin { return context.getString(R.string.pref_plugin_runcommand); } - public boolean canAddCommand(){ + public boolean canAddCommand() { return canAddCommand; } diff --git a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidgetDataProvider.kt b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidgetDataProvider.kt index 3f50929d..ee6767c2 100644 --- a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidgetDataProvider.kt +++ b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidgetDataProvider.kt @@ -20,19 +20,22 @@ import org.kde.kdeconnect_tp.R internal class RunCommandWidgetDataProvider(private val context: Context, val intent: Intent?) : RemoteViewsFactory { - private lateinit var deviceId : String + private var deviceId : String? = null private var widgetId : Int = AppWidgetManager.INVALID_APPWIDGET_ID override fun onCreate() { widgetId = intent?.getIntExtra(EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID) ?: AppWidgetManager.INVALID_APPWIDGET_ID if (widgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { - Log.e("KDEConnect/Widget", "RunCommandWidgetDataProvider: No widget id extra set") + Log.e("KDEConnect/Widget", "RunCommandWidgetDataProvider: No widget id extra was set") return } - deviceId = loadWidgetDeviceIdPref(context, widgetId)!! + deviceId = loadWidgetDeviceIdPref(context, widgetId) + } + + override fun onDataSetChanged() { + deviceId = loadWidgetDeviceIdPref(context, widgetId) } - override fun onDataSetChanged() {} override fun onDestroy() {} private fun getPlugin(): RunCommandPlugin? { @@ -48,7 +51,11 @@ internal class RunCommandWidgetDataProvider(private val context: Context, val in val plugin : RunCommandPlugin? = getPlugin() if (plugin == null) { - Log.e("getViewAt", "RunCommandWidgetDataProvider: Plugin not found"); + // Either the deviceId was null, or the plugin is not available. + if (deviceId != null) { + Log.e("getViewAt", "RunCommandWidgetDataProvider: Plugin not found") + } + // Return a new, not-configured layout as a fallback return remoteView } diff --git a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidgetProvider.kt b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidgetProvider.kt index 64c0842c..6412867c 100644 --- a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidgetProvider.kt +++ b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidgetProvider.kt @@ -15,7 +15,6 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.util.Log -import android.view.View import android.widget.RemoteViews import org.kde.kdeconnect.Device import org.kde.kdeconnect.KdeConnect @@ -87,6 +86,20 @@ fun forceRefreshWidgets(context : Context) { context.sendBroadcast(intent) } +/** + * Recreate the [RemoteViews] layout of a given widget. + * + * This function is called when a new widget is created, or when the list of devices changes, or if + * a device enables/disables its [RunCommandPlugin]. Hosting apps that contain our widgets will do + * anything they can to avoid extra renders. + * + * 1. We use [appWidgetId] as a request code in [assignTitleIntent] to force hosting apps to track a + * separate intent for each widget. + * 2. We call [AppWidgetManager.notifyAppWidgetViewDataChanged] at the end of this function, which + * lets the list adapter know that it might be referring to the wrong device id. + * + * See also [RunCommandWidgetDataProvider.onDataSetChanged]. + */ internal fun updateAppWidget( context: Context, appWidgetManager: AppWidgetManager, @@ -94,46 +107,86 @@ internal fun updateAppWidget( ) { Log.d("WidgetProvider", "updateAppWidget: $appWidgetId") + // Determine which device provided these commands val deviceId = loadWidgetDeviceIdPref(context, appWidgetId) val device: Device? = if (deviceId != null) KdeConnect.getInstance().getDevice(deviceId) else null val views = RemoteViews(BuildConfig.APPLICATION_ID, R.layout.widget_remotecommandplugin) + assignTitleIntent(context, appWidgetId, views) + + Log.d("WidgetProvider", "updateAppWidget device: " + if (device == null) "null" else device.name) + + // Android should automatically toggle between the command list and the error text + views.setEmptyView(R.id.widget_command_list, R.id.widget_error_text) + + // TODO: Use string resources + + if (device == null) { + // There are two reasons we reach this condition: + // 1. there is no preference string for this widget id + // 2. the string id does not match any devices in KdeConnect.getInstance() + // In both cases, we want the user to assign a device to this widget + views.setTextViewText(R.id.widget_title_text, context.getString(R.string.kde_connect)) + views.setTextViewText(R.id.widget_error_text, "Whose commands should we show? Click the title to set a device.") + } else { + views.setTextViewText(R.id.widget_title_text, device.name) + val plugin = device.getPlugin(RunCommandPlugin::class.java) + if (device.isReachable) { + val message: String = if (plugin == null) { + "Device doesn't allow us to run commands." + } else { + "Device has no commands available." + } + views.setTextViewText(R.id.widget_error_text, message) + assignListAdapter(context, appWidgetId, views) + assignListIntent(context, appWidgetId, views) + } else { + views.setTextViewText(R.id.widget_error_text, context.getString(R.string.runcommand_notreachable)) + } + } + + appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.widget_command_list) + appWidgetManager.updateAppWidget(appWidgetId, views) +} + +/** + * Create an Intent to launch the config activity whenever the title is clicked. + * + * See [RunCommandWidgetConfigActivity]. + */ +private fun assignTitleIntent(context: Context, appWidgetId: Int, views: RemoteViews) { val setDeviceIntent = Intent(context, RunCommandWidgetConfigActivity::class.java) setDeviceIntent.putExtra(EXTRA_APPWIDGET_ID, appWidgetId) // We pass appWidgetId as requestCode even if it's not used to force the creation a new PendingIntent // instead of reusing an existing one, which is what happens if only the "extras" field differs. // Docs: https://developer.android.com/reference/android/app/PendingIntent.html val setDevicePendingIntent = PendingIntent.getActivity(context, appWidgetId, setDeviceIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) - views.setOnClickPendingIntent(R.id.runcommandWidgetTitleHeader, setDevicePendingIntent) - - Log.d("WidgetProvider", "updateAppWidget device: " + if (device == null) "null" else device.name) - - if (device == null) { - views.setTextViewText(R.id.runcommandWidgetTitle, context.getString(R.string.kde_connect)) - views.setViewVisibility(R.id.run_commands_list, View.VISIBLE) - views.setViewVisibility(R.id.not_reachable_message, View.GONE) - } else { - views.setTextViewText(R.id.runcommandWidgetTitle, device.name) - if (device.isReachable) { - views.setViewVisibility(R.id.run_commands_list, View.VISIBLE) - views.setViewVisibility(R.id.not_reachable_message, View.GONE) - // Configure remote adapter - val dataProviderIntent = Intent(context, CommandsRemoteViewsService::class.java) - dataProviderIntent.putExtra(EXTRA_APPWIDGET_ID, appWidgetId) - dataProviderIntent.data = Uri.parse(dataProviderIntent.toUri(Intent.URI_INTENT_SCHEME)) - views.setRemoteAdapter(R.id.run_commands_list, dataProviderIntent) - // This pending intent allows the remote adapter to call fillInIntent so list items can do things - val runCommandTemplateIntent = Intent(context, RunCommandWidgetProvider::class.java) - runCommandTemplateIntent.action = RUN_COMMAND_ACTION - runCommandTemplateIntent.putExtra(EXTRA_APPWIDGET_ID, appWidgetId) - val runCommandTemplatePendingIntent = PendingIntent.getBroadcast(context, appWidgetId, runCommandTemplateIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE) - views.setPendingIntentTemplate(R.id.run_commands_list, runCommandTemplatePendingIntent) - } else { - views.setViewVisibility(R.id.run_commands_list, View.GONE) - views.setViewVisibility(R.id.not_reachable_message, View.VISIBLE) - } - } - - appWidgetManager.notifyAppWidgetViewDataChanged(appWidgetId, R.id.run_commands_list) - appWidgetManager.updateAppWidget(appWidgetId, views) + views.setOnClickPendingIntent(R.id.widget_title_wrapper, setDevicePendingIntent) +} + +/** + * Configure remote adapter + * + * This function can only be called once in the lifetime of the widget. Subsequent calls do nothing. + * Use [RunCommandWidgetConfigActivity] and the config function [saveWidgetDeviceIdPref] to change + * the adapter's behavior. + */ +private fun assignListAdapter(context: Context, appWidgetId: Int, views: RemoteViews) { + val dataProviderIntent = Intent(context, CommandsRemoteViewsService::class.java) + dataProviderIntent.putExtra(EXTRA_APPWIDGET_ID, appWidgetId) + dataProviderIntent.data = Uri.parse(dataProviderIntent.toUri(Intent.URI_INTENT_SCHEME)) + views.setRemoteAdapter(R.id.widget_command_list, dataProviderIntent) +} + +/** + * This pending intent allows the remote adapter to call fillInIntent so list items can do things. + * + * See [RemoteViews.setOnClickFillInIntent]. + */ +private fun assignListIntent(context: Context, appWidgetId: Int, views: RemoteViews) { + val runCommandTemplateIntent = Intent(context, RunCommandWidgetProvider::class.java) + runCommandTemplateIntent.action = RUN_COMMAND_ACTION + runCommandTemplateIntent.putExtra(EXTRA_APPWIDGET_ID, appWidgetId) + val runCommandTemplatePendingIntent = PendingIntent.getBroadcast(context, appWidgetId, runCommandTemplateIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE) + views.setPendingIntentTemplate(R.id.widget_command_list, runCommandTemplatePendingIntent) }