Index: AndroidManifest.xml =================================================================== --- AndroidManifest.xml +++ AndroidManifest.xml @@ -31,6 +31,7 @@ + + + + + + + + + + + + + + + Index: res/layout/list_item_entry.xml =================================================================== --- res/layout/list_item_entry.xml +++ res/layout/list_item_entry.xml @@ -11,7 +11,8 @@ android:paddingEnd="?android:attr/scrollbarSize" android:paddingLeft="12dip" android:paddingRight="?android:attr/scrollbarSize" - android:paddingStart="12dip"> + android:paddingStart="12dip" + android:id="@+id/list_item_entry"> Index: res/layout/widget_remotecommandplugin.xml =================================================================== --- res/layout/widget_remotecommandplugin.xml +++ res/layout/widget_remotecommandplugin.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + \ No newline at end of file Index: res/layout/widget_remotecommandplugin_dialog.xml =================================================================== --- res/layout/widget_remotecommandplugin_dialog.xml +++ res/layout/widget_remotecommandplugin_dialog.xml @@ -0,0 +1,16 @@ + + + + + + \ No newline at end of file Index: res/xml/remotecommandplugin_widget.xml =================================================================== --- res/xml/remotecommandplugin_widget.xml +++ res/xml/remotecommandplugin_widget.xml @@ -0,0 +1,3 @@ + + Index: src/org/kde/kdeconnect/Plugins/RunCommandPlugin/CommandEntry.java =================================================================== --- src/org/kde/kdeconnect/Plugins/RunCommandPlugin/CommandEntry.java +++ src/org/kde/kdeconnect/Plugins/RunCommandPlugin/CommandEntry.java @@ -37,4 +37,8 @@ public String getName() { return title; } + + public String getCommand() { + return subtitle; + } } Index: src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandPlugin.java =================================================================== --- src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandPlugin.java +++ src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandPlugin.java @@ -33,6 +33,7 @@ import org.kde.kdeconnect_tp.R; import java.util.ArrayList; +import java.util.Collections; import java.util.Iterator; public class RunCommandPlugin extends Plugin { @@ -42,6 +43,7 @@ private ArrayList commandList = new ArrayList<>(); private ArrayList callbacks = new ArrayList<>(); + private final ArrayList commandItems = new ArrayList<>(); private boolean canAddCommand; @@ -61,6 +63,10 @@ return commandList; } + public ArrayList getCommandItems() { + return commandItems; + } + @Override public String getDisplayName() { return context.getResources().getString(R.string.pref_plugin_runcommand); @@ -88,6 +94,7 @@ if (np.has("commandList")) { commandList.clear(); try { + commandItems.clear(); JSONObject obj = new JSONObject(np.getString("commandList")); Iterator keys = obj.keys(); while (keys.hasNext()) { @@ -95,7 +102,25 @@ 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") + ) + ); + } catch (JSONException e) { + e.printStackTrace(); + } } + + Collections.sort(commandItems, (lhs, rhs) -> lhs.getName().compareTo(rhs.getName()) ); + + Intent updateWidget = new Intent(context, RunCommandWidget.class); + context.sendBroadcast(updateWidget); + } catch (JSONException e) { e.printStackTrace(); } Index: src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidget.java =================================================================== --- src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidget.java +++ src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidget.java @@ -0,0 +1,154 @@ +package org.kde.kdeconnect.Plugins.RunCommandPlugin; + +import android.app.PendingIntent; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.support.annotation.RequiresApi; +import android.util.Log; +import android.view.View; +import android.widget.RemoteViews; + +import org.kde.kdeconnect.BackgroundService; +import org.kde.kdeconnect.Device; +import org.kde.kdeconnect_tp.R; + +@RequiresApi( api = Build.VERSION_CODES.ICE_CREAM_SANDWICH ) +public class RunCommandWidget extends AppWidgetProvider { + + public static final String RUN_COMMAND_ACTION = "RUN_COMMAND_ACTION"; + public static final String TARGET_COMMAND = "TARGET_COMMAND"; + public static final String TARGET_DEVICE = "TARGET_DEVICE"; + public static final String SET_CURRENT_DEVICE = "SET_CURRENT_DEVICE"; + + private static String currentDeviceId; + + @Override + public void onReceive(Context context, Intent intent) { + + super.onReceive(context, intent); + + if (intent != null && intent.getAction() != null && intent.getAction().equals(RUN_COMMAND_ACTION)) { + + final String targetCommand = intent.getStringExtra(TARGET_COMMAND); + final String targetDevice = intent.getStringExtra(TARGET_DEVICE); + + BackgroundService.RunCommand(context, service -> { + RunCommandPlugin plugin = service.getDevice(targetDevice).getPlugin(RunCommandPlugin.class); + + if (plugin != null) { + try { + + plugin.runCommand(targetCommand); + } catch (Exception ex) { + Log.e("RunCommandWidget", "Error running command", ex); + } + } + }); + } else if (intent != null && intent.getAction() != null && intent.getAction().equals(SET_CURRENT_DEVICE)) { + setCurrentDevice(context); + } + + final Intent newIntent = new Intent(context, RunCommandWidgetDataProviderService.class); + context.startService(newIntent); + updateWidget(context); + + } + + private void setCurrentDevice(final Context context) { + Intent popup = new Intent(context, RunCommandWidgetDeviceSelector.class); + popup.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + + context.startActivity(popup); + } + + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + updateWidget(context); + } + + private void updateWidget(final Context context) { + + if (getCurrentDevice() == null || !getCurrentDevice().isReachable()) { + + BackgroundService.RunCommand(context, service -> { + if (service.getDevices().size() > 0) + currentDeviceId = service.getDevices().elements().nextElement().getDeviceId(); + + updateWidgetImpl(context); + }); + } + + updateWidgetImpl(context); + } + + private void updateWidgetImpl(Context context) { + + try { + + AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); + int[] appWidgetIds = appWidgetManager.getAppWidgetIds(new ComponentName(context, RunCommandWidget.class)); + RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_remotecommandplugin); + + PendingIntent pendingIntent; + Intent intent; + + intent = new Intent(context, RunCommandWidget.class); + intent.setAction(SET_CURRENT_DEVICE); + pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + views.setOnClickPendingIntent(R.id.runcommandWidgetTitleHeader, pendingIntent); + + if (getCurrentDevice() == null || !getCurrentDevice().isReachable()) { + views.setTextViewText(R.id.runcommandWidgetTitle, context.getString(R.string.pref_plugin_runcommand)); + views.setViewVisibility(R.id.runcommandslist, View.GONE); + views.setViewVisibility(R.id.not_reachable_message, View.VISIBLE); + } else { + views.setTextViewText(R.id.runcommandWidgetTitle, getCurrentDevice().getName()); + views.setViewVisibility(R.id.runcommandslist, View.VISIBLE); + views.setViewVisibility(R.id.not_reachable_message, View.GONE); + } + + for (int appWidgetId : appWidgetIds) { + + intent = new Intent(context, RunCommandWidgetDataProviderService.class); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); + intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))); + views.setRemoteAdapter(R.id.runcommandslist, intent); + + intent = new Intent(context, RunCommandWidget.class); + pendingIntent = PendingIntent.getBroadcast(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); + views.setPendingIntentTemplate(R.id.runcommandslist, pendingIntent); + + AppWidgetManager.getInstance(context).updateAppWidget(appWidgetId, views); + AppWidgetManager.getInstance(context).notifyAppWidgetViewDataChanged(appWidgetId, R.id.runcommandslist); + + } + + } catch (Exception ex) { + Log.e("RunCommandWidget", "Error updating widget", ex); + } + + if (BackgroundService.getInstance() != null) { + BackgroundService.getInstance().addDeviceListChangedCallback("RunCommandWidget", () -> { + Intent updateWidget = new Intent(context, RunCommandWidget.class); + context.sendBroadcast(updateWidget); + }); + } + } + + public static Device getCurrentDevice() { + + try { + return BackgroundService.getInstance().getDevice(currentDeviceId); + } catch (Exception ex) { + return null; + } + } + + public static void setCurrentDevice(final String DeviceId) { + currentDeviceId = DeviceId; + } +} \ No newline at end of file Index: src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidgetDataProvider.java =================================================================== --- src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidgetDataProvider.java +++ src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidgetDataProvider.java @@ -0,0 +1,93 @@ +package org.kde.kdeconnect.Plugins.RunCommandPlugin; + +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.support.annotation.RequiresApi; +import android.view.View; +import android.widget.RemoteViews; +import android.widget.RemoteViewsService; + +import org.kde.kdeconnect_tp.R; + + +@RequiresApi( api = Build.VERSION_CODES.ICE_CREAM_SANDWICH ) +public class RunCommandWidgetDataProvider implements RemoteViewsService.RemoteViewsFactory { + + private final Context mContext; + + public RunCommandWidgetDataProvider(Context context, Intent intent) { + mContext = context; + } + + private boolean checkPlugin() { + if (RunCommandWidget.getCurrentDevice() == null || !RunCommandWidget.getCurrentDevice().isReachable()) + return false; + + return RunCommandWidget.getCurrentDevice().getPlugin(RunCommandPlugin.class) != null; + } + + @Override + public void onCreate() { + } + + @Override + public void onDataSetChanged() { + + } + + @Override + public void onDestroy() { + } + + @Override + public int getCount() { + return checkPlugin() ? RunCommandWidget.getCurrentDevice().getPlugin(RunCommandPlugin.class).getCommandItems().size() : 0; + } + + @Override + public RemoteViews getViewAt(int i) { + + RemoteViews remoteView = new RemoteViews(mContext.getPackageName(), R.layout.list_item_entry); + + if (checkPlugin() && RunCommandWidget.getCurrentDevice().getPlugin(RunCommandPlugin.class).getCommandItems().size() > i) { + CommandEntry listItem = RunCommandWidget.getCurrentDevice().getPlugin(RunCommandPlugin.class).getCommandItems().get(i); + + final Intent configIntent = new Intent(mContext, RunCommandWidget.class); + configIntent.setAction(RunCommandWidget.RUN_COMMAND_ACTION); + configIntent.putExtra(RunCommandWidget.TARGET_COMMAND, listItem.getKey()); + configIntent.putExtra(RunCommandWidget.TARGET_DEVICE, RunCommandWidget.getCurrentDevice().getDeviceId()); + + remoteView.setTextViewText(R.id.list_item_entry_title, listItem.getName()); + remoteView.setTextViewText(R.id.list_item_entry_summary, listItem.getCommand()); + remoteView.setViewVisibility(R.id.list_item_entry_summary, View.VISIBLE); + + remoteView.setOnClickFillInIntent(R.id.list_item_entry, configIntent); + } + + return remoteView; + } + + @Override + public RemoteViews getLoadingView() { + return null; + } + + @Override + public int getViewTypeCount() { + return 1; + } + + @Override + public long getItemId(int i) { + if (RunCommandWidget.getCurrentDevice() != null) + return RunCommandWidget.getCurrentDevice().getPlugin(RunCommandPlugin.class).getCommandItems().get(i).getKey().hashCode(); + + return 0; + } + + @Override + public boolean hasStableIds() { + return false; + } +} \ No newline at end of file Index: src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidgetDataProviderService.java =================================================================== --- src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidgetDataProviderService.java +++ src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidgetDataProviderService.java @@ -0,0 +1,16 @@ +package org.kde.kdeconnect.Plugins.RunCommandPlugin; + +import android.content.Intent; +import android.os.Build; +import android.support.annotation.RequiresApi; +import android.widget.RemoteViewsService; + +@RequiresApi( api = Build.VERSION_CODES.ICE_CREAM_SANDWICH ) +public class RunCommandWidgetDataProviderService extends RemoteViewsService { + + @Override + public RemoteViewsFactory onGetViewFactory(Intent intent) { + + return (new RunCommandWidgetDataProvider(RunCommandWidgetDataProviderService.this, intent)); + } +} \ No newline at end of file Index: src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidgetDeviceSelector.java =================================================================== --- src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidgetDeviceSelector.java +++ src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandWidgetDeviceSelector.java @@ -0,0 +1,61 @@ +package org.kde.kdeconnect.Plugins.RunCommandPlugin; + +import android.content.Intent; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.RequiresApi; +import android.support.v7.app.AppCompatActivity; +import android.view.Window; +import android.widget.ListView; + +import org.kde.kdeconnect.BackgroundService; +import org.kde.kdeconnect.Device; +import org.kde.kdeconnect.UserInterface.List.ListAdapter; +import org.kde.kdeconnect_tp.R; + +import java.util.ArrayList; +import java.util.Collections; + +@RequiresApi( api = Build.VERSION_CODES.ICE_CREAM_SANDWICH ) +public class RunCommandWidgetDeviceSelector extends AppCompatActivity { + @Override + protected void onCreate(Bundle bundle) { + super.onCreate(bundle); + + requestWindowFeature(Window.FEATURE_NO_TITLE); + setContentView(R.layout.widget_remotecommandplugin_dialog); + + BackgroundService.RunCommand(this, service -> runOnUiThread(() -> { + ListView view = (ListView) findViewById(R.id.runcommandsdevicelist); + + final ArrayList deviceItems = new ArrayList<>(); + + for (Device device : service.getDevices().values()) { + if (device.isPaired() && device.isReachable()) { + deviceItems.add( + new CommandEntry( + device.getName(), + null, + device.getDeviceId() + ) + ); + } + } + + Collections.sort(deviceItems, (lhs, rhs) -> ((CommandEntry) lhs).getName().compareTo(((CommandEntry) rhs).getName())); + + ListAdapter adapter = new ListAdapter(RunCommandWidgetDeviceSelector.this, deviceItems); + + view.setAdapter(adapter); + view.setOnItemClickListener((adapterView, viewContent, i, l) -> { + CommandEntry entry = (CommandEntry) deviceItems.get(i); + RunCommandWidget.setCurrentDevice(entry.getKey()); + + Intent updateWidget = new Intent(RunCommandWidgetDeviceSelector.this, RunCommandWidget.class); + RunCommandWidgetDeviceSelector.this.sendBroadcast(updateWidget); + + finish(); + }); + })); + } +} \ No newline at end of file