2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-22 01:51:47 +00:00

WIP Power plugin

This commit is contained in:
Albert Vaca Cintora 2020-10-18 14:49:59 +02:00
parent c429bb81b0
commit a331b1e3f1
8 changed files with 322 additions and 1 deletions

View File

@ -114,6 +114,14 @@
</intent-filter>
</service>
<!-- New signature permission to ensure only systemui can bind to these services -->
<service android:name="org.kde.kdeconnect.Plugins.PowerPlugin.PowerControlsService" android:label="@string/kde_connect"
android:permission="android.permission.BIND_CONTROLS">
<intent-filter>
<action android:name="android.service.controls.ControlsProviderService" />
</intent-filter>
</service>
<service
android:name="com.android.mms.transaction.TransactionService"
android:enabled="true"

View File

@ -18,7 +18,7 @@ buildscript {
}
android {
compileSdkVersion 29
compileSdkVersion 30
defaultConfig {
minSdkVersion 14
targetSdkVersion 29
@ -167,6 +167,8 @@ dependencies {
implementation 'com.google.android.material:material:1.2.1'
implementation 'com.jakewharton:disklrucache:2.0.2' //For caching album art bitmaps
implementation 'com.jaredrummler:android-device-names:1.1.9' //To get a human-friendly device name
implementation 'org.reactivestreams:reactive-streams:1.0.3'
implementation 'io.reactivex.rxjava2:rxjava:2.2.0'
implementation 'org.apache.sshd:sshd-core:0.14.0'
implementation 'org.apache.mina:mina-core:2.0.19' //For some reason, makes sshd-core:0.14.0 work without NIO, which isn't available until Android 8 (api 26)

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button
android:id="@+id/shutdown"
android:layout_width="fill_parent"
android:layout_height="75dp"
android:layout_gravity="center"
android:adjustViewBounds="false"
android:baselineAlignBottom="true"
android:clickable="false"
android:text="@string/power_shutdown"
android:theme="@style/DisableableButton" />
<Button
android:id="@+id/suspend"
android:layout_width="fill_parent"
android:layout_height="75dp"
android:layout_gravity="center"
android:adjustViewBounds="false"
android:baselineAlignBottom="true"
android:clickable="false"
android:text="@string/power_suspend"
android:theme="@style/DisableableButton" />
<Button
android:id="@+id/lock"
android:layout_width="fill_parent"
android:layout_height="75dp"
android:layout_gravity="center"
android:adjustViewBounds="false"
android:baselineAlignBottom="true"
android:clickable="false"
android:text="@string/power_lock"
android:theme="@style/DisableableButton" />
</LinearLayout>

View File

@ -29,6 +29,8 @@
<string name="pref_plugin_mpris_desc">Provides a remote control for your media player</string>
<string name="pref_plugin_runcommand">Run Command</string>
<string name="pref_plugin_runcommand_desc">Trigger remote commands from your phone or tablet</string>
<string name="pref_plugin_power">Power</string>
<string name="pref_plugin_power_desc">Power off, lock or suspend your device</string>
<string name="pref_plugin_contacts">Contacts Synchronizer</string>
<string name="pref_plugin_contacts_desc">Allow synchronizing the device\'s contacts book</string>
<string name="pref_plugin_ping">Ping</string>
@ -183,6 +185,9 @@
<string name="mpris_time_settings_title">Forward/rewind buttons</string>
<string name="mpris_time_settings_summary">Adjust the time to fast forward/rewind when pressed</string>
<string name="mpris_time_key" translatable="false">mpris_interval_time</string>
<string name="power_shutdown">Shutdown</string>
<string name="power_suspend">Suspend</string>
<string name="power_lock">Lock screen</string>
<string-array name="mpris_time_entries">
<item>10 seconds</item>
<item>20 seconds</item>

View File

@ -0,0 +1,54 @@
/*
* SPDX-FileCopyrightText: 2020 Albert Vaca Cintora <albertvaka@gmail.com>
*
* 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");
}
)
);
}
}

View File

@ -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<Control> 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<Control> createPublisherFor(@NonNull List<String> 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.
*/
}
}
}

View File

@ -0,0 +1,81 @@
/*
* SPDX-FileCopyrightText: 2015 Aleix Pol Gonzalez <aleixpol@kde.org>
* SPDX-FileCopyrightText: 2015 Albert Vaca Cintora <albertvaka@gmail.com>
*
* 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);
}
}

View File

@ -0,0 +1,22 @@
package org.kde.kdeconnect.Plugins.PowerPlugin;
import java.util.HashMap;
import java.util.concurrent.Flow;
public class PowerPluginsSingleton {
HashMap<String, PowerPlugin> availableDevices = new HashMap<>();
static public PowerPluginsSingleton getInstance() {
if (instance == null) {
instance = new PowerPluginsSingleton();
}
return instance;
}
private static PowerPluginsSingleton instance = null;
private PowerPluginsSingleton() { }
}