Commit 8d9d15e6 authored by Albert Vaca Cintora's avatar Albert Vaca Cintora

Added a plugin to present slideshows

Summary:
This adds a plugin with buttons for next and previous slide and fullscreen.
It also allows you to use volume up/down to change slide, even when the screen is off.
It can be extended to show do more presentation-related stuff eventually.
On the desktop side, it uses the already present 'mousepad' (keyboard input) plugin.

Test Plan: Tested manually.

Reviewers: #kde_connect, nicolasfella, apol

Reviewed By: #kde_connect, nicolasfella, apol

Subscribers: apol, nicolasfella, kdeconnect

Tags: #kde_connect

Differential Revision: https://phabricator.kde.org/D14430
parent 3acce7dd
......@@ -199,14 +199,24 @@
<activity
android:name="org.kde.kdeconnect.Plugins.MousePadPlugin.MousePadActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/remote_control"
android:parentActivityName="org.kde.kdeconnect.UserInterface.MainActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.UserInterface.MainActivity" />
</activity>
<activity
android:name="org.kde.kdeconnect.Plugins.PresenterPlugin.PresenterActivity"
android:configChanges="orientation|keyboardHidden|screenSize"
android:label="@string/pref_plugin_presenter"
android:parentActivityName="org.kde.kdeconnect.UserInterface.MainActivity"
android:windowSoftInputMode="stateHidden|adjustResize">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value="org.kde.kdeconnect.UserInterface.MainActivity" />
</activity>
<activity
android:name="org.kde.kdeconnect.Plugins.SharePlugin.ShareActivity"
android:label="KDE Connect">
......
<?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:id="@+id/mpris_control_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center"
android:orientation="vertical">
<Space
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.25" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="60dip"
android:layout_gravity="center"
android:orientation="horizontal">
<ImageButton
android:id="@+id/previous_button"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:layout_weight="0.25"
android:contentDescription="@string/mpris_rew"
android:src="@drawable/ic_previous_black"
android:theme="@style/DisableableButton" />
<ImageButton
android:id="@+id/next_button"
android:layout_width="match_parent"
android:layout_height="fill_parent"
android:layout_weight="0.25"
android:contentDescription="@string/mpris_ff"
android:src="@drawable/ic_next_black"
android:theme="@style/DisableableButton" />
</LinearLayout>
<TextView
style="@android:style/TextAppearance.Medium"
android:padding="12dip"
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0.25"
android:gravity="center"
android:text="@string/presenter_lock_tip" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/fullscreen"
android:title="@string/presenter_fullscreen" />
<item
android:id="@+id/exit_presentation"
android:title="@string/presenter_exit" />
</menu>
\ No newline at end of file
......@@ -11,6 +11,8 @@
<string name="pref_plugin_clipboard_desc">Share the clipboard content</string>
<string name="pref_plugin_mousepad">Remote input</string>
<string name="pref_plugin_mousepad_desc">Use your phone or tablet as a touchpad and keyboard</string>
<string name="pref_plugin_presenter">Presentation remote</string>
<string name="pref_plugin_presenter_desc">Use your device to change slides in a presentation</string>
<string name="pref_plugin_remotekeyboard">Receive remote keypresses</string>
<string name="pref_plugin_remotekeyboard_desc">Receive keypress events from remote devices</string>
<string name="pref_plugin_mpris">Multimedia controls</string>
......@@ -236,6 +238,10 @@
<string name="device_icon_description">Device icon</string>
<string name="settings_icon_description">Settings icon</string>
<string name="presenter_fullscreen">Fullscreen</string>
<string name="presenter_exit">Exit presentation</string>
<string name="presenter_lock_tip">You can lock your device to use the volume keys as previous/next buttons</string>
<string name="add_command">Add a command</string>
<string name="addcommand_explanation">There are no commands registered</string>
<string name="addcommand_explanation2">You can add new commands in the KDE Connect System Settings</string>
......
......@@ -37,7 +37,7 @@ public class KeyListenerView extends View {
private String deviceId;
private static SparseIntArray SpecialKeysMap = new SparseIntArray();
public static SparseIntArray SpecialKeysMap = new SparseIntArray();
static {
int i = 0;
......
......@@ -35,6 +35,7 @@ import org.kde.kdeconnect.Plugins.MprisPlugin.MprisPlugin;
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 @@ public class PluginFactory {
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);
......
/*
* Copyright 2014 Ahmed I. Khalil <ahmedibrahimkhali@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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();
}
}
};
}
}
/*
* Copyright 2014 Ahmed I. Khalil <ahmedibrahimkhali@gmail.com>
*
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment