diff --git a/AndroidManifest.xml b/AndroidManifest.xml
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -199,14 +199,24 @@
+
+
+
+
+
diff --git a/res/drawable-hdpi/ic_presenter.png b/res/drawable-hdpi/ic_presenter.png
new file mode 100644
index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/menu/menu_presenter.xml b/res/menu/menu_presenter.xml
new file mode 100644
--- /dev/null
+++ b/res/menu/menu_presenter.xml
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -11,6 +11,8 @@
Share the clipboard content
Remote input
Use your phone or tablet as a touchpad and keyboard
+ Presentation remote
+ Use your device to change slides in a presentation
Receive remote keypresses
Receive keypress events from remote devices
Multimedia controls
@@ -236,6 +238,10 @@
Device icon
Settings icon
+ Fullscreen
+ Exit presentation
+ You can lock your device to use the volume keys as previous/next buttons
+
Add a command
There are no commands registered
You can add new commands in the KDE Connect System Settings
diff --git a/src/org/kde/kdeconnect/Plugins/MousePadPlugin/KeyListenerView.java b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/KeyListenerView.java
--- a/src/org/kde/kdeconnect/Plugins/MousePadPlugin/KeyListenerView.java
+++ b/src/org/kde/kdeconnect/Plugins/MousePadPlugin/KeyListenerView.java
@@ -37,7 +37,7 @@
private String deviceId;
- private static SparseIntArray SpecialKeysMap = new SparseIntArray();
+ public static SparseIntArray SpecialKeysMap = new SparseIntArray();
static {
int i = 0;
diff --git a/src/org/kde/kdeconnect/Plugins/PluginFactory.java b/src/org/kde/kdeconnect/Plugins/PluginFactory.java
--- a/src/org/kde/kdeconnect/Plugins/PluginFactory.java
+++ b/src/org/kde/kdeconnect/Plugins/PluginFactory.java
@@ -35,6 +35,7 @@
import org.kde.kdeconnect.Plugins.MprisReceiverPlugin.MprisReceiverPlugin;
import org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationsPlugin;
import org.kde.kdeconnect.Plugins.PingPlugin.PingPlugin;
+import org.kde.kdeconnect.Plugins.PresenterPlugin.PresenterPlugin;
import org.kde.kdeconnect.Plugins.ReceiveNotificationsPlugin.ReceiveNotificationsPlugin;
import org.kde.kdeconnect.Plugins.RemoteKeyboardPlugin.RemoteKeyboardPlugin;
import org.kde.kdeconnect.Plugins.RunCommandPlugin.RunCommandPlugin;
@@ -127,6 +128,7 @@
PluginFactory.registerPlugin(NotificationsPlugin.class);
PluginFactory.registerPlugin(ReceiveNotificationsPlugin.class);
PluginFactory.registerPlugin(MousePadPlugin.class);
+ PluginFactory.registerPlugin(PresenterPlugin.class);
PluginFactory.registerPlugin(SharePlugin.class);
PluginFactory.registerPlugin(TelepathyPlugin.class);
PluginFactory.registerPlugin(FindMyPhonePlugin.class);
diff --git a/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterActivity.java b/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterActivity.java
new file mode 100644
--- /dev/null
+++ b/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterActivity.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright 2014 Ahmed I. Khalil
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+package org.kde.kdeconnect.Plugins.PresenterPlugin;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.PowerManager;
+import android.support.v4.media.VolumeProviderCompat;
+import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.media.session.PlaybackStateCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+
+import org.kde.kdeconnect.BackgroundService;
+import org.kde.kdeconnect.Device;
+import org.kde.kdeconnect.UserInterface.ThemeUtil;
+import org.kde.kdeconnect_tp.R;
+
+public class PresenterActivity extends AppCompatActivity {
+
+ private MediaSessionCompat mMediaSession;
+
+ PresenterPlugin plugin;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ ThemeUtil.setUserPreferredTheme(this);
+
+ setContentView(R.layout.activity_presenter);
+
+ final String deviceId = getIntent().getStringExtra("deviceId");
+
+ BackgroundService.RunCommand(this, service -> {
+ Device device = service.getDevice(deviceId);
+
+ plugin = device.getPlugin(PresenterPlugin.class);
+ if (plugin == null) {
+ Log.e("PresenterActivity", "device has no presenter plugin!");
+ return;
+ }
+
+ runOnUiThread(() -> {
+
+ findViewById(R.id.next_button).setOnClickListener(v -> {
+ plugin.sendNext();
+ });
+
+ findViewById(R.id.previous_button).setOnClickListener(v -> {
+ plugin.sendPrevious();
+ });
+
+
+ });
+ });
+
+ }
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ MenuInflater inflater = getMenuInflater();
+ inflater.inflate(R.menu.menu_presenter, menu);
+ return true;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch(item.getItemId()) {
+ case R.id.fullscreen:
+ plugin.sendFullscreen();
+ return true;
+ case R.id.exit_presentation:
+ plugin.sendEsc();
+ return true;
+ default:
+ return super.onContextItemSelected(item);
+ }
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ BackgroundService.addGuiInUseCounter(this);
+ if (mMediaSession != null) {
+ mMediaSession.setActive(true);
+ return;
+ }
+ createMediaSession(); //Mediasession will keep
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ BackgroundService.removeGuiInUseCounter(this);
+
+ if (mMediaSession != null) {
+ PowerManager pm = (PowerManager) this.getSystemService(Context.POWER_SERVICE);
+ boolean screenOn;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) {
+ screenOn = pm.isInteractive();
+ } else {
+ screenOn = pm.isScreenOn();
+ }
+ if (screenOn) {
+ mMediaSession.release();
+ } // else we are in the lockscreen, keep the mediasession
+ }
+ }
+
+ private void createMediaSession() {
+ mMediaSession = new MediaSessionCompat(this, "kdeconnect");
+
+ mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
+ MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
+ mMediaSession.setPlaybackState(new PlaybackStateCompat.Builder()
+ .setState(PlaybackStateCompat.STATE_PLAYING, 0, 0)
+ .build());
+ mMediaSession.setPlaybackToRemote(getVolumeProvider());
+ mMediaSession.setActive(true);
+ }
+
+ private VolumeProviderCompat getVolumeProvider() {
+ final int VOLUME_UP = 1;
+ final int VOLUME_DOWN = -1;
+ return new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, 1, 0) {
+ @Override
+ public void onAdjustVolume(int direction) {
+ if (direction == VOLUME_UP) {
+ plugin.sendNext();
+ }
+ else if (direction == VOLUME_DOWN) {
+ plugin.sendPrevious();
+ }
+ }
+ };
+ }
+
+}
+
diff --git a/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterPlugin.java b/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterPlugin.java
new file mode 100644
--- /dev/null
+++ b/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterPlugin.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2014 Ahmed I. Khalil
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License or (at your option) version 3 or any later version
+ * accepted by the membership of KDE e.V. (or its successor approved
+ * by the membership of KDE e.V.), which shall act as a proxy
+ * defined in Section 14 of version 3 of the license.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+*/
+
+package org.kde.kdeconnect.Plugins.PresenterPlugin;
+
+
+import android.app.Activity;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.support.v4.content.ContextCompat;
+import android.view.KeyEvent;
+
+import org.kde.kdeconnect.NetworkPacket;
+import org.kde.kdeconnect.Plugins.Plugin;
+import org.kde.kdeconnect_tp.R;
+
+import static org.kde.kdeconnect.Plugins.MousePadPlugin.KeyListenerView.SpecialKeysMap;
+
+public class PresenterPlugin extends Plugin {
+
+ public final static String PACKET_TYPE_MOUSEPAD_REQUEST = "kdeconnect.mousepad.request";
+
+ @Override
+ public String getDisplayName() {
+ return context.getString(R.string.pref_plugin_presenter);
+ }
+
+ @Override
+ public String getDescription() {
+ return context.getString(R.string.pref_plugin_presenter_desc);
+ }
+
+ @Override
+ public Drawable getIcon() {
+ return ContextCompat.getDrawable(context, R.drawable.ic_presenter);
+ }
+
+ @Override
+ public boolean hasSettings() {
+ return false;
+ }
+
+ @Override
+ public boolean hasMainActivity() {
+ return true;
+ }
+
+ @Override
+ public void startMainActivity(Activity parentActivity) {
+ Intent intent = new Intent(parentActivity, PresenterActivity.class);
+ intent.putExtra("deviceId", device.getDeviceId());
+ parentActivity.startActivity(intent);
+ }
+
+ @Override
+ public String[] getSupportedPacketTypes() {
+ return new String[0];
+ }
+
+ @Override
+ public String[] getOutgoingPacketTypes() {
+ return new String[]{PACKET_TYPE_MOUSEPAD_REQUEST};
+ }
+
+ @Override
+ public String getActionName() {
+ return context.getString(R.string.pref_plugin_presenter);
+ }
+
+ public void sendNext() {
+ NetworkPacket np = new NetworkPacket(PACKET_TYPE_MOUSEPAD_REQUEST);
+ np.set("specialKey", SpecialKeysMap.get(KeyEvent.KEYCODE_PAGE_DOWN));
+ device.sendPacket(np);
+ }
+
+ public void sendPrevious() {
+ NetworkPacket np = new NetworkPacket(PACKET_TYPE_MOUSEPAD_REQUEST);
+ np.set("specialKey", SpecialKeysMap.get(KeyEvent.KEYCODE_PAGE_UP));
+ device.sendPacket(np);
+ }
+
+ public void sendFullscreen() {
+ NetworkPacket np = new NetworkPacket(PACKET_TYPE_MOUSEPAD_REQUEST);
+ np.set("specialKey", SpecialKeysMap.get(KeyEvent.KEYCODE_F5));
+ device.sendPacket(np);
+ }
+
+ public void sendEsc() {
+ NetworkPacket np = new NetworkPacket(PACKET_TYPE_MOUSEPAD_REQUEST);
+ np.set("specialKey", SpecialKeysMap.get(KeyEvent.KEYCODE_ESCAPE));
+ device.sendPacket(np);
+ }
+
+}