diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 3e21a813..74d4549b 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -114,6 +114,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 0503030a..54dbd3dd 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -29,6 +29,8 @@
Provides a remote control for your media player
Run Command
Trigger remote commands from your phone or tablet
+ Power
+ Power off, lock or suspend your device
Contacts Synchronizer
Allow synchronizing the device\'s contacts book
Ping
@@ -183,6 +185,9 @@
Forward/rewind buttons
Adjust the time to fast forward/rewind when pressed
mpris_interval_time
+ Shutdown
+ Suspend
+ Lock screen
- 10 seconds
- 20 seconds
diff --git a/src/org/kde/kdeconnect/Plugins/PowerPlugin/PowerActivity.java b/src/org/kde/kdeconnect/Plugins/PowerPlugin/PowerActivity.java
new file mode 100644
index 00000000..e03cd1a5
--- /dev/null
+++ b/src/org/kde/kdeconnect/Plugins/PowerPlugin/PowerActivity.java
@@ -0,0 +1,54 @@
+/*
+ * SPDX-FileCopyrightText: 2020 Albert Vaca Cintora
+ *
+ * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
+ */
+
+package org.kde.kdeconnect.Plugins.PowerPlugin;
+
+import android.os.Bundle;
+
+import androidx.appcompat.app.AppCompatActivity;
+
+import org.kde.kdeconnect.BackgroundService;
+import org.kde.kdeconnect.UserInterface.ThemeUtil;
+import org.kde.kdeconnect_tp.databinding.ActivityPowerBinding;
+
+public class PowerActivity extends AppCompatActivity {
+ private String deviceId;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ThemeUtil.setUserPreferredTheme(this);
+
+ ActivityPowerBinding binding = ActivityPowerBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ deviceId = getIntent().getStringExtra("deviceId");
+
+ binding.shutdown.setOnClickListener(
+ v -> BackgroundService.RunWithPlugin(PowerActivity.this, deviceId, PowerPlugin.class, plugin ->
+ {
+ plugin.sendCommand("shutdown");
+ }
+ )
+ );
+
+ binding.suspend.setOnClickListener(
+ v -> BackgroundService.RunWithPlugin(PowerActivity.this, deviceId, PowerPlugin.class, plugin ->
+ {
+ plugin.sendCommand("suspend");
+ }
+ )
+ );
+
+ binding.lock.setOnClickListener(
+ v -> BackgroundService.RunWithPlugin(PowerActivity.this, deviceId, PowerPlugin.class, plugin ->
+ {
+ plugin.sendCommand("lock");
+ }
+ )
+ );
+ }
+}
diff --git a/src/org/kde/kdeconnect/Plugins/PowerPlugin/PowerControlsService.java b/src/org/kde/kdeconnect/Plugins/PowerPlugin/PowerControlsService.java
new file mode 100644
index 00000000..f0e8df1b
--- /dev/null
+++ b/src/org/kde/kdeconnect/Plugins/PowerPlugin/PowerControlsService.java
@@ -0,0 +1,106 @@
+package org.kde.kdeconnect.Plugins.PowerPlugin;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.service.controls.Control;
+import android.service.controls.ControlsProviderService;
+import android.service.controls.DeviceTypes;
+import android.service.controls.actions.BooleanAction;
+import android.service.controls.actions.ControlAction;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+
+import org.reactivestreams.FlowAdapters;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Flow;
+import java.util.function.Consumer;
+
+import io.reactivex.Flowable;
+import io.reactivex.processors.ReplayProcessor;
+
+@RequiresApi(30)
+public class PowerControlsService extends ControlsProviderService {
+
+ private ReplayProcessor updatePublisher;
+ ArrayList controls =new ArrayList<>();
+
+ final static String MY_TEST_ID = "myAwesomeId";
+
+ @NonNull
+ @Override
+ public Flow.Publisher createPublisherForAllAvailable() {
+ Context context=getBaseContext();
+ Intent i=new Intent();
+ PendingIntent pi=PendingIntent.getActivity(context,1,i,PendingIntent.FLAG_UPDATE_CURRENT);
+
+
+ Control control = new Control.StatelessBuilder(MY_TEST_ID, pi)
+ .setTitle("My device")
+ .setSubtitle("KDE Connect")
+ .setDeviceType(DeviceTypes.TYPE_DISPLAY) //There's no type "computer"
+ .build();
+
+ controls.add(control);
+
+ return FlowAdapters.toFlowPublisher(Flowable.fromIterable(controls));
+ }
+
+
+ @NonNull
+ @Override
+ public Flow.Publisher createPublisherFor(@NonNull List controlIds) {
+
+ Context context = getBaseContext();
+ /* Fill in details for the activity related to this device. On long press,
+ * this Intent will be launched in a bottomsheet. Please design the activity
+ * accordingly to fit a more limited space (about 2/3 screen height).
+ */
+ Intent i = new Intent();
+ PendingIntent pi = PendingIntent.getActivity(context, 1, i, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ updatePublisher = ReplayProcessor.create();
+
+ // For each controlId in controlIds
+ if (controlIds.contains(MY_TEST_ID)) {
+
+ Control control = new Control.StatefulBuilder(MY_TEST_ID, pi)
+ .setTitle("My device")
+ .setSubtitle("KDE Connect")
+ .setDeviceType(DeviceTypes.TYPE_DISPLAY) //There's no type "computer"
+ .setStatus(Control.STATUS_OK) // For example, Control.STATUS_OK
+ .build();
+
+ updatePublisher.onNext(control);
+ }
+
+ return FlowAdapters.toFlowPublisher(updatePublisher);
+ }
+
+ @Override
+ public void performControlAction(@NonNull String controlId, @NonNull ControlAction action, @NonNull Consumer consumer) {
+
+ /* First, locate the control identified by the controlId. Once it is located, you can
+ * interpret the action appropriately for that specific device. For instance, the following
+ * assumes that the controlId is associated with a light, and the light can be turned on
+ * or off.
+ */
+ if (action instanceof BooleanAction) {
+
+ // Inform SystemUI that the action has been received and is being processed
+ consumer.accept(ControlAction.RESPONSE_OK);
+
+
+ /* This is where application logic/network requests would be invoked to update the state of
+ * the device.
+ * After updating, the application should use the publisher to update SystemUI with the new
+ * state.
+ */
+
+ }
+ }
+}
diff --git a/src/org/kde/kdeconnect/Plugins/PowerPlugin/PowerPlugin.java b/src/org/kde/kdeconnect/Plugins/PowerPlugin/PowerPlugin.java
new file mode 100644
index 00000000..c6bdb536
--- /dev/null
+++ b/src/org/kde/kdeconnect/Plugins/PowerPlugin/PowerPlugin.java
@@ -0,0 +1,81 @@
+/*
+ * SPDX-FileCopyrightText: 2015 Aleix Pol Gonzalez
+ * SPDX-FileCopyrightText: 2015 Albert Vaca Cintora
+ *
+ * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
+*/
+
+package org.kde.kdeconnect.Plugins.PowerPlugin;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+
+import androidx.core.content.ContextCompat;
+
+import org.kde.kdeconnect.NetworkPacket;
+import org.kde.kdeconnect.Plugins.Plugin;
+import org.kde.kdeconnect.Plugins.PluginFactory;
+import org.kde.kdeconnect_tp.R;
+
+@PluginFactory.LoadablePlugin
+public class PowerPlugin extends Plugin {
+
+ private final static String PACKET_TYPE_POWER = "kdeconnect.power";
+
+ @Override
+ public String getDisplayName() {
+ return context.getResources().getString(R.string.pref_plugin_runcommand);
+ }
+
+ @Override
+ public String getDescription() {
+ return context.getResources().getString(R.string.pref_plugin_runcommand_desc);
+ }
+
+ @Override
+ public Drawable getIcon() {
+ return ContextCompat.getDrawable(context, R.drawable.run_command_plugin_icon_24dp);
+ }
+
+ @Override
+ public boolean onCreate() {
+
+
+ return true;
+ }
+
+ @Override
+ public String[] getSupportedPacketTypes() {
+ return new String[]{};
+ }
+
+ @Override
+ public String[] getOutgoingPacketTypes() {
+ return new String[]{PACKET_TYPE_POWER};
+ }
+
+ public void sendCommand(String command) {
+ NetworkPacket np = new NetworkPacket(PACKET_TYPE_POWER);
+ np.set("command", command);
+ device.sendPacket(np);
+ }
+
+ @Override
+ public boolean hasMainActivity() {
+ return true;
+ }
+
+ @Override
+ public void startMainActivity(Activity parentActivity) {
+ Intent intent = new Intent(parentActivity, PowerActivity.class);
+ intent.putExtra("deviceId", device.getDeviceId());
+ parentActivity.startActivity(intent);
+ }
+
+ @Override
+ public String getActionName() {
+ return context.getString(R.string.pref_plugin_power);
+ }
+
+}
diff --git a/src/org/kde/kdeconnect/Plugins/PowerPlugin/PowerPluginsSingleton.java b/src/org/kde/kdeconnect/Plugins/PowerPlugin/PowerPluginsSingleton.java
new file mode 100644
index 00000000..e0007ce3
--- /dev/null
+++ b/src/org/kde/kdeconnect/Plugins/PowerPlugin/PowerPluginsSingleton.java
@@ -0,0 +1,22 @@
+package org.kde.kdeconnect.Plugins.PowerPlugin;
+
+import java.util.HashMap;
+import java.util.concurrent.Flow;
+
+public class PowerPluginsSingleton {
+
+ HashMap availableDevices = new HashMap<>();
+
+
+ static public PowerPluginsSingleton getInstance() {
+ if (instance == null) {
+ instance = new PowerPluginsSingleton();
+ }
+ return instance;
+ }
+
+ private static PowerPluginsSingleton instance = null;
+ private PowerPluginsSingleton() { }
+
+
+}