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); + } + +}