mprisDevices = new HashSet<>();
private Context context;
private MediaSessionCompat mediaSession;
//Callback for mpris plugin updates
private Handler mediaNotificationHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
updateMediaNotification();
}
};
//Callback for control via the media session API
private MediaSessionCompat.Callback mediaSessionCallback = new MediaSessionCompat.Callback() {
@Override
public void onPlay() {
notificationPlayer.play();
}
@Override
public void onPause() {
notificationPlayer.pause();
}
@Override
public void onSkipToNext() {
notificationPlayer.next();
}
@Override
public void onSkipToPrevious() {
notificationPlayer.previous();
}
@Override
public void onStop() {
notificationPlayer.stop();
}
};
/**
* Called by the mpris plugin when it wants media control notifications for its device
- *
+ *
* Can be called multiple times, once for each device
+ *
* @param _context The context
- * @param mpris The mpris plugin
- * @param device The device id
+ * @param mpris The mpris plugin
+ * @param device The device id
*/
public void onCreate(Context _context, MprisPlugin mpris, String device) {
if (mprisDevices.isEmpty()) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(_context);
prefs.registerOnSharedPreferenceChangeListener(this);
}
context = _context;
mprisDevices.add(device);
mpris.setPlayerListUpdatedHandler("media_notification", mediaNotificationHandler);
mpris.setPlayerStatusUpdatedHandler("media_notification", mediaNotificationHandler);
updateMediaNotification();
}
/**
* Called when a device disconnects/does not want notifications anymore
- *
+ *
* Can be called multiple times, once for each device
- * @param mpris The mpris plugin
+ *
+ * @param mpris The mpris plugin
* @param device The device id
*/
public void onDestroy(MprisPlugin mpris, String device) {
mprisDevices.remove(device);
mpris.removePlayerStatusUpdatedHandler("media_notification");
mpris.removePlayerListUpdatedHandler("media_notification");
updateMediaNotification();
if (mprisDevices.isEmpty()) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
prefs.unregisterOnSharedPreferenceChangeListener(this);
}
}
/**
* Updates which device+player we're going to use in the notification
- *
+ *
* Prefers playing devices/mpris players, but tries to keep displaying the same
* player and device, while possible.
+ *
* @param service The background service
*/
private void updateCurrentPlayer(BackgroundService service) {
Device device = null;
MprisPlugin.MprisPlayer playing = null;
//First try the previously displayed player
if (notificationDevice != null && mprisDevices.contains(notificationDevice) && notificationPlayer != null) {
device = service.getDevice(notificationDevice);
}
MprisPlugin mpris = null;
if (device != null) {
mpris = device.getPlugin(MprisPlugin.class);
}
if (mpris != null) {
playing = mpris.getPlayerStatus(notificationPlayer.getPlayer());
}
//If nonexistant or not playing, try a different player for the same device
if ((playing == null || !playing.isPlaying()) && mpris != null) {
MprisPlugin.MprisPlayer playingPlayer = mpris.getPlayingPlayer();
//Only replace the previously found player if we really found one
if (playingPlayer != null) {
playing = playingPlayer;
}
}
//If nonexistant or not playing, try a different player for another device
if (playing == null || !playing.isPlaying()) {
for (Device otherDevice : service.getDevices().values()) {
//First, check if we actually display notification for this device
if (!mprisDevices.contains(otherDevice.getDeviceId())) continue;
mpris = otherDevice.getPlugin(MprisPlugin.class);
if (mpris == null) continue;
MprisPlugin.MprisPlayer playingPlayer = mpris.getPlayingPlayer();
//Only replace the previously found player if we really found one
if (playingPlayer != null) {
playing = playingPlayer;
device = otherDevice;
break;
}
}
}
//Update the last-displayed device and player
notificationDevice = device == null ? null : device.getDeviceId();
notificationPlayer = playing;
}
/**
* Update the media control notification
*/
private void updateMediaNotification() {
BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
- //If the user disabled the media notification, do not show it
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- if (!prefs.getBoolean(context.getString(R.string.mpris_notification_key), true)) {
- closeMediaNotification();
- return;
- }
+ //If the user disabled the media notification, do not show it
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+ if (!prefs.getBoolean(context.getString(R.string.mpris_notification_key), true)) {
+ closeMediaNotification();
+ return;
+ }
- //Make sure our information is up-to-date
- updateCurrentPlayer(service);
+ //Make sure our information is up-to-date
+ updateCurrentPlayer(service);
- //If the player disappeared (and no other playing one found), just remove the notification
- if (notificationPlayer == null) {
- closeMediaNotification();
- return;
- }
+ //If the player disappeared (and no other playing one found), just remove the notification
+ if (notificationPlayer == null) {
+ closeMediaNotification();
+ return;
+ }
- //Update the metadata and playback status
- if (mediaSession == null) {
- mediaSession = new MediaSessionCompat(context, MPRIS_MEDIA_SESSION_TAG);
- mediaSession.setCallback(mediaSessionCallback);
- mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
- }
- MediaMetadataCompat.Builder metadata = new MediaMetadataCompat.Builder();
+ //Update the metadata and playback status
+ if (mediaSession == null) {
+ mediaSession = new MediaSessionCompat(context, MPRIS_MEDIA_SESSION_TAG);
+ mediaSession.setCallback(mediaSessionCallback);
+ mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS | MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
+ }
+ MediaMetadataCompat.Builder metadata = new MediaMetadataCompat.Builder();
- //Fallback because older KDE connect versions do not support getTitle()
- if (!notificationPlayer.getTitle().isEmpty()) {
- metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, notificationPlayer.getTitle());
- } else {
- metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, notificationPlayer.getCurrentSong());
- }
- if (!notificationPlayer.getArtist().isEmpty()) {
- metadata.putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, notificationPlayer.getArtist());
- }
- if (!notificationPlayer.getAlbum().isEmpty()) {
- metadata.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, notificationPlayer.getAlbum());
- }
- if (notificationPlayer.getLength() > 0) {
- metadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, notificationPlayer.getLength());
- }
+ //Fallback because older KDE connect versions do not support getTitle()
+ if (!notificationPlayer.getTitle().isEmpty()) {
+ metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, notificationPlayer.getTitle());
+ } else {
+ metadata.putString(MediaMetadataCompat.METADATA_KEY_TITLE, notificationPlayer.getCurrentSong());
+ }
+ if (!notificationPlayer.getArtist().isEmpty()) {
+ metadata.putString(MediaMetadataCompat.METADATA_KEY_AUTHOR, notificationPlayer.getArtist());
+ }
+ if (!notificationPlayer.getAlbum().isEmpty()) {
+ metadata.putString(MediaMetadataCompat.METADATA_KEY_ALBUM, notificationPlayer.getAlbum());
+ }
+ if (notificationPlayer.getLength() > 0) {
+ metadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, notificationPlayer.getLength());
+ }
- mediaSession.setMetadata(metadata.build());
- PlaybackStateCompat.Builder playbackState = new PlaybackStateCompat.Builder();
+ mediaSession.setMetadata(metadata.build());
+ PlaybackStateCompat.Builder playbackState = new PlaybackStateCompat.Builder();
- if (notificationPlayer.isPlaying()) {
- playbackState.setState(PlaybackStateCompat.STATE_PLAYING, notificationPlayer.getPosition(), 1.0f);
- } else {
- playbackState.setState(PlaybackStateCompat.STATE_PAUSED, notificationPlayer.getPosition(), 0.0f);
- }
+ if (notificationPlayer.isPlaying()) {
+ playbackState.setState(PlaybackStateCompat.STATE_PLAYING, notificationPlayer.getPosition(), 1.0f);
+ } else {
+ playbackState.setState(PlaybackStateCompat.STATE_PAUSED, notificationPlayer.getPosition(), 0.0f);
+ }
- //Create all actions (previous/play/pause/next)
- Intent iPlay = new Intent(service, MprisMediaNotificationReceiver.class);
- iPlay.setAction(MprisMediaNotificationReceiver.ACTION_PLAY);
- iPlay.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
- iPlay.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer());
- PendingIntent piPlay = PendingIntent.getBroadcast(service, 0, iPlay, PendingIntent.FLAG_UPDATE_CURRENT);
- NotificationCompat.Action.Builder aPlay = new NotificationCompat.Action.Builder(
- R.drawable.ic_play_white, service.getString(R.string.mpris_play), piPlay);
-
- Intent iPause = new Intent(service, MprisMediaNotificationReceiver.class);
- iPause.setAction(MprisMediaNotificationReceiver.ACTION_PAUSE);
- iPause.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
- iPause.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer());
- PendingIntent piPause = PendingIntent.getBroadcast(service, 0, iPause, PendingIntent.FLAG_UPDATE_CURRENT);
- NotificationCompat.Action.Builder aPause = new NotificationCompat.Action.Builder(
- R.drawable.ic_pause_white, service.getString(R.string.mpris_pause), piPause);
-
- Intent iPrevious = new Intent(service, MprisMediaNotificationReceiver.class);
- iPrevious.setAction(MprisMediaNotificationReceiver.ACTION_PREVIOUS);
- iPrevious.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
- iPrevious.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer());
- PendingIntent piPrevious = PendingIntent.getBroadcast(service, 0, iPrevious, PendingIntent.FLAG_UPDATE_CURRENT);
- NotificationCompat.Action.Builder aPrevious = new NotificationCompat.Action.Builder(
- R.drawable.ic_previous_white, service.getString(R.string.mpris_previous), piPrevious);
-
- Intent iNext = new Intent(service, MprisMediaNotificationReceiver.class);
- iNext.setAction(MprisMediaNotificationReceiver.ACTION_NEXT);
- iNext.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
- iNext.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer());
- PendingIntent piNext = PendingIntent.getBroadcast(service, 0, iNext, PendingIntent.FLAG_UPDATE_CURRENT);
- NotificationCompat.Action.Builder aNext = new NotificationCompat.Action.Builder(
- R.drawable.ic_next_white, service.getString(R.string.mpris_next), piNext);
-
- Intent iOpenActivity = new Intent(service, MprisActivity.class);
- iOpenActivity.putExtra("deviceId", notificationDevice);
- iOpenActivity.putExtra("player", notificationPlayer.getPlayer());
- PendingIntent piOpenActivity = PendingIntent.getActivity(service, 0, iOpenActivity, PendingIntent.FLAG_UPDATE_CURRENT);
-
- //Create the notification
- final NotificationCompat.Builder notification = new NotificationCompat.Builder(service);
- notification
- .setAutoCancel(false)
- .setContentIntent(piOpenActivity)
- .setSmallIcon(R.drawable.ic_play_white)
- .setShowWhen(false)
- .setColor(service.getResources().getColor(R.color.primary));
-
- if (!notificationPlayer.getTitle().isEmpty()) {
- notification.setContentTitle(notificationPlayer.getTitle());
- } else {
- notification.setContentTitle(notificationPlayer.getCurrentSong());
- }
- //Only set the notification body text if we have an author and/or album
- if (!notificationPlayer.getArtist().isEmpty() && !notificationPlayer.getAlbum().isEmpty()) {
- notification.setContentText(notificationPlayer.getArtist() + " - " + notificationPlayer.getAlbum() + " (" + notificationPlayer.getPlayer() + ")");
- } else if (!notificationPlayer.getArtist().isEmpty()) {
- notification.setContentText(notificationPlayer.getArtist() + " (" + notificationPlayer.getPlayer() + ")");
- } else if (!notificationPlayer.getAlbum().isEmpty()) {
- notification.setContentText(notificationPlayer.getAlbum() + " (" + notificationPlayer.getPlayer() + ")");
- } else {
- notification.setContentText(notificationPlayer.getPlayer());
- }
+ //Create all actions (previous/play/pause/next)
+ Intent iPlay = new Intent(service, MprisMediaNotificationReceiver.class);
+ iPlay.setAction(MprisMediaNotificationReceiver.ACTION_PLAY);
+ iPlay.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
+ iPlay.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer());
+ PendingIntent piPlay = PendingIntent.getBroadcast(service, 0, iPlay, PendingIntent.FLAG_UPDATE_CURRENT);
+ NotificationCompat.Action.Builder aPlay = new NotificationCompat.Action.Builder(
+ R.drawable.ic_play_white, service.getString(R.string.mpris_play), piPlay);
+
+ Intent iPause = new Intent(service, MprisMediaNotificationReceiver.class);
+ iPause.setAction(MprisMediaNotificationReceiver.ACTION_PAUSE);
+ iPause.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
+ iPause.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer());
+ PendingIntent piPause = PendingIntent.getBroadcast(service, 0, iPause, PendingIntent.FLAG_UPDATE_CURRENT);
+ NotificationCompat.Action.Builder aPause = new NotificationCompat.Action.Builder(
+ R.drawable.ic_pause_white, service.getString(R.string.mpris_pause), piPause);
+
+ Intent iPrevious = new Intent(service, MprisMediaNotificationReceiver.class);
+ iPrevious.setAction(MprisMediaNotificationReceiver.ACTION_PREVIOUS);
+ iPrevious.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
+ iPrevious.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer());
+ PendingIntent piPrevious = PendingIntent.getBroadcast(service, 0, iPrevious, PendingIntent.FLAG_UPDATE_CURRENT);
+ NotificationCompat.Action.Builder aPrevious = new NotificationCompat.Action.Builder(
+ R.drawable.ic_previous_white, service.getString(R.string.mpris_previous), piPrevious);
+
+ Intent iNext = new Intent(service, MprisMediaNotificationReceiver.class);
+ iNext.setAction(MprisMediaNotificationReceiver.ACTION_NEXT);
+ iNext.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
+ iNext.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer());
+ PendingIntent piNext = PendingIntent.getBroadcast(service, 0, iNext, PendingIntent.FLAG_UPDATE_CURRENT);
+ NotificationCompat.Action.Builder aNext = new NotificationCompat.Action.Builder(
+ R.drawable.ic_next_white, service.getString(R.string.mpris_next), piNext);
+
+ Intent iOpenActivity = new Intent(service, MprisActivity.class);
+ iOpenActivity.putExtra("deviceId", notificationDevice);
+ iOpenActivity.putExtra("player", notificationPlayer.getPlayer());
+ PendingIntent piOpenActivity = PendingIntent.getActivity(service, 0, iOpenActivity, PendingIntent.FLAG_UPDATE_CURRENT);
+
+ //Create the notification
+ final NotificationCompat.Builder notification = new NotificationCompat.Builder(service);
+ notification
+ .setAutoCancel(false)
+ .setContentIntent(piOpenActivity)
+ .setSmallIcon(R.drawable.ic_play_white)
+ .setShowWhen(false)
+ .setColor(service.getResources().getColor(R.color.primary));
+
+ if (!notificationPlayer.getTitle().isEmpty()) {
+ notification.setContentTitle(notificationPlayer.getTitle());
+ } else {
+ notification.setContentTitle(notificationPlayer.getCurrentSong());
+ }
+ //Only set the notification body text if we have an author and/or album
+ if (!notificationPlayer.getArtist().isEmpty() && !notificationPlayer.getAlbum().isEmpty()) {
+ notification.setContentText(notificationPlayer.getArtist() + " - " + notificationPlayer.getAlbum() + " (" + notificationPlayer.getPlayer() + ")");
+ } else if (!notificationPlayer.getArtist().isEmpty()) {
+ notification.setContentText(notificationPlayer.getArtist() + " (" + notificationPlayer.getPlayer() + ")");
+ } else if (!notificationPlayer.getAlbum().isEmpty()) {
+ notification.setContentText(notificationPlayer.getAlbum() + " (" + notificationPlayer.getPlayer() + ")");
+ } else {
+ notification.setContentText(notificationPlayer.getPlayer());
+ }
- if (!notificationPlayer.isPlaying()) {
- Intent iCloseNotification = new Intent(service, MprisMediaNotificationReceiver.class);
- iCloseNotification.setAction(MprisMediaNotificationReceiver.ACTION_CLOSE_NOTIFICATION);
- iCloseNotification.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
- iCloseNotification.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer());
- PendingIntent piCloseNotification = PendingIntent.getActivity(service, 0, iCloseNotification, PendingIntent.FLAG_UPDATE_CURRENT);
- notification.setDeleteIntent(piCloseNotification);
- }
+ if (!notificationPlayer.isPlaying()) {
+ Intent iCloseNotification = new Intent(service, MprisMediaNotificationReceiver.class);
+ iCloseNotification.setAction(MprisMediaNotificationReceiver.ACTION_CLOSE_NOTIFICATION);
+ iCloseNotification.putExtra(MprisMediaNotificationReceiver.EXTRA_DEVICE_ID, notificationDevice);
+ iCloseNotification.putExtra(MprisMediaNotificationReceiver.EXTRA_MPRIS_PLAYER, notificationPlayer.getPlayer());
+ PendingIntent piCloseNotification = PendingIntent.getActivity(service, 0, iCloseNotification, PendingIntent.FLAG_UPDATE_CURRENT);
+ notification.setDeleteIntent(piCloseNotification);
+ }
- //Add media control actions
- int numActions = 0;
- long playbackActions = 0;
- if (notificationPlayer.isGoPreviousAllowed()) {
- notification.addAction(aPrevious.build());
- playbackActions |= PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
- ++numActions;
- }
- if (notificationPlayer.isPlaying() && notificationPlayer.isPauseAllowed()) {
- notification.addAction(aPause.build());
- playbackActions |= PlaybackStateCompat.ACTION_PAUSE;
- ++numActions;
- }
- if (!notificationPlayer.isPlaying() && notificationPlayer.isPlayAllowed()) {
- notification.addAction(aPlay.build());
- playbackActions |= PlaybackStateCompat.ACTION_PLAY;
- ++numActions;
- }
- if (notificationPlayer.isGoNextAllowed()) {
- notification.addAction(aNext.build());
- playbackActions |= PlaybackStateCompat.ACTION_SKIP_TO_NEXT;
- ++numActions;
- }
- playbackState.setActions(playbackActions);
- mediaSession.setPlaybackState(playbackState.build());
-
- //Only allow deletion if no music is notificationPlayer
- if (notificationPlayer.isPlaying()) {
- notification.setOngoing(true);
- } else {
- notification.setOngoing(false);
- }
+ //Add media control actions
+ int numActions = 0;
+ long playbackActions = 0;
+ if (notificationPlayer.isGoPreviousAllowed()) {
+ notification.addAction(aPrevious.build());
+ playbackActions |= PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS;
+ ++numActions;
+ }
+ if (notificationPlayer.isPlaying() && notificationPlayer.isPauseAllowed()) {
+ notification.addAction(aPause.build());
+ playbackActions |= PlaybackStateCompat.ACTION_PAUSE;
+ ++numActions;
+ }
+ if (!notificationPlayer.isPlaying() && notificationPlayer.isPlayAllowed()) {
+ notification.addAction(aPlay.build());
+ playbackActions |= PlaybackStateCompat.ACTION_PLAY;
+ ++numActions;
+ }
+ if (notificationPlayer.isGoNextAllowed()) {
+ notification.addAction(aNext.build());
+ playbackActions |= PlaybackStateCompat.ACTION_SKIP_TO_NEXT;
+ ++numActions;
+ }
+ playbackState.setActions(playbackActions);
+ mediaSession.setPlaybackState(playbackState.build());
+
+ //Only allow deletion if no music is notificationPlayer
+ if (notificationPlayer.isPlaying()) {
+ notification.setOngoing(true);
+ } else {
+ notification.setOngoing(false);
+ }
- //Use the MediaStyle notification, so it feels like other media players. That also allows adding actions
- NotificationCompat.MediaStyle mediaStyle = new NotificationCompat.MediaStyle();
- if (numActions == 1) {
- mediaStyle.setShowActionsInCompactView(0);
- } else if (numActions == 2) {
- mediaStyle.setShowActionsInCompactView(0, 1);
- } else if (numActions >= 3) {
- mediaStyle.setShowActionsInCompactView(0, 1, 2);
- }
- mediaStyle.setMediaSession(mediaSession.getSessionToken());
- notification.setStyle(mediaStyle);
+ //Use the MediaStyle notification, so it feels like other media players. That also allows adding actions
+ NotificationCompat.MediaStyle mediaStyle = new NotificationCompat.MediaStyle();
+ if (numActions == 1) {
+ mediaStyle.setShowActionsInCompactView(0);
+ } else if (numActions == 2) {
+ mediaStyle.setShowActionsInCompactView(0, 1);
+ } else if (numActions >= 3) {
+ mediaStyle.setShowActionsInCompactView(0, 1, 2);
+ }
+ mediaStyle.setMediaSession(mediaSession.getSessionToken());
+ notification.setStyle(mediaStyle);
- //Display the notification
- mediaSession.setActive(true);
- final NotificationManager nm = (NotificationManager) service.getSystemService(Context.NOTIFICATION_SERVICE);
- nm.notify(MPRIS_MEDIA_NOTIFICATION_ID, notification.build());
+ //Display the notification
+ mediaSession.setActive(true);
+ final NotificationManager nm = (NotificationManager) service.getSystemService(Context.NOTIFICATION_SERVICE);
+ nm.notify(MPRIS_MEDIA_NOTIFICATION_ID, notification.build());
}
});
}
public void closeMediaNotification() {
//Remove the notification
NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
nm.cancel(MPRIS_MEDIA_NOTIFICATION_ID);
//Clear the current player and media session
notificationPlayer = null;
if (mediaSession != null) {
mediaSession.release();
mediaSession = null;
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
updateMediaNotification();
}
}
diff --git a/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisPlugin.java b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisPlugin.java
index 7a728fc7..84608511 100644
--- a/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisPlugin.java
+++ b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisPlugin.java
@@ -1,460 +1,466 @@
/*
* Copyright 2014 Albert Vaca Cintora
*
* 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.MprisPlugin;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.support.v4.content.ContextCompat;
import android.util.Log;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect_tp.R;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
public class MprisPlugin extends Plugin {
public class MprisPlayer {
private String player = "";
private boolean playing = false;
private String currentSong = "";
private String title = "";
private String artist = "";
private String album = "";
private String albumArtUrl = "";
private int volume = 50;
private long length = -1;
private long lastPosition = 0;
private long lastPositionTime;
private boolean playAllowed = true;
private boolean pauseAllowed = true;
private boolean goNextAllowed = true;
private boolean goPreviousAllowed = true;
private boolean seekAllowed = true;
public MprisPlayer() {
lastPositionTime = System.currentTimeMillis();
}
public String getCurrentSong() {
return currentSong;
}
public String getTitle() {
return title;
}
public String getArtist() {
return artist;
}
public String getAlbum() {
return album;
}
public String getPlayer() {
return player;
}
private boolean isSpotify() {
return getPlayer().toLowerCase().equals("spotify");
}
public int getVolume() {
return volume;
}
- public long getLength(){ return length; }
+ public long getLength() {
+ return length;
+ }
public boolean isPlaying() {
return playing;
}
public boolean isPlayAllowed() {
return playAllowed;
}
public boolean isPauseAllowed() {
return pauseAllowed;
}
public boolean isGoNextAllowed() {
return goNextAllowed;
}
public boolean isGoPreviousAllowed() {
return goPreviousAllowed;
}
public boolean isSeekAllowed() {
return seekAllowed && getLength() >= 0 && getPosition() >= 0 && !isSpotify();
}
public boolean hasAlbumArt() {
return !albumArtUrl.isEmpty();
}
/**
* Returns the album art (if available). Note that this can return null even if hasAlbumArt() returns true.
+ *
* @return The album art, or null if not available
*/
public Bitmap getAlbumArt() {
return AlbumArtCache.getAlbumArt(albumArtUrl);
}
public boolean isSetVolumeAllowed() {
return !isSpotify();
}
- public long getPosition(){
- if(playing) {
+ public long getPosition() {
+ if (playing) {
return lastPosition + (System.currentTimeMillis() - lastPositionTime);
} else {
return lastPosition;
}
}
public void playPause() {
if (isPauseAllowed() || isPlayAllowed()) {
MprisPlugin.this.sendCommand(getPlayer(), "action", "PlayPause");
}
}
public void play() {
if (isPlayAllowed()) {
MprisPlugin.this.sendCommand(getPlayer(), "action", "Play");
}
}
public void pause() {
if (isPauseAllowed()) {
MprisPlugin.this.sendCommand(getPlayer(), "action", "Pause");
}
}
public void stop() {
MprisPlugin.this.sendCommand(getPlayer(), "action", "Stop");
}
public void previous() {
if (isGoPreviousAllowed()) {
MprisPlugin.this.sendCommand(getPlayer(), "action", "Previous");
}
}
public void next() {
if (isGoNextAllowed()) {
MprisPlugin.this.sendCommand(getPlayer(), "action", "Next");
}
}
public void setVolume(int volume) {
if (isSetVolumeAllowed()) {
MprisPlugin.this.sendCommand(getPlayer(), "setVolume", volume);
}
}
public void setPosition(int position) {
if (isSeekAllowed()) {
MprisPlugin.this.sendCommand(getPlayer(), "SetPosition", position);
lastPosition = position;
lastPositionTime = System.currentTimeMillis();
}
}
public void seek(int offset) {
if (isSeekAllowed()) {
MprisPlugin.this.sendCommand(getPlayer(), "Seek", offset);
}
}
}
public final static String PACKAGE_TYPE_MPRIS = "kdeconnect.mpris";
public final static String PACKAGE_TYPE_MPRIS_REQUEST = "kdeconnect.mpris.request";
private HashMap players = new HashMap<>();
- private HashMap playerStatusUpdated = new HashMap<>();
+ private HashMap playerStatusUpdated = new HashMap<>();
- private HashMap playerListUpdated = new HashMap<>();
+ private HashMap playerListUpdated = new HashMap<>();
@Override
public String getDisplayName() {
return context.getResources().getString(R.string.pref_plugin_mpris);
}
@Override
public String getDescription() {
return context.getResources().getString(R.string.pref_plugin_mpris_desc);
}
@Override
public Drawable getIcon() {
return ContextCompat.getDrawable(context, R.drawable.mpris_plugin_action);
}
@Override
public boolean hasSettings() {
return true;
}
@Override
public boolean onCreate() {
requestPlayerList();
MprisMediaSession.getInstance().onCreate(context.getApplicationContext(), this, device.getDeviceId());
//Always request the player list so the data is up-to-date
requestPlayerList();
AlbumArtCache.initializeDiskCache(context);
AlbumArtCache.registerPlugin(this);
return true;
}
@Override
public void onDestroy() {
players.clear();
AlbumArtCache.deregisterPlugin(this);
MprisMediaSession.getInstance().onDestroy(this, device.getDeviceId());
}
private void sendCommand(String player, String method, String value) {
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MPRIS_REQUEST);
np.set("player", player);
np.set(method, value);
device.sendPackage(np);
}
private void sendCommand(String player, String method, int value) {
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MPRIS_REQUEST);
np.set("player", player);
np.set(method, value);
device.sendPackage(np);
}
@Override
public boolean onPackageReceived(NetworkPackage np) {
if (np.has("player")) {
MprisPlayer playerStatus = players.get(np.getString("player"));
if (playerStatus != null) {
playerStatus.currentSong = np.getString("nowPlaying", playerStatus.currentSong);
//Note: title, artist and album will not be available for all desktop clients
playerStatus.title = np.getString("title", playerStatus.title);
playerStatus.artist = np.getString("artist", playerStatus.artist);
playerStatus.album = np.getString("album", playerStatus.album);
playerStatus.volume = np.getInt("volume", playerStatus.volume);
playerStatus.length = np.getLong("length", playerStatus.length);
- if(np.has("pos")){
+ if (np.has("pos")) {
playerStatus.lastPosition = np.getLong("pos", playerStatus.lastPosition);
playerStatus.lastPositionTime = System.currentTimeMillis();
}
playerStatus.playing = np.getBoolean("isPlaying", playerStatus.playing);
playerStatus.playAllowed = np.getBoolean("canPlay", playerStatus.playAllowed);
playerStatus.pauseAllowed = np.getBoolean("canPause", playerStatus.pauseAllowed);
playerStatus.goNextAllowed = np.getBoolean("canGoNext", playerStatus.goNextAllowed);
playerStatus.goPreviousAllowed = np.getBoolean("canGoPrevious", playerStatus.goPreviousAllowed);
playerStatus.seekAllowed = np.getBoolean("canSeek", playerStatus.seekAllowed);
String newAlbumArtUrlstring = np.getString("albumArtUrl", playerStatus.albumArtUrl);
try {
//Turn the url into canonical form (and check its validity)
URL newAlbumArtUrl = new URL(newAlbumArtUrlstring);
playerStatus.albumArtUrl = newAlbumArtUrl.toString();
- } catch (MalformedURLException ignored) {}
+ } catch (MalformedURLException ignored) {
+ }
+
for (String key : playerStatusUpdated.keySet()) {
try {
playerStatusUpdated.get(key).dispatchMessage(new Message());
- } catch(Exception e) {
+ } catch (Exception e) {
e.printStackTrace();
- Log.e("MprisControl","Exception");
+ Log.e("MprisControl", "Exception");
playerStatusUpdated.remove(key);
}
}
}
}
List newPlayerList = np.getStringList("playerList");
if (newPlayerList != null) {
boolean equals = true;
for (String newPlayer : newPlayerList) {
if (!players.containsKey(newPlayer)) {
equals = false;
MprisPlayer player = new MprisPlayer();
player.player = newPlayer;
players.put(newPlayer, player);
//Immediately ask for the data of this player
requestPlayerStatus(newPlayer);
}
}
Iterator> iter = players.entrySet().iterator();
while (iter.hasNext()) {
String oldPlayer = iter.next().getKey();
boolean found = false;
for (String newPlayer : newPlayerList) {
if (newPlayer.equals(oldPlayer)) {
found = true;
break;
}
}
if (!found) {
iter.remove();
equals = false;
}
}
if (!equals) {
for (String key : playerListUpdated.keySet()) {
try {
playerListUpdated.get(key).dispatchMessage(new Message());
- } catch(Exception e) {
+ } catch (Exception e) {
e.printStackTrace();
- Log.e("MprisControl","Exception");
+ Log.e("MprisControl", "Exception");
playerListUpdated.remove(key);
}
}
}
}
return true;
}
@Override
public String[] getSupportedPackageTypes() {
- return new String[] {PACKAGE_TYPE_MPRIS};
+ return new String[]{PACKAGE_TYPE_MPRIS};
}
@Override
public String[] getOutgoingPackageTypes() {
- return new String[] {PACKAGE_TYPE_MPRIS_REQUEST};
+ return new String[]{PACKAGE_TYPE_MPRIS_REQUEST};
}
public void setPlayerStatusUpdatedHandler(String id, Handler h) {
playerStatusUpdated.put(id, h);
h.dispatchMessage(new Message());
}
public void removePlayerStatusUpdatedHandler(String id) {
playerStatusUpdated.remove(id);
}
public void setPlayerListUpdatedHandler(String id, Handler h) {
- playerListUpdated.put(id,h);
+ playerListUpdated.put(id, h);
h.dispatchMessage(new Message());
}
public void removePlayerListUpdatedHandler(String id) {
playerListUpdated.remove(id);
}
public List getPlayerList() {
List playerlist = new ArrayList<>(players.keySet());
Collections.sort(playerlist);
return playerlist;
}
public MprisPlayer getPlayerStatus(String player) {
return players.get(player);
}
public MprisPlayer getEmptyPlayer() {
return new MprisPlayer();
}
/**
* Returns a playing mpris player, if any exist
+ *
* @return null if no players are playing, a playing player otherwise
*/
public MprisPlayer getPlayingPlayer() {
for (MprisPlayer player : players.values()) {
if (player.isPlaying()) {
return player;
}
}
return null;
}
private void requestPlayerList() {
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MPRIS_REQUEST);
- np.set("requestPlayerList",true);
+ np.set("requestPlayerList", true);
device.sendPackage(np);
}
private void requestPlayerStatus(String player) {
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MPRIS_REQUEST);
np.set("player", player);
- np.set("requestNowPlaying",true);
- np.set("requestVolume",true);
+ np.set("requestNowPlaying", true);
+ np.set("requestVolume", true);
device.sendPackage(np);
}
@Override
public boolean hasMainActivity() {
return true;
}
@Override
public void startMainActivity(Activity parentActivity) {
Intent intent = new Intent(parentActivity, MprisActivity.class);
intent.putExtra("deviceId", device.getDeviceId());
parentActivity.startActivity(intent);
}
@Override
public String getActionName() {
return context.getString(R.string.open_mpris_controls);
}
public void fetchedAlbumArt(String url) {
boolean doEmitUpdate = false;
for (MprisPlayer player : players.values()) {
if (url.equals(player.albumArtUrl)) {
doEmitUpdate = true;
}
}
if (doEmitUpdate) {
for (String key : playerStatusUpdated.keySet()) {
try {
playerStatusUpdated.get(key).dispatchMessage(new Message());
- } catch(Exception e) {
+ } catch (Exception e) {
e.printStackTrace();
- Log.e("MprisControl","Exception");
+ Log.e("MprisControl", "Exception");
playerStatusUpdated.remove(key);
}
}
}
}
}
diff --git a/src/org/kde/kdeconnect/Plugins/PingPlugin/PingPlugin.java b/src/org/kde/kdeconnect/Plugins/PingPlugin/PingPlugin.java
index b52aeb6f..7b650d72 100644
--- a/src/org/kde/kdeconnect/Plugins/PingPlugin/PingPlugin.java
+++ b/src/org/kde/kdeconnect/Plugins/PingPlugin/PingPlugin.java
@@ -1,131 +1,131 @@
/*
* Copyright 2014 Albert Vaca Cintora
*
* 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.PingPlugin;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.util.Log;
import org.kde.kdeconnect.Helpers.NotificationHelper;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.UserInterface.MaterialActivity;
import org.kde.kdeconnect_tp.R;
public class PingPlugin extends Plugin {
public final static String PACKAGE_TYPE_PING = "kdeconnect.ping";
@Override
public String getDisplayName() {
return context.getResources().getString(R.string.pref_plugin_ping);
}
@Override
public String getDescription() {
return context.getResources().getString(R.string.pref_plugin_ping_desc);
}
@Override
public boolean onPackageReceived(NetworkPackage np) {
if (!np.getType().equals(PACKAGE_TYPE_PING)) {
Log.e("PingPlugin", "Ping plugin should not receive packets other than pings!");
return false;
}
//Log.e("PingPackageReceiver", "was a ping!");
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addParentStack(MaterialActivity.class);
stackBuilder.addNextIntent(new Intent(context, MaterialActivity.class));
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(
- 0,
- PendingIntent.FLAG_UPDATE_CURRENT
+ 0,
+ PendingIntent.FLAG_UPDATE_CURRENT
);
int id;
String message;
if (np.has("message")) {
message = np.getString("message");
- id = (int)System.currentTimeMillis();
+ id = (int) System.currentTimeMillis();
} else {
message = "Ping!";
id = 42; //A unique id to create only one notification
}
Notification noti = new NotificationCompat.Builder(context)
.setContentTitle(device.getName())
.setContentText(message)
.setContentIntent(resultPendingIntent)
.setTicker(message)
.setSmallIcon(R.drawable.ic_notification)
.setAutoCancel(true)
.setDefaults(Notification.DEFAULT_ALL)
.build();
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationHelper.notifyCompat(notificationManager, id, noti);
return true;
}
@Override
public String getActionName() {
return context.getString(R.string.send_ping);
}
@Override
public void startMainActivity(Activity activity) {
if (device != null) {
device.sendPackage(new NetworkPackage(PACKAGE_TYPE_PING));
}
}
@Override
public boolean hasMainActivity() {
return true;
}
@Override
public boolean displayInContextMenu() {
return true;
}
@Override
public String[] getSupportedPackageTypes() {
return new String[]{PACKAGE_TYPE_PING};
}
@Override
public String[] getOutgoingPackageTypes() {
return new String[]{PACKAGE_TYPE_PING};
}
}
diff --git a/src/org/kde/kdeconnect/Plugins/Plugin.java b/src/org/kde/kdeconnect/Plugins/Plugin.java
index 4ccc97d1..e1c571b1 100644
--- a/src/org/kde/kdeconnect/Plugins/Plugin.java
+++ b/src/org/kde/kdeconnect/Plugins/Plugin.java
@@ -1,284 +1,289 @@
/*
* Copyright 2014 Albert Vaca Cintora
*
* 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;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.StringRes;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.view.View;
import android.widget.Button;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.UserInterface.PluginSettingsActivity;
import org.kde.kdeconnect.UserInterface.SettingsActivity;
import org.kde.kdeconnect_tp.R;
public abstract class Plugin {
protected Device device;
protected Context context;
protected int permissionExplanation = R.string.permission_explanation;
protected int optionalPermissionExplanation = R.string.optional_permission_explanation;
public final void setContext(Context context, Device device) {
this.device = device;
this.context = context;
}
/**
* To receive the network package from the unpaired device, override
* listensToUnpairedDevices to return true and this method.
*/
public boolean onUnpairedDevicePackageReceived(NetworkPackage np) {
return false;
}
/**
* Returns whether this plugin should be loaded or not, to listen to NetworkPackages
* from the unpaired devices. By default, returns false.
*/
public boolean listensToUnpairedDevices() {
return false;
}
/**
* Return the internal plugin name, that will be used as a
* unique key to distinguish it. Use the class name as key.
*/
public String getPluginKey() {
return getPluginKey(this.getClass());
}
+
public static String getPluginKey(Class extends Plugin> p) {
return p.getSimpleName();
}
/**
* Return the human-readable plugin name. This function can
* access this.context to provide translated text.
*/
public abstract String getDisplayName();
/**
* Return the human-readable description of this plugin. This
* function can access this.context to provide translated text.
*/
public abstract String getDescription();
/**
* Return the action name displayed in the main activity, that
* will call startMainActivity when clicked
*/
public String getActionName() {
return getDisplayName();
}
/**
* Return an icon associated to this plugin. This function can
* access this.context to load the image from resources.
*/
public Drawable getIcon() {
return null;
}
/**
* Return true if this plugin should be enabled on new devices.
* This function can access this.context and perform compatibility
* checks with the Android version, but can not access this.device.
*/
public boolean isEnabledByDefault() {
return true;
}
/**
* Return true if this plugin needs an specific UI settings.
*/
public boolean hasSettings() {
return false;
}
/**
* If hasSettings returns true, this will be called when the user
* wants to access this plugin preferences and should launch some
* kind of interface. The default implementation will launch a
* SettingsActivity with content from "yourplugin"_preferences.xml.
*/
public void startPreferencesActivity(SettingsActivity parentActivity) {
Intent intent = new Intent(parentActivity, PluginSettingsActivity.class);
intent.putExtra("plugin_display_name", getDisplayName());
intent.putExtra("plugin_key", getPluginKey());
parentActivity.startActivity(intent);
}
/**
* Return true if the plugin should display something in the Device main view
*/
public boolean hasMainActivity() {
return false;
}
/**
* Implement here what your plugin should do when clicked
*/
- public void startMainActivity(Activity parentActivity) { }
+ public void startMainActivity(Activity parentActivity) {
+ }
/**
* Return true if the entry for this app should appear in the context menu instead of the main view
*/
public boolean displayInContextMenu() {
return false;
}
/**
* Initialize the listeners and structures in your plugin.
* Should return true if initialization was successful.
*/
public boolean onCreate() {
return true;
}
/**
* Finish any ongoing operations, remove listeners... so
* this object could be garbage collected.
*/
- public void onDestroy() { }
+ public void onDestroy() {
+ }
/**
* Called when a plugin receives a package. By convention we return true
* when we have done something in response to the package or false
* otherwise, even though that value is unused as of now.
*/
- public boolean onPackageReceived(NetworkPackage np) { return false; }
+ public boolean onPackageReceived(NetworkPackage np) {
+ return false;
+ }
/**
* Should return the list of NetworkPackage types that this plugin can handle
*/
public abstract String[] getSupportedPackageTypes();
/**
* Should return the list of NetworkPackage types that this plugin can send
*/
public abstract String[] getOutgoingPackageTypes();
/**
* Creates a button that will be displayed in the user interface
* It can open an activity or perform any other action that the
* plugin would wants to expose to the user. Return null if no
* button should be displayed.
*/
@Deprecated
public Button getInterfaceButton(final Activity activity) {
if (!hasMainActivity()) return null;
Button b = new Button(activity);
b.setText(getActionName());
b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
startMainActivity(activity);
}
});
return b;
}
public String[] getRequiredPermissions() {
return new String[0];
}
public String[] getOptionalPermissions() {
return new String[0];
}
//Permission from Manifest.permission.*
protected boolean isPermissionGranted(String permission) {
int result = ContextCompat.checkSelfPermission(context, permission);
return (result == PackageManager.PERMISSION_GRANTED);
}
protected boolean arePermissionsGranted(String[] permissions) {
- for(String permission: permissions){
- if(!isPermissionGranted(permission)){
+ for (String permission : permissions) {
+ if (!isPermissionGranted(permission)) {
return false;
}
}
return true;
}
protected AlertDialog requestPermissionDialog(Activity activity, String permissions, @StringRes int reason) {
return requestPermissionDialog(activity, new String[]{permissions}, reason);
}
- protected AlertDialog requestPermissionDialog(final Activity activity, final String[] permissions, @StringRes int reason){
+ protected AlertDialog requestPermissionDialog(final Activity activity, final String[] permissions, @StringRes int reason) {
return new AlertDialog.Builder(activity)
.setTitle(getDisplayName())
.setMessage(reason)
.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
ActivityCompat.requestPermissions(activity, permissions, 0);
}
})
- .setNegativeButton(R.string.cancel,new DialogInterface.OnClickListener() {
+ .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
//Do nothing
}
})
.create();
}
/**
* If onCreate returns false, should create a dialog explaining
* the problem (and how to fix it, if possible) to the user.
*/
public AlertDialog getErrorDialog(Activity deviceActivity) {
return null;
}
public AlertDialog getPermissionExplanationDialog(Activity deviceActivity) {
- return requestPermissionDialog(deviceActivity,getRequiredPermissions(), permissionExplanation);
+ return requestPermissionDialog(deviceActivity, getRequiredPermissions(), permissionExplanation);
}
public AlertDialog getOptionalPermissionExplanationDialog(Activity deviceActivity) {
- return requestPermissionDialog(deviceActivity,getOptionalPermissions(), optionalPermissionExplanation);
+ return requestPermissionDialog(deviceActivity, getOptionalPermissions(), optionalPermissionExplanation);
}
- public boolean checkRequiredPermissions(){
+ public boolean checkRequiredPermissions() {
return arePermissionsGranted(getRequiredPermissions());
}
- public boolean checkOptionalPermissions(){
- return arePermissionsGranted(getOptionalPermissions());
+ public boolean checkOptionalPermissions() {
+ return arePermissionsGranted(getOptionalPermissions());
}
public int getMinSdk() {
return Build.VERSION_CODES.BASE;
}
}
diff --git a/src/org/kde/kdeconnect/Plugins/PluginFactory.java b/src/org/kde/kdeconnect/Plugins/PluginFactory.java
index 38247d15..e173d16c 100644
--- a/src/org/kde/kdeconnect/Plugins/PluginFactory.java
+++ b/src/org/kde/kdeconnect/Plugins/PluginFactory.java
@@ -1,222 +1,224 @@
/*
* Copyright 2014 Albert Vaca Cintora
*
* 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;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.util.Log;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Plugins.BatteryPlugin.BatteryPlugin;
import org.kde.kdeconnect.Plugins.ClibpoardPlugin.ClipboardPlugin;
import org.kde.kdeconnect.Plugins.FindMyPhonePlugin.FindMyPhonePlugin;
import org.kde.kdeconnect.Plugins.MousePadPlugin.MousePadPlugin;
import org.kde.kdeconnect.Plugins.MprisPlugin.MprisPlugin;
import org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationsPlugin;
import org.kde.kdeconnect.Plugins.PingPlugin.PingPlugin;
import org.kde.kdeconnect.Plugins.ReceiveNotificationsPlugin.ReceiveNotificationsPlugin;
import org.kde.kdeconnect.Plugins.RemoteKeyboardPlugin.RemoteKeyboardPlugin;
import org.kde.kdeconnect.Plugins.RunCommandPlugin.RunCommandPlugin;
import org.kde.kdeconnect.Plugins.SftpPlugin.SftpPlugin;
import org.kde.kdeconnect.Plugins.SharePlugin.SharePlugin;
import org.kde.kdeconnect.Plugins.TelepathyPlugin.TelepathyPlugin;
import org.kde.kdeconnect.Plugins.TelephonyPlugin.TelephonyPlugin;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
public class PluginFactory {
public static class PluginInfo {
public PluginInfo(String displayName, String description, Drawable icon,
boolean enabledByDefault, boolean hasSettings, boolean listenToUnpaired,
String[] supportedPackageTypes, String[] outgoingPackageTypes) {
this.displayName = displayName;
this.description = description;
this.icon = icon;
this.enabledByDefault = enabledByDefault;
this.hasSettings = hasSettings;
this.listenToUnpaired = listenToUnpaired;
HashSet incoming = new HashSet<>();
if (supportedPackageTypes != null) Collections.addAll(incoming, supportedPackageTypes);
this.supportedPackageTypes = Collections.unmodifiableSet(incoming);
HashSet outgoing = new HashSet<>();
if (outgoingPackageTypes != null) Collections.addAll(outgoing, outgoingPackageTypes);
this.outgoingPackageTypes = Collections.unmodifiableSet(outgoing);
}
public String getDisplayName() {
return displayName;
}
public String getDescription() {
return description;
}
public Drawable getIcon() {
return icon;
}
- public boolean hasSettings() { return hasSettings; }
+ public boolean hasSettings() {
+ return hasSettings;
+ }
public boolean isEnabledByDefault() {
return enabledByDefault;
}
public boolean listenToUnpaired() {
return listenToUnpaired;
}
public Set getOutgoingPackageTypes() {
return outgoingPackageTypes;
}
public Set getSupportedPackageTypes() {
return supportedPackageTypes;
}
private final String displayName;
private final String description;
private final Drawable icon;
private final boolean enabledByDefault;
private final boolean hasSettings;
private final boolean listenToUnpaired;
private final Set supportedPackageTypes;
private final Set outgoingPackageTypes;
}
private static final Map availablePlugins = new TreeMap<>();
private static final Map pluginInfoCache = new TreeMap<>();
static {
PluginFactory.registerPlugin(TelephonyPlugin.class);
PluginFactory.registerPlugin(PingPlugin.class);
PluginFactory.registerPlugin(MprisPlugin.class);
PluginFactory.registerPlugin(ClipboardPlugin.class);
PluginFactory.registerPlugin(BatteryPlugin.class);
PluginFactory.registerPlugin(SftpPlugin.class);
PluginFactory.registerPlugin(NotificationsPlugin.class);
PluginFactory.registerPlugin(ReceiveNotificationsPlugin.class);
PluginFactory.registerPlugin(MousePadPlugin.class);
PluginFactory.registerPlugin(SharePlugin.class);
PluginFactory.registerPlugin(TelepathyPlugin.class);
PluginFactory.registerPlugin(FindMyPhonePlugin.class);
PluginFactory.registerPlugin(RunCommandPlugin.class);
PluginFactory.registerPlugin(RemoteKeyboardPlugin.class);
}
public static PluginInfo getPluginInfo(Context context, String pluginKey) {
PluginInfo info = pluginInfoCache.get(pluginKey); //Is it cached?
if (info != null) {
return info;
}
try {
- Plugin p = ((Plugin)availablePlugins.get(pluginKey).newInstance());
+ Plugin p = ((Plugin) availablePlugins.get(pluginKey).newInstance());
p.setContext(context, null);
info = new PluginInfo(p.getDisplayName(), p.getDescription(), p.getIcon(),
p.isEnabledByDefault(), p.hasSettings(), p.listensToUnpairedDevices(),
p.getSupportedPackageTypes(), p.getOutgoingPackageTypes());
pluginInfoCache.put(pluginKey, info); //Cache it
return info;
- } catch(Exception e) {
- Log.e("PluginFactory","getPluginInfo exception");
+ } catch (Exception e) {
+ Log.e("PluginFactory", "getPluginInfo exception");
e.printStackTrace();
throw new RuntimeException(e);
}
}
public static Set getAvailablePlugins() {
return availablePlugins.keySet();
}
public static Plugin instantiatePluginForDevice(Context context, String pluginKey, Device device) {
Class c = availablePlugins.get(pluginKey);
if (c == null) {
- Log.e("PluginFactory", "Plugin not found: "+pluginKey);
+ Log.e("PluginFactory", "Plugin not found: " + pluginKey);
return null;
}
try {
- Plugin plugin = (Plugin)c.newInstance();
+ Plugin plugin = (Plugin) c.newInstance();
plugin.setContext(context, device);
return plugin;
- } catch(Exception e) {
- Log.e("PluginFactory", "Could not instantiate plugin: "+pluginKey);
+ } catch (Exception e) {
+ Log.e("PluginFactory", "Could not instantiate plugin: " + pluginKey);
e.printStackTrace();
return null;
}
}
public static void registerPlugin(Class extends Plugin> pluginClass) {
try {
String pluginKey = Plugin.getPluginKey(pluginClass);
availablePlugins.put(pluginKey, pluginClass);
- } catch(Exception e) {
- Log.e("PluginFactory","addPlugin exception");
+ } catch (Exception e) {
+ Log.e("PluginFactory", "addPlugin exception");
e.printStackTrace();
}
}
public static Set getIncomingCapabilities(Context context) {
HashSet capabilities = new HashSet<>();
for (String pluginId : availablePlugins.keySet()) {
PluginInfo plugin = getPluginInfo(context, pluginId);
capabilities.addAll(plugin.getSupportedPackageTypes());
}
return capabilities;
}
public static Set getOutgoingCapabilities(Context context) {
HashSet capabilities = new HashSet<>();
for (String pluginId : availablePlugins.keySet()) {
PluginInfo plugin = getPluginInfo(context, pluginId);
capabilities.addAll(plugin.getOutgoingPackageTypes());
}
return capabilities;
}
public static Set pluginsForCapabilities(Context context, Set incoming, Set outgoing) {
HashSet plugins = new HashSet<>();
for (String pluginId : availablePlugins.keySet()) {
PluginInfo plugin = getPluginInfo(context, pluginId);
//Check incoming against outgoing
if (Collections.disjoint(outgoing, plugin.getSupportedPackageTypes())
- && Collections.disjoint(incoming, plugin.getOutgoingPackageTypes())) {
+ && Collections.disjoint(incoming, plugin.getOutgoingPackageTypes())) {
Log.i("PluginFactory", "Won't load " + pluginId + " because of unmatched capabilities");
continue; //No capabilities in common, do not load this plugin
}
plugins.add(pluginId);
}
return plugins;
}
}
diff --git a/src/org/kde/kdeconnect/Plugins/ReceiveNotificationsPlugin/ReceiveNotificationsPlugin.java b/src/org/kde/kdeconnect/Plugins/ReceiveNotificationsPlugin/ReceiveNotificationsPlugin.java
index d22da5f1..14a006b1 100644
--- a/src/org/kde/kdeconnect/Plugins/ReceiveNotificationsPlugin/ReceiveNotificationsPlugin.java
+++ b/src/org/kde/kdeconnect/Plugins/ReceiveNotificationsPlugin/ReceiveNotificationsPlugin.java
@@ -1,135 +1,138 @@
/*
* Copyright 2014 Albert Vaca Cintora
*
* 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.ReceiveNotificationsPlugin;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.util.Log;
import org.kde.kdeconnect.Helpers.NotificationHelper;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.UserInterface.MaterialActivity;
import org.kde.kdeconnect_tp.R;
import java.io.InputStream;
public class ReceiveNotificationsPlugin extends Plugin {
public final static String PACKAGE_TYPE_NOTIFICATION = "kdeconnect.notification";
public final static String PACKAGE_TYPE_NOTIFICATION_REQUEST = "kdeconnect.notification.request";
@Override
public String getDisplayName() {
return context.getResources().getString(R.string.pref_plugin_receive_notifications);
}
@Override
public String getDescription() {
return context.getResources().getString(R.string.pref_plugin_receive_notifications_desc);
}
@Override
public boolean isEnabledByDefault() {
return false;
}
@Override
public boolean onCreate() {
// request all existing notifications
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_NOTIFICATION_REQUEST);
np.set("request", true);
device.sendPackage(np);
return true;
}
@Override
public boolean onPackageReceived(final NetworkPackage np) {
if (!np.has("ticker") || !np.has("appName") || !np.has("id")) {
Log.e("NotificationsPlugin", "Received notification package lacks properties");
} else {
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addParentStack(MaterialActivity.class);
stackBuilder.addNextIntent(new Intent(context, MaterialActivity.class));
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT
);
Bitmap largeIcon = null;
if (np.hasPayload()) {
int width = 64; // default icon dimensions
int height = 64;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
width = context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_width);
height = context.getResources().getDimensionPixelSize(android.R.dimen.notification_large_icon_height);
}
final InputStream input = np.getPayload();
largeIcon = BitmapFactory.decodeStream(np.getPayload());
- try { input.close(); } catch (Exception e) { }
+ try {
+ input.close();
+ } catch (Exception e) {
+ }
if (largeIcon != null) {
//Log.i("NotificationsPlugin", "hasPayload: size=" + largeIcon.getWidth() + "/" + largeIcon.getHeight() + " opti=" + width + "/" + height);
if (largeIcon.getWidth() > width || largeIcon.getHeight() > height) {
// older API levels don't scale notification icons automatically, therefore:
largeIcon = Bitmap.createScaledBitmap(largeIcon, width, height, false);
}
}
}
Notification noti = new NotificationCompat.Builder(context)
.setContentTitle(np.getString("appName"))
.setContentText(np.getString("ticker"))
.setContentIntent(resultPendingIntent)
.setTicker(np.getString("ticker"))
.setSmallIcon(R.drawable.ic_notification)
.setLargeIcon(largeIcon)
.setAutoCancel(true)
.setLocalOnly(true) // to avoid bouncing the notification back to other kdeconnect nodes
.setDefaults(Notification.DEFAULT_ALL)
.build();
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationHelper.notifyCompat(notificationManager, "kdeconnectId:" + np.getString("id", "0"), np.getInt("id", 0), noti);
}
return true;
}
@Override
public String[] getSupportedPackageTypes() {
return new String[]{PACKAGE_TYPE_NOTIFICATION};
}
@Override
public String[] getOutgoingPackageTypes() {
return new String[]{PACKAGE_TYPE_NOTIFICATION_REQUEST};
}
}
diff --git a/src/org/kde/kdeconnect/Plugins/RemoteKeyboardPlugin/RemoteKeyboardPlugin.java b/src/org/kde/kdeconnect/Plugins/RemoteKeyboardPlugin/RemoteKeyboardPlugin.java
index 1598e260..7e45f1de 100644
--- a/src/org/kde/kdeconnect/Plugins/RemoteKeyboardPlugin/RemoteKeyboardPlugin.java
+++ b/src/org/kde/kdeconnect/Plugins/RemoteKeyboardPlugin/RemoteKeyboardPlugin.java
@@ -1,398 +1,402 @@
/*
* Copyright 2017 Holger Kaelberer
*
* 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.RemoteKeyboardPlugin;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.support.v4.content.ContextCompat;
import android.support.v4.util.Pair;
import android.util.Log;
import android.util.SparseIntArray;
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ExtractedText;
import android.view.inputmethod.ExtractedTextRequest;
import android.view.inputmethod.InputConnection;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect_tp.R;
import java.util.ArrayList;
import java.util.concurrent.locks.ReentrantLock;
public class RemoteKeyboardPlugin extends Plugin {
public final static String PACKAGE_TYPE_MOUSEPAD_REQUEST = "kdeconnect.mousepad.request";
public final static String PACKAGE_TYPE_MOUSEPAD_ECHO = "kdeconnect.mousepad.echo";
public final static String PACKAGE_TYPE_MOUSEPAD_KEYBOARDSTATE = "kdeconnect.mousepad.keyboardstate";
/**
* Track and expose plugin instances to allow for a 'connected'-indicator in the IME:
*/
private static ArrayList instances = new ArrayList();
private static ReentrantLock instancesLock = new ReentrantLock(true);
+
public static ArrayList getInstances() {
return instances;
}
+
public static ArrayList acquireInstances() {
instancesLock.lock();
return getInstances();
}
+
public static ArrayList releaseInstances() {
instancesLock.unlock();
return getInstances();
}
+
public static boolean isConnected() {
return instances.size() > 0;
}
private static SparseIntArray specialKeyMap = new SparseIntArray();
static {
int i = 0;
specialKeyMap.put(++i, KeyEvent.KEYCODE_DEL); // 1
specialKeyMap.put(++i, KeyEvent.KEYCODE_TAB); // 2
++i; //specialKeyMap.put(++i, KeyEvent.KEYCODE_ENTER, 12); // 3 is not used
specialKeyMap.put(++i, KeyEvent.KEYCODE_DPAD_LEFT); // 4
specialKeyMap.put(++i, KeyEvent.KEYCODE_DPAD_UP); // 5
specialKeyMap.put(++i, KeyEvent.KEYCODE_DPAD_RIGHT); // 6
specialKeyMap.put(++i, KeyEvent.KEYCODE_DPAD_DOWN); // 7
specialKeyMap.put(++i, KeyEvent.KEYCODE_PAGE_UP); // 8
specialKeyMap.put(++i, KeyEvent.KEYCODE_PAGE_DOWN); // 9
if (Build.VERSION.SDK_INT >= 11) {
specialKeyMap.put(++i, KeyEvent.KEYCODE_MOVE_HOME); // 10
specialKeyMap.put(++i, KeyEvent.KEYCODE_MOVE_END); // 11
specialKeyMap.put(++i, KeyEvent.KEYCODE_ENTER); // 12
specialKeyMap.put(++i, KeyEvent.KEYCODE_FORWARD_DEL); // 13
specialKeyMap.put(++i, KeyEvent.KEYCODE_ESCAPE); // 14
specialKeyMap.put(++i, KeyEvent.KEYCODE_SYSRQ); // 15
specialKeyMap.put(++i, KeyEvent.KEYCODE_SCROLL_LOCK); // 16
++i; // 17
++i; // 18
++i; // 19
++i; // 20
specialKeyMap.put(++i, KeyEvent.KEYCODE_F1); // 21
specialKeyMap.put(++i, KeyEvent.KEYCODE_F2); // 22
specialKeyMap.put(++i, KeyEvent.KEYCODE_F3); // 23
specialKeyMap.put(++i, KeyEvent.KEYCODE_F4); // 24
specialKeyMap.put(++i, KeyEvent.KEYCODE_F5); // 25
specialKeyMap.put(++i, KeyEvent.KEYCODE_F6); // 26
specialKeyMap.put(++i, KeyEvent.KEYCODE_F7); // 27
specialKeyMap.put(++i, KeyEvent.KEYCODE_F8); // 28
specialKeyMap.put(++i, KeyEvent.KEYCODE_F9); // 29
specialKeyMap.put(++i, KeyEvent.KEYCODE_F10); // 30
specialKeyMap.put(++i, KeyEvent.KEYCODE_F11); // 31
specialKeyMap.put(++i, KeyEvent.KEYCODE_F12); // 21
}
}
@Override
public boolean onCreate() {
Log.d("RemoteKeyboardPlugin", "Creating for device " + device.getName());
acquireInstances();
try {
instances.add(this);
} finally {
releaseInstances();
}
if (RemoteKeyboardService.instance != null)
RemoteKeyboardService.instance.handler.post(new Runnable() {
@Override
public void run() {
RemoteKeyboardService.instance.updateInputView();
}
});
return true;
}
@Override
public void onDestroy() {
acquireInstances();
try {
if (instances.contains(this)) {
instances.remove(this);
if (instances.size() < 1 && RemoteKeyboardService.instance != null)
RemoteKeyboardService.instance.handler.post(new Runnable() {
@Override
public void run() {
RemoteKeyboardService.instance.updateInputView();
}
});
}
} finally {
releaseInstances();
}
Log.d("RemoteKeyboardPlugin", "Destroying for device " + device.getName());
}
@Override
public String getDisplayName() {
return context.getString(R.string.pref_plugin_remotekeyboard);
}
@Override
public String getDescription() {
return context.getString(R.string.pref_plugin_remotekeyboard_desc);
}
@Override
public Drawable getIcon() {
return ContextCompat.getDrawable(context, R.drawable.ic_action_keyboard);
}
@Override
public boolean hasSettings() {
return true;
}
@Override
public boolean hasMainActivity() {
return false;
}
@Override
public String[] getSupportedPackageTypes() {
return new String[]{PACKAGE_TYPE_MOUSEPAD_REQUEST};
}
@Override
public String[] getOutgoingPackageTypes() {
return new String[]{PACKAGE_TYPE_MOUSEPAD_ECHO, PACKAGE_TYPE_MOUSEPAD_KEYBOARDSTATE};
}
private boolean isValidSpecialKey(int key) {
return (specialKeyMap.get(key, 0) > 0);
}
private int getCharPos(ExtractedText extractedText, char ch, boolean forward) {
int pos = -1;
if (extractedText != null) {
if (!forward) // backward
pos = extractedText.text.toString().lastIndexOf(" ", extractedText.selectionEnd - 2);
else
pos = extractedText.text.toString().indexOf(" ", extractedText.selectionEnd + 1);
return pos;
}
return pos;
}
private int currentTextLength(ExtractedText extractedText) {
if (extractedText != null)
return extractedText.text.length();
return -1;
}
private int currentCursorPos(ExtractedText extractedText) {
if (extractedText != null)
return extractedText.selectionEnd;
return -1;
}
- private Pair currentSelection(ExtractedText extractedText) {
+ private Pair currentSelection(ExtractedText extractedText) {
if (extractedText != null)
return new Pair<>(extractedText.selectionStart, extractedText.selectionEnd);
return new Pair<>(-1, -1);
}
private boolean handleSpecialKey(int key, boolean shift, boolean ctrl, boolean alt) {
int keyEvent = specialKeyMap.get(key, 0);
if (keyEvent == 0)
return false;
InputConnection inputConn = RemoteKeyboardService.instance.getCurrentInputConnection();
// Log.d("RemoteKeyboardPlugin", "Handling special key " + key + " translated to " + keyEvent + " shift=" + shift + " ctrl=" + ctrl + " alt=" + alt);
// special sequences:
if (ctrl && (keyEvent == KeyEvent.KEYCODE_DPAD_RIGHT)) {
// Ctrl + right -> next word
ExtractedText extractedText = inputConn.getExtractedText(new ExtractedTextRequest(), 0);
int pos = getCharPos(extractedText, ' ', keyEvent == KeyEvent.KEYCODE_DPAD_RIGHT);
if (pos == -1)
pos = currentTextLength(extractedText);
else
pos++;
int startPos = pos;
int endPos = pos;
if (shift) { // Shift -> select word (otherwise jump)
- Pair sel = currentSelection(extractedText);
+ Pair sel = currentSelection(extractedText);
int cursor = currentCursorPos(extractedText);
// Log.d("RemoteKeyboardPlugin", "Selection (to right): " + sel.first + " / " + sel.second + " cursor: " + cursor);
startPos = cursor;
if (sel.first < cursor || // active selection from left to right -> grow
- sel.first > sel.second) // active selection from right to left -> shrink
+ sel.first > sel.second) // active selection from right to left -> shrink
startPos = sel.first;
}
inputConn.setSelection(startPos, endPos);
} else if (ctrl && keyEvent == KeyEvent.KEYCODE_DPAD_LEFT) {
// Ctrl + left -> previous word
ExtractedText extractedText = inputConn.getExtractedText(new ExtractedTextRequest(), 0);
int pos = getCharPos(extractedText, ' ', keyEvent == KeyEvent.KEYCODE_DPAD_RIGHT);
if (pos == -1)
pos = 0;
else
pos++;
int startPos = pos;
int endPos = pos;
if (shift) {
- Pair sel = currentSelection(extractedText);
+ Pair sel = currentSelection(extractedText);
int cursor = currentCursorPos(extractedText);
// Log.d("RemoteKeyboardPlugin", "Selection (to left): " + sel.first + " / " + sel.second + " cursor: " + cursor);
startPos = cursor;
if (cursor < sel.first || // active selection from right to left -> grow
- sel.first < sel.second) // active selection from right to left -> shrink
+ sel.first < sel.second) // active selection from right to left -> shrink
startPos = sel.first;
}
inputConn.setSelection(startPos, endPos);
} else if (shift
&& (keyEvent == KeyEvent.KEYCODE_DPAD_LEFT
|| keyEvent == KeyEvent.KEYCODE_DPAD_RIGHT
|| keyEvent == KeyEvent.KEYCODE_DPAD_UP
|| keyEvent == KeyEvent.KEYCODE_DPAD_DOWN
|| keyEvent == KeyEvent.KEYCODE_MOVE_HOME
|| keyEvent == KeyEvent.KEYCODE_MOVE_END)) {
// Shift + up/down/left/right/home/end
long now = SystemClock.uptimeMillis();
inputConn.sendKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_SHIFT_LEFT, 0, 0));
inputConn.sendKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_DOWN, keyEvent, 0, KeyEvent.META_SHIFT_LEFT_ON));
inputConn.sendKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_UP, keyEvent, 0, KeyEvent.META_SHIFT_LEFT_ON));
inputConn.sendKeyEvent(new KeyEvent(now, now, KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SHIFT_LEFT, 0, 0));
} else if (keyEvent == KeyEvent.KEYCODE_NUMPAD_ENTER
|| keyEvent == KeyEvent.KEYCODE_ENTER) {
// Enter key
EditorInfo editorInfo = RemoteKeyboardService.instance.getCurrentInputEditorInfo();
// Log.d("RemoteKeyboardPlugin", "Enter: " + editorInfo.imeOptions);
if (editorInfo != null
&& (((editorInfo.imeOptions & EditorInfo.IME_FLAG_NO_ENTER_ACTION) == 0)
- || ctrl)) { // Ctrl+Return overrides IME_FLAG_NO_ENTER_ACTION (FIXME: make configurable?)
+ || ctrl)) { // Ctrl+Return overrides IME_FLAG_NO_ENTER_ACTION (FIXME: make configurable?)
// check for special DONE/GO/etc actions first:
- int[] actions = { EditorInfo.IME_ACTION_GO, EditorInfo.IME_ACTION_NEXT,
+ int[] actions = {EditorInfo.IME_ACTION_GO, EditorInfo.IME_ACTION_NEXT,
EditorInfo.IME_ACTION_SEND, EditorInfo.IME_ACTION_SEARCH,
EditorInfo.IME_ACTION_DONE}; // note: DONE should be last or we might hide the ime instead of "go"
for (int i = 0; i < actions.length; i++) {
if ((editorInfo.imeOptions & actions[i]) == actions[i]) {
// Log.d("RemoteKeyboardPlugin", "Enter-action: " + actions[i]);
inputConn.performEditorAction(actions[i]);
return true;
}
}
} else {
// else: fall back to regular Enter-event:
// Log.d("RemoteKeyboardPlugin", "Enter: normal keypress");
inputConn.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyEvent));
inputConn.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyEvent));
}
} else {
// default handling:
inputConn.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyEvent));
inputConn.sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyEvent));
}
return true;
}
private boolean handleVisibleKey(String key, boolean shift, boolean ctrl, boolean alt) {
// Log.d("RemoteKeyboardPlugin", "Handling visible key " + key + " shift=" + shift + " ctrl=" + ctrl + " alt=" + alt + " " + key.equalsIgnoreCase("c") + " " + key.length());
if (key.isEmpty())
return false;
InputConnection inputConn = RemoteKeyboardService.instance.getCurrentInputConnection();
if (inputConn == null)
return false;
// ctrl+c/v/x
if (key.equalsIgnoreCase("c") && ctrl) {
return inputConn.performContextMenuAction(android.R.id.copy);
} else if (key.equalsIgnoreCase("v") && ctrl)
return inputConn.performContextMenuAction(android.R.id.paste);
else if (key.equalsIgnoreCase("x") && ctrl)
return inputConn.performContextMenuAction(android.R.id.cut);
else if (key.equalsIgnoreCase("a") && ctrl)
return inputConn.performContextMenuAction(android.R.id.selectAll);
// Log.d("RemoteKeyboardPlugin", "Committing visible key '" + key + "'");
inputConn.commitText(key, key.length());
return true;
}
private boolean handleEvent(NetworkPackage np) {
if (np.has("specialKey") && isValidSpecialKey(np.getInt("specialKey")))
return handleSpecialKey(np.getInt("specialKey"), np.getBoolean("shift"),
np.getBoolean("ctrl"), np.getBoolean("alt"));
// try visible key
return handleVisibleKey(np.getString("key"), np.getBoolean("shift"),
np.getBoolean("ctrl"), np.getBoolean("alt"));
}
@Override
public boolean onPackageReceived(NetworkPackage np) {
if (!np.getType().equals(PACKAGE_TYPE_MOUSEPAD_REQUEST)
|| (!np.has("key") && !np.has("specialKey"))) { // expect at least key OR specialKey
Log.e("RemoteKeyboardPlugin", "Invalid package for remotekeyboard plugin!");
return false;
}
if (RemoteKeyboardService.instance == null) {
Log.i("RemoteKeyboardPlugin", "Remote keyboard is not the currently selected input method, dropping key");
return false;
}
if (!RemoteKeyboardService.instance.visible &&
PreferenceManager.getDefaultSharedPreferences(context).getBoolean(context.getString(R.string.remotekeyboard_editing_only), true)) {
Log.i("RemoteKeyboardPlugin", "Remote keyboard is currently not visible, dropping key");
return false;
}
if (!handleEvent(np)) {
Log.i("RemoteKeyboardPlugin", "Could not handle event!");
return false;
}
if (np.getBoolean("sendAck")) {
NetworkPackage reply = new NetworkPackage(PACKAGE_TYPE_MOUSEPAD_ECHO);
reply.set("key", np.getString("key"));
if (np.has("specialKey"))
reply.set("specialKey", np.getInt("specialKey"));
if (np.has("shift"))
reply.set("shift", np.getBoolean("shift"));
if (np.has("ctrl"))
reply.set("ctrl", np.getBoolean("ctrl"));
if (np.has("alt"))
reply.set("alt", np.getBoolean("alt"));
reply.set("isAck", true);
device.sendPackage(reply);
}
return true;
}
public void notifyKeyboardState(boolean state) {
Log.d("RemoteKeyboardPlugin", "Keyboardstate changed to " + state);
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_MOUSEPAD_KEYBOARDSTATE);
np.set("state", state);
device.sendPackage(np);
}
}
diff --git a/src/org/kde/kdeconnect/Plugins/RemoteKeyboardPlugin/RemoteKeyboardService.java b/src/org/kde/kdeconnect/Plugins/RemoteKeyboardPlugin/RemoteKeyboardService.java
index a118226e..3f2b3852 100644
--- a/src/org/kde/kdeconnect/Plugins/RemoteKeyboardPlugin/RemoteKeyboardService.java
+++ b/src/org/kde/kdeconnect/Plugins/RemoteKeyboardPlugin/RemoteKeyboardService.java
@@ -1,233 +1,233 @@
/*
* Copyright 2017 Holger Kaelberer
*
* 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.RemoteKeyboardPlugin;
import android.content.Context;
import android.content.Intent;
import android.inputmethodservice.InputMethodService;
import android.inputmethodservice.Keyboard;
import android.inputmethodservice.KeyboardView;
import android.inputmethodservice.KeyboardView.OnKeyboardActionListener;
import android.os.Handler;
import android.util.Log;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Toast;
import org.kde.kdeconnect.UserInterface.MaterialActivity;
import org.kde.kdeconnect.UserInterface.PluginSettingsActivity;
import org.kde.kdeconnect_tp.R;
import java.util.ArrayList;
import java.util.List;
public class RemoteKeyboardService
extends InputMethodService
implements OnKeyboardActionListener {
/**
* Reference to our instance
* null if this InputMethod is not currently selected.
*/
public static RemoteKeyboardService instance = null;
/**
* Whether input is currently accepted
* Implies visible == true
*/
public boolean active = false;
/**
* Whether this InputMethod is currently visible.
*/
public boolean visible = false;
KeyboardView inputView = null;
private final int StatusKeyIdx = 3;
private final int connectedIcon = R.drawable.ic_phonelink_white_36dp;
private final int disconnectedIcon = R.drawable.ic_phonelink_off_white_36dp;
Handler handler;
void updateInputView() {
if (inputView == null)
return;
Keyboard currentKeyboard = inputView.getKeyboard();
List keys = currentKeyboard.getKeys();
boolean connected = RemoteKeyboardPlugin.isConnected();
// Log.d("RemoteKeyboardService", "Updating keyboard connection icon, connected=" + connected);
keys.get(StatusKeyIdx).icon = getResources().getDrawable(connected ? connectedIcon : disconnectedIcon);
inputView.invalidateKey(StatusKeyIdx);
}
@Override
public void onCreate() {
super.onCreate();
active = false;
visible = false;
instance = this;
handler = new Handler();
Log.d("RemoteKeyboardService", "Remote keyboard initialized");
}
@Override
public void onDestroy() {
super.onDestroy();
instance = null;
Log.d("RemoteKeyboardService", "Destroyed");
}
@Override
public void onInitializeInterface() {
super.onInitializeInterface();
}
@Override
public View onCreateInputView() {
// Log.d("RemoteKeyboardService", "onCreateInputView connected=" + RemoteKeyboardPlugin.isConnected());
inputView = new KeyboardView(this, null);
inputView.setKeyboard(new Keyboard(this, R.xml.remotekeyboardplugin_keyboard));
inputView.setPreviewEnabled(false);
inputView.setOnKeyboardActionListener(this);
updateInputView();
return inputView;
}
@Override
public void onStartInputView(EditorInfo attribute, boolean restarting) {
// Log.d("RemoteKeyboardService", "onStartInputView");
super.onStartInputView(attribute, restarting);
visible = true;
ArrayList instances = RemoteKeyboardPlugin.acquireInstances();
try {
- for (RemoteKeyboardPlugin i: instances)
+ for (RemoteKeyboardPlugin i : instances)
i.notifyKeyboardState(true);
} finally {
RemoteKeyboardPlugin.releaseInstances();
}
}
@Override
public void onFinishInputView(boolean finishingInput) {
// Log.d("RemoteKeyboardService", "onFinishInputView");
super.onFinishInputView(finishingInput);
visible = false;
ArrayList instances = RemoteKeyboardPlugin.acquireInstances();
try {
- for (RemoteKeyboardPlugin i: instances)
+ for (RemoteKeyboardPlugin i : instances)
i.notifyKeyboardState(false);
} finally {
RemoteKeyboardPlugin.releaseInstances();
}
}
@Override
public void onStartInput(EditorInfo attribute, boolean restarting) {
// Log.d("RemoteKeyboardService", "onStartInput");
super.onStartInput(attribute, restarting);
active = true;
}
@Override
public void onFinishInput() {
// Log.d("RemoteKeyboardService", "onFinishInput");
super.onFinishInput();
active = false;
}
@Override
public void onPress(int primaryCode) {
switch (primaryCode) {
case 0: { // "hide keyboard"
requestHideSelf(0);
break;
}
case 1: { // "settings"
ArrayList instances = RemoteKeyboardPlugin.acquireInstances();
try {
if (instances.size() == 1) { // single instance of RemoteKeyboardPlugin -> access its settings
RemoteKeyboardPlugin plugin = instances.get(0);
if (plugin != null) {
Intent intent = new Intent(this, PluginSettingsActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("plugin_display_name", plugin.getDisplayName());
intent.putExtra("plugin_key", plugin.getPluginKey());
startActivity(intent);
}
} else { // != 1 instance of plugin -> show main activity view
Intent intent = new Intent(this, MaterialActivity.class);
intent.putExtra("forceOverview", true);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
if (instances.size() < 1)
Toast.makeText(this, R.string.remotekeyboard_not_connected, Toast.LENGTH_SHORT).show();
else // instances.size() > 1
Toast.makeText(this, R.string.remotekeyboard_multiple_connections, Toast.LENGTH_SHORT).show();
}
} finally {
RemoteKeyboardPlugin.releaseInstances();
}
break;
}
case 2: { // "keyboard"
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.showInputMethodPicker();
break;
}
case 3: { // "connected"?
if (RemoteKeyboardPlugin.isConnected())
Toast.makeText(this, R.string.remotekeyboard_connected, Toast.LENGTH_SHORT).show();
else
Toast.makeText(this, R.string.remotekeyboard_not_connected, Toast.LENGTH_SHORT).show();
break;
}
}
}
@Override
public void onKey(int primaryCode, int[] keyCodes) {
}
@Override
public void onText(CharSequence text) {
}
@Override
public void swipeRight() {
}
@Override
public void swipeLeft() {
}
@Override
public void swipeDown() {
}
@Override
public void swipeUp() {
}
@Override
public void onRelease(int primaryCode) {
}
}
diff --git a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandPlugin.java b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandPlugin.java
index ccb8bac4..077bebea 100644
--- a/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandPlugin.java
+++ b/src/org/kde/kdeconnect/Plugins/RunCommandPlugin/RunCommandPlugin.java
@@ -1,151 +1,151 @@
/*
* Copyright 2015 Aleix Pol Gonzalez
* Copyright 2015 Albert Vaca Cintora
*
* 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.RunCommandPlugin;
import android.app.Activity;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.support.v4.content.ContextCompat;
import org.json.JSONException;
import org.json.JSONObject;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect_tp.R;
import java.util.ArrayList;
import java.util.Iterator;
public class RunCommandPlugin extends Plugin {
public final static String PACKAGE_TYPE_RUNCOMMAND = "kdeconnect.runcommand";
public final static String PACKAGE_TYPE_RUNCOMMAND_REQUEST = "kdeconnect.runcommand.request";
private ArrayList commandList = new ArrayList<>();
private ArrayList callbacks = new ArrayList<>();
public void addCommandsUpdatedCallback(CommandsChangedCallback newCallback) {
callbacks.add(newCallback);
}
public void removeCommandsUpdatedCallback(CommandsChangedCallback theCallback) {
callbacks.remove(theCallback);
}
interface CommandsChangedCallback {
void update();
- };
+ }
public ArrayList getCommandList() {
return commandList;
}
@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.runcommand_plugin_icon);
}
@Override
public boolean onCreate() {
requestCommandList();
return true;
}
@Override
public boolean onPackageReceived(NetworkPackage np) {
if (np.has("commandList")) {
commandList.clear();
try {
JSONObject obj = new JSONObject(np.getString("commandList"));
Iterator keys = obj.keys();
- while(keys.hasNext()){
+ while (keys.hasNext()) {
String s = keys.next();
JSONObject o = obj.getJSONObject(s);
o.put("key", s);
commandList.add(o);
}
} catch (JSONException e) {
e.printStackTrace();
}
for (CommandsChangedCallback aCallback : callbacks) {
aCallback.update();
}
device.onPluginsChanged();
return true;
}
return false;
}
@Override
public String[] getSupportedPackageTypes() {
return new String[]{PACKAGE_TYPE_RUNCOMMAND};
}
@Override
public String[] getOutgoingPackageTypes() {
return new String[]{PACKAGE_TYPE_RUNCOMMAND_REQUEST};
}
public void runCommand(String cmdKey) {
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_RUNCOMMAND_REQUEST);
np.set("key", cmdKey);
device.sendPackage(np);
}
private void requestCommandList() {
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_RUNCOMMAND_REQUEST);
np.set("requestCommandList", true);
device.sendPackage(np);
}
@Override
public boolean hasMainActivity() {
return !commandList.isEmpty();
}
@Override
public void startMainActivity(Activity parentActivity) {
Intent intent = new Intent(parentActivity, RunCommandActivity.class);
intent.putExtra("deviceId", device.getDeviceId());
parentActivity.startActivity(intent);
}
@Override
public String getActionName() {
return context.getString(R.string.pref_plugin_runcommand);
}
}
diff --git a/src/org/kde/kdeconnect/Plugins/SftpPlugin/SimpleSftpServer.java b/src/org/kde/kdeconnect/Plugins/SftpPlugin/SimpleSftpServer.java
index e021cb5e..f2e36ae0 100644
--- a/src/org/kde/kdeconnect/Plugins/SftpPlugin/SimpleSftpServer.java
+++ b/src/org/kde/kdeconnect/Plugins/SftpPlugin/SimpleSftpServer.java
@@ -1,291 +1,292 @@
/*
* Copyright 2014 Samoilenko Yuri
*
* 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.SftpPlugin;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import org.apache.sshd.SshServer;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.Session;
import org.apache.sshd.common.util.SecurityUtils;
import org.apache.sshd.server.Command;
import org.apache.sshd.server.FileSystemFactory;
import org.apache.sshd.server.FileSystemView;
import org.apache.sshd.server.PasswordAuthenticator;
import org.apache.sshd.server.PublickeyAuthenticator;
import org.apache.sshd.server.SshFile;
import org.apache.sshd.server.command.ScpCommandFactory;
import org.apache.sshd.server.filesystem.NativeFileSystemView;
import org.apache.sshd.server.filesystem.NativeSshFile;
import org.apache.sshd.server.kex.DHG1;
import org.apache.sshd.server.kex.DHG14;
import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider;
import org.apache.sshd.server.session.ServerSession;
import org.apache.sshd.server.sftp.SftpSubsystem;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.MediaStoreHelper;
import org.kde.kdeconnect.Helpers.RandomHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.security.PublicKey;
import java.security.Security;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
class SimpleSftpServer {
private static final int STARTPORT = 1739;
private static final int ENDPORT = 1764;
static final String USER = "kdeconnect";
private int port = -1;
private boolean started = false;
private final SimplePasswordAuthenticator passwordAuth = new SimplePasswordAuthenticator();
private final SimplePublicKeyAuthenticator keyAuth = new SimplePublicKeyAuthenticator();
static {
Security.insertProviderAt(SslHelper.BC, 1);
SecurityUtils.setRegisterBouncyCastle(false);
}
+
private final SshServer sshd = SshServer.setUpDefaultServer();
public void init(Context context, Device device) {
sshd.setKeyExchangeFactories(Arrays.asList(
new DHG14.Factory(),
new DHG1.Factory()));
sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(context.getFilesDir() + "/sftpd.ser"));
sshd.setFileSystemFactory(new AndroidFileSystemFactory(context));
sshd.setCommandFactory(new ScpCommandFactory());
- sshd.setSubsystemFactories(Collections.singletonList((NamedFactory)new SftpSubsystem.Factory()));
+ sshd.setSubsystemFactories(Collections.singletonList((NamedFactory) new SftpSubsystem.Factory()));
if (device.publicKey != null) {
keyAuth.deviceKey = device.publicKey;
sshd.setPublickeyAuthenticator(keyAuth);
}
sshd.setPasswordAuthenticator(passwordAuth);
}
public boolean start() {
if (!started) {
passwordAuth.password = RandomHelper.randomString(28);
port = STARTPORT;
- while(!started) {
+ while (!started) {
try {
sshd.setPort(port);
sshd.start();
started = true;
- } catch(Exception e) {
+ } catch (Exception e) {
e.printStackTrace();
port++;
if (port >= ENDPORT) {
port = -1;
Log.e("SftpServer", "No more ports available");
return false;
}
}
}
}
return true;
}
public void stop() {
try {
started = false;
sshd.stop(true);
} catch (Exception e) {
e.printStackTrace();
}
}
public String getPassword() {
return passwordAuth.password;
}
public int getPort() {
return port;
}
public String getLocalIpAddress() {
String ip6 = null;
try {
- for (Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) {
+ for (Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
NetworkInterface intf = en.nextElement();
// Anything with rmnet is related to cellular connections or USB
// tethering mechanisms. See:
//
// https://android.googlesource.com/kernel/msm/+/android-msm-flo-3.4-kitkat-mr1/Documentation/usb/gadget_rmnet.txt
//
// If we run across an interface that has this, we can safely
// ignore it. In fact, it's much safer to do. If we don't, we
// might get invalid IP adddresses out of it.
- if(intf.getDisplayName().contains("rmnet")) continue;
+ if (intf.getDisplayName().contains("rmnet")) continue;
- for (Enumeration enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) {
+ for (Enumeration enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
InetAddress inetAddress = enumIpAddr.nextElement();
if (!inetAddress.isLoopbackAddress()) {
String address = inetAddress.getHostAddress();
- if(inetAddress instanceof Inet4Address) { //Prefer IPv4 over IPv6, because sshfs doesn't seem to like IPv6
+ if (inetAddress instanceof Inet4Address) { //Prefer IPv4 over IPv6, because sshfs doesn't seem to like IPv6
return address;
} else {
ip6 = address;
}
}
}
}
} catch (SocketException ex) {
}
return ip6;
}
static class AndroidFileSystemFactory implements FileSystemFactory {
final private Context context;
public AndroidFileSystemFactory(Context context) {
this.context = context;
}
@Override
public FileSystemView createFileSystemView(final Session username) {
return new AndroidFileSystemView(username.getUsername(), context);
}
}
static class AndroidFileSystemView extends NativeFileSystemView {
final private String userName;
final private Context context;
public AndroidFileSystemView(final String userName, Context context) {
super(userName, true);
this.userName = userName;
this.context = context;
}
@Override
protected SshFile getFile(final String dir, final String file) {
File fileObj = new File(dir, file);
return new AndroidSshFile(fileObj, userName, context);
}
}
static class AndroidSshFile extends NativeSshFile {
final private Context context;
final private File file;
public AndroidSshFile(final File file, final String userName, Context context) {
super(file.getAbsolutePath(), file, userName);
this.context = context;
this.file = file;
}
@Override
public OutputStream createOutputStream(long offset) throws IOException {
if (!isWritable()) {
throw new IOException("No write permission : " + file.getName());
}
final RandomAccessFile raf = new RandomAccessFile(file, "rw");
try {
if (offset < raf.length()) {
throw new IOException("Your SSHFS is bugged"); //SSHFS 3.0 and 3.2 cause data corruption, abort the transfer if this happens
}
raf.setLength(offset);
raf.seek(offset);
return new FileOutputStream(raf.getFD()) {
public void close() throws IOException {
super.close();
raf.close();
}
};
} catch (IOException e) {
raf.close();
throw e;
}
}
@Override
public boolean delete() {
//Log.e("Sshd", "deleting file");
boolean ret = super.delete();
if (ret) {
MediaStoreHelper.indexFile(context, Uri.fromFile(file));
}
return ret;
}
@Override
public boolean create() throws IOException {
//Log.e("Sshd", "creating file");
boolean ret = super.create();
if (ret) {
MediaStoreHelper.indexFile(context, Uri.fromFile(file));
}
return ret;
}
}
static class SimplePasswordAuthenticator implements PasswordAuthenticator {
public String password;
@Override
public boolean authenticate(String user, String password, ServerSession session) {
return user.equals(SimpleSftpServer.USER) && password.equals(this.password);
}
}
static class SimplePublicKeyAuthenticator implements PublickeyAuthenticator {
public PublicKey deviceKey;
@Override
public boolean authenticate(String user, PublicKey key, ServerSession session) {
return deviceKey.equals(key);
}
}
}
diff --git a/src/org/kde/kdeconnect/Plugins/SharePlugin/NotificationUpdateCallback.java b/src/org/kde/kdeconnect/Plugins/SharePlugin/NotificationUpdateCallback.java
index c448e86f..911294b1 100644
--- a/src/org/kde/kdeconnect/Plugins/SharePlugin/NotificationUpdateCallback.java
+++ b/src/org/kde/kdeconnect/Plugins/SharePlugin/NotificationUpdateCallback.java
@@ -1,124 +1,124 @@
package org.kde.kdeconnect.Plugins.SharePlugin;
import android.app.NotificationManager;
import android.content.Context;
import android.content.res.Resources;
import android.support.v4.app.NotificationCompat;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.NotificationHelper;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect_tp.R;
import java.util.ArrayList;
class NotificationUpdateCallback extends Device.SendPackageStatusCallback {
final Context context;
final Resources res;
final Device device;
final NotificationManager notificationManager;
final NotificationCompat.Builder builder;
final ArrayList toSend;
final int notificationId;
int sentFiles = 0;
final int numFiles;
NotificationUpdateCallback(Context context, Device device, ArrayList toSend) {
this.context = context;
this.toSend = toSend;
this.device = device;
this.res = context.getResources();
String title;
if (toSend.size() > 1) {
title = res.getString(R.string.outgoing_files_title, device.getName());
} else {
title = res.getString(R.string.outgoing_file_title, device.getName());
}
- notificationManager = (NotificationManager)context.getSystemService(Context.NOTIFICATION_SERVICE);
+ notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
builder = new NotificationCompat.Builder(context)
.setSmallIcon(android.R.drawable.stat_sys_upload)
.setAutoCancel(true)
.setProgress(100, 0, false)
.setContentTitle(title)
.setTicker(title);
- notificationId = (int)System.currentTimeMillis();
+ notificationId = (int) System.currentTimeMillis();
numFiles = toSend.size();
}
@Override
public void onProgressChanged(int progress) {
builder.setProgress(100 * numFiles, (100 * sentFiles) + progress, false);
NotificationHelper.notifyCompat(notificationManager, notificationId, builder.build());
}
@Override
public void onSuccess() {
sentFiles++;
if (sentFiles == numFiles) {
updateDone(true);
} else {
updateText();
}
NotificationHelper.notifyCompat(notificationManager, notificationId, builder.build());
}
@Override
public void onFailure(Throwable e) {
updateDone(false);
NotificationHelper.notifyCompat(notificationManager, notificationId, builder.build());
if (e != null) {
e.printStackTrace();
}
}
private void updateText() {
String text;
if (numFiles > 1) {
text = res.getString(R.string.outgoing_files_text, sentFiles, numFiles);
} else {
text = res.getString(R.string.outgoing_file_text, device.getName());
}
builder.setContentText(text);
}
private void updateDone(boolean successful) {
int icon;
String title;
String text;
int progress;
if (successful) {
progress = 1;
if (numFiles > 1) {
text = res.getString(R.string.outgoing_files_text, sentFiles, numFiles);
} else {
final String filename = toSend.get(0).getString("filename");
text = res.getString(R.string.sent_file_text, filename);
}
title = res.getString(R.string.sent_file_title, device.getName());
icon = android.R.drawable.stat_sys_upload_done;
} else {
progress = 0;
final String filename = toSend.get(sentFiles).getString("filename");
title = res.getString(R.string.sent_file_failed_title, device.getName());
text = res.getString(R.string.sent_file_failed_text, filename);
icon = android.R.drawable.stat_notify_error;
}
builder.setOngoing(false)
.setTicker(title)
.setContentTitle(title)
.setContentText(text)
.setSmallIcon(icon)
.setProgress(0, 0, false); //setting progress to 0 out of 0 remove the progress bar
}
}
diff --git a/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareActivity.java b/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareActivity.java
index 2d1fe2e4..cfe01a95 100644
--- a/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareActivity.java
+++ b/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareActivity.java
@@ -1,216 +1,219 @@
/*
* Copyright 2014 Albert Vaca Cintora
*
* 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.SharePlugin;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.UserInterface.List.EntryItem;
import org.kde.kdeconnect.UserInterface.List.ListAdapter;
import org.kde.kdeconnect.UserInterface.List.SectionItem;
import org.kde.kdeconnect_tp.R;
import java.util.ArrayList;
import java.util.Collection;
public class ShareActivity extends AppCompatActivity {
private SwipeRefreshLayout mSwipeRefreshLayout;
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.refresh, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_refresh:
updateComputerListAction();
break;
default:
break;
}
return true;
}
private void updateComputerListAction() {
updateComputerList();
BackgroundService.RunCommand(ShareActivity.this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
service.onNetworkChange();
}
});
mSwipeRefreshLayout.setRefreshing(true);
new Thread(new Runnable() {
@Override
public void run() {
- try { Thread.sleep(1500); } catch (InterruptedException ignored) { }
+ try {
+ Thread.sleep(1500);
+ } catch (InterruptedException ignored) {
+ }
runOnUiThread(new Runnable() {
@Override
public void run() {
mSwipeRefreshLayout.setRefreshing(false);
}
});
}
}).start();
}
private void updateComputerList() {
final Intent intent = getIntent();
String action = intent.getAction();
if (!Intent.ACTION_SEND.equals(action) && !Intent.ACTION_SEND_MULTIPLE.equals(action)) {
finish();
return;
}
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(final BackgroundService service) {
Collection devices = service.getDevices().values();
final ArrayList devicesList = new ArrayList<>();
final ArrayList items = new ArrayList<>();
items.add(new SectionItem(getString(R.string.share_to)));
for (Device d : devices) {
if (d.isReachable() && d.isPaired()) {
devicesList.add(d);
items.add(new EntryItem(d.getName()));
}
}
runOnUiThread(new Runnable() {
@Override
public void run() {
ListView list = (ListView) findViewById(R.id.listView1);
list.setAdapter(new ListAdapter(ShareActivity.this, items));
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> adapterView, View view, int i, long l) {
Device device = devicesList.get(i - 1); //NOTE: -1 because of the title!
SharePlugin.share(intent, device);
finish();
}
});
}
});
}
});
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_refresh_list);
ActionBar actionBar = getSupportActionBar();
mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.refresh_list_layout);
mSwipeRefreshLayout.setOnRefreshListener(
new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
updateComputerListAction();
}
}
);
if (actionBar != null) {
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM);
}
}
@Override
protected void onStart() {
super.onStart();
final Intent intent = getIntent();
final String deviceId = intent.getStringExtra("deviceId");
- if (deviceId!=null) {
+ if (deviceId != null) {
- BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback(){
+ BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
- Log.d("DirectShare", "sharing to "+service.getDevice(deviceId).getName());
+ Log.d("DirectShare", "sharing to " + service.getDevice(deviceId).getName());
Device device = service.getDevice(deviceId);
if (device.isReachable() && device.isPaired()) {
SharePlugin.share(intent, device);
}
finish();
}
});
} else {
BackgroundService.addGuiInUseCounter(this);
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
service.onNetworkChange();
service.addDeviceListChangedCallback("ShareActivity", new BackgroundService.DeviceListChangedCallback() {
@Override
public void onDeviceListChanged() {
updateComputerList();
}
});
}
});
updateComputerList();
}
}
@Override
protected void onStop() {
BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
service.removeDeviceListChangedCallback("ShareActivity");
}
});
BackgroundService.removeGuiInUseCounter(this);
super.onStop();
}
}
diff --git a/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareChooserTargetService.java b/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareChooserTargetService.java
index e8196361..40ef2392 100644
--- a/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareChooserTargetService.java
+++ b/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareChooserTargetService.java
@@ -1,63 +1,63 @@
/*
* Copyright 2017 Nicolas Fella
*
* 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.SharePlugin;
import android.annotation.TargetApi;
import android.content.ComponentName;
import android.content.IntentFilter;
import android.graphics.drawable.Icon;
import android.os.Bundle;
import android.service.chooser.ChooserTarget;
import android.service.chooser.ChooserTargetService;
import android.util.Log;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect_tp.R;
import java.util.ArrayList;
import java.util.List;
@TargetApi(23)
public class ShareChooserTargetService extends ChooserTargetService {
@Override
public List onGetChooserTargets(ComponentName targetActivityName, IntentFilter matchedFilter) {
Log.d("DirectShare", "invoked");
final List targets = new ArrayList<>();
- for(Device d: BackgroundService.getInstance().getDevices().values()){
- if(d.isReachable() && d.isPaired()) {
+ for (Device d : BackgroundService.getInstance().getDevices().values()) {
+ if (d.isReachable() && d.isPaired()) {
Log.d("DirectShare", d.getName());
final String targetName = d.getName();
final Icon targetIcon = Icon.createWithResource(this, R.drawable.icon);
final float targetRanking = 1;
final ComponentName targetComponentName = new ComponentName(getPackageName(),
ShareActivity.class.getCanonicalName());
final Bundle targetExtras = new Bundle();
targetExtras.putString("deviceId", d.getDeviceId());
targets.add(new ChooserTarget(
targetName, targetIcon, targetRanking, targetComponentName, targetExtras
));
}
}
return targets;
}
}
\ No newline at end of file
diff --git a/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareNotification.java b/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareNotification.java
index 2627ec6b..ff24f7b0 100644
--- a/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareNotification.java
+++ b/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareNotification.java
@@ -1,123 +1,123 @@
package org.kde.kdeconnect.Plugins.SharePlugin;
/*
* Copyright 2017 Nicolas Fella
*
* 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 .
*/
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.support.v4.content.FileProvider;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.NotificationHelper;
import org.kde.kdeconnect_tp.R;
import java.io.File;
public class ShareNotification {
private final String filename;
private NotificationManager notificationManager;
private int notificationId;
private NotificationCompat.Builder builder;
private Device device;
public ShareNotification(Device device, String filename) {
this.device = device;
this.filename = filename;
notificationId = (int) System.currentTimeMillis();
notificationManager = (NotificationManager) device.getContext().getSystemService(Context.NOTIFICATION_SERVICE);
builder = new NotificationCompat.Builder(device.getContext())
.setContentTitle(device.getContext().getResources().getString(R.string.incoming_file_title, device.getName()))
.setContentText(device.getContext().getResources().getString(R.string.incoming_file_text, filename))
.setTicker(device.getContext().getResources().getString(R.string.incoming_file_title, device.getName()))
.setSmallIcon(android.R.drawable.stat_sys_download)
.setAutoCancel(true)
.setOngoing(true)
.setProgress(100, 0, true);
}
public void show() {
NotificationHelper.notifyCompat(notificationManager, notificationId, builder.build());
}
public int getId() {
return notificationId;
}
public void setProgress(int progress) {
builder.setProgress(100, progress, false)
- .setContentTitle(device.getContext().getResources().getString(R.string.incoming_file_title, device.getName())+" ("+progress+"%)");
+ .setContentTitle(device.getContext().getResources().getString(R.string.incoming_file_title, device.getName()) + " (" + progress + "%)");
}
public void setFinished(boolean success) {
String message = success ? device.getContext().getResources().getString(R.string.received_file_title, device.getName()) : device.getContext().getResources().getString(R.string.received_file_fail_title, device.getName());
builder = new NotificationCompat.Builder(device.getContext());
builder.setContentTitle(message)
.setTicker(message)
.setSmallIcon(android.R.drawable.stat_sys_download_done)
.setAutoCancel(true)
.setOngoing(false);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(device.getContext());
if (prefs.getBoolean("share_notification_preference", true)) {
builder.setDefaults(Notification.DEFAULT_ALL);
}
}
public void setURI(Uri destinationUri, String mimeType) {
/*
* We only support file URIs (because sending a content uri to another app does not work for security reasons).
* In effect, that means only the default download folder currently works.
*
* TODO: implement our own content provider (instead of support-v4's FileProvider). It should:
* - Proxy to real files (in case of the default download folder)
* - Proxy to the underlying content uri (in case of a custom download folder)
*/
if (!"file".equals(destinationUri.getScheme())) {
return;
}
Intent intent = new Intent(Intent.ACTION_VIEW);
if (Build.VERSION.SDK_INT >= 24) {
//Nougat and later require "content://" uris instead of "file://" uris
File file = new File(destinationUri.getPath());
Uri contentUri = FileProvider.getUriForFile(device.getContext(), "org.kde.kdeconnect_tp.fileprovider", file);
intent.setDataAndType(contentUri, mimeType);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
} else {
intent.setDataAndType(destinationUri, mimeType);
}
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(device.getContext());
stackBuilder.addNextIntent(intent);
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
builder.setContentText(device.getContext().getResources().getString(R.string.received_file_text, filename))
.setContentIntent(resultPendingIntent);
}
}
diff --git a/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java b/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java
index 8a822cc4..51d12bdf 100644
--- a/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java
+++ b/src/org/kde/kdeconnect/Plugins/SharePlugin/SharePlugin.java
@@ -1,472 +1,472 @@
/*
* Copyright 2014 Albert Vaca Cintora
*
* 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.SharePlugin;
import android.Manifest;
import android.app.Activity;
import android.app.DownloadManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ClipboardManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.support.v4.content.ContextCompat;
import android.support.v4.provider.DocumentFile;
import android.util.Log;
import android.widget.Toast;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.FilesHelper;
import org.kde.kdeconnect.Helpers.MediaStoreHelper;
import org.kde.kdeconnect.Helpers.NotificationHelper;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.UserInterface.SettingsActivity;
import org.kde.kdeconnect_tp.R;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayList;
public class SharePlugin extends Plugin {
public final static String PACKAGE_TYPE_SHARE_REQUEST = "kdeconnect.share.request";
final static boolean openUrlsDirectly = true;
private int sharePermissionExplanation = R.string.share_optional_permission_explanation;
@Override
public boolean onCreate() {
optionalPermissionExplanation = sharePermissionExplanation;
return true;
}
@Override
public String getDisplayName() {
return context.getResources().getString(R.string.pref_plugin_sharereceiver);
}
@Override
public Drawable getIcon() {
return ContextCompat.getDrawable(context, R.drawable.share_plugin_action);
}
@Override
public String getDescription() {
return context.getResources().getString(R.string.pref_plugin_sharereceiver_desc);
}
@Override
public boolean hasMainActivity() {
return true;
}
@Override
public String getActionName() {
return context.getString(R.string.send_files);
}
@Override
public void startMainActivity(Activity parentActivity) {
Intent intent = new Intent(parentActivity, SendFileActivity.class);
intent.putExtra("deviceId", device.getDeviceId());
parentActivity.startActivity(intent);
}
@Override
public boolean hasSettings() {
return true;
}
@Override
public boolean onPackageReceived(NetworkPackage np) {
try {
if (np.hasPayload()) {
Log.i("SharePlugin", "hasPayload");
-
+
if (isPermissionGranted(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
receiveFile(np);
} else {
Log.i("SharePlugin", "no Permission for Storage");
}
} else if (np.has("text")) {
Log.i("SharePlugin", "hasText");
receiveText(np);
} else if (np.has("url")) {
receiveUrl(np);
} else {
Log.e("SharePlugin", "Error: Nothing attached!");
}
} catch (Exception e) {
Log.e("SharePlugin", "Exception");
e.printStackTrace();
}
return true;
}
private void receiveUrl(NetworkPackage np) {
String url = np.getString("url");
Log.i("SharePlugin", "hasUrl: " + url);
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
browserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (openUrlsDirectly) {
context.startActivity(browserIntent);
} else {
Resources res = context.getResources();
TaskStackBuilder stackBuilder = TaskStackBuilder.create(context);
stackBuilder.addNextIntent(browserIntent);
PendingIntent resultPendingIntent = stackBuilder.getPendingIntent(
0,
PendingIntent.FLAG_UPDATE_CURRENT
);
Notification noti = new NotificationCompat.Builder(context)
.setContentTitle(res.getString(R.string.received_url_title, device.getName()))
.setContentText(res.getString(R.string.received_url_text, url))
.setContentIntent(resultPendingIntent)
.setTicker(res.getString(R.string.received_url_title, device.getName()))
.setSmallIcon(R.drawable.ic_notification)
.setAutoCancel(true)
.setDefaults(Notification.DEFAULT_ALL)
.build();
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
NotificationHelper.notifyCompat(notificationManager, (int) System.currentTimeMillis(), noti);
}
}
private void receiveText(NetworkPackage np) {
String text = np.getString("text");
if (Build.VERSION.SDK_INT >= 11) {
ClipboardManager cm = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
cm.setText(text);
} else {
android.text.ClipboardManager clipboard = (android.text.ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setText(text);
}
Toast.makeText(context, R.string.shareplugin_text_saved, Toast.LENGTH_LONG).show();
}
private void receiveFile(NetworkPackage np) {
final InputStream input = np.getPayload();
final long fileLength = np.getPayloadSize();
final String originalFilename = np.getString("filename", Long.toString(System.currentTimeMillis()));
//We need to check for already existing files only when storing in the default path.
//User-defined paths use the new Storage Access Framework that already handles this.
final boolean customDestination = ShareSettingsActivity.isCustomDestinationEnabled(context);
final String defaultPath = ShareSettingsActivity.getDefaultDestinationDirectory().getAbsolutePath();
final String filename = customDestination ? originalFilename : FilesHelper.findNonExistingNameForNewFile(defaultPath, originalFilename);
String displayName = FilesHelper.getFileNameWithoutExt(filename);
final String mimeType = FilesHelper.getMimeTypeFromFile(filename);
if ("*/*".equals(mimeType)) {
displayName = filename;
}
final DocumentFile destinationFolderDocument = ShareSettingsActivity.getDestinationDirectory(context);
final DocumentFile destinationDocument = destinationFolderDocument.createFile(mimeType, displayName);
final OutputStream destinationOutput;
try {
destinationOutput = context.getContentResolver().openOutputStream(destinationDocument.getUri());
} catch (FileNotFoundException e) {
e.printStackTrace();
return;
}
final Uri destinationUri = destinationDocument.getUri();
final ShareNotification notification = new ShareNotification(device, filename);
notification.show();
new Thread(new Runnable() {
@Override
public void run() {
try {
byte data[] = new byte[4096];
long progress = 0, prevProgressPercentage = -1;
int count;
long lastUpdate = 0;
while ((count = input.read(data)) >= 0) {
progress += count;
destinationOutput.write(data, 0, count);
if (fileLength > 0) {
if (progress >= fileLength) break;
long progressPercentage = (progress * 100 / fileLength);
if (progressPercentage != prevProgressPercentage &&
System.currentTimeMillis() - lastUpdate > 100) {
prevProgressPercentage = progressPercentage;
lastUpdate = System.currentTimeMillis();
notification.setProgress((int) progressPercentage);
notification.show();
}
}
//else Log.e("SharePlugin", "Infinite loop? :D");
}
destinationOutput.flush();
Log.i("SharePlugin", "Transfer finished: " + destinationUri.getPath());
//Update the notification and allow to open the file from it
notification.setFinished(true);
notification.setURI(destinationUri, mimeType);
notification.show();
if (!customDestination && Build.VERSION.SDK_INT >= 12) {
Log.i("SharePlugin", "Adding to downloads");
DownloadManager manager = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
manager.addCompletedDownload(destinationUri.getLastPathSegment(), device.getName(), true, mimeType, destinationUri.getPath(), fileLength, false);
} else {
//Make sure it is added to the Android Gallery anyway
MediaStoreHelper.indexFile(context, destinationUri);
}
} catch (Exception e) {
Log.e("SharePlugin", "Receiver thread exception");
e.printStackTrace();
notification.setFinished(false);
notification.show();
} finally {
try {
destinationOutput.close();
} catch (Exception e) {
}
try {
input.close();
} catch (Exception e) {
}
}
}
}).start();
}
@Override
public void startPreferencesActivity(SettingsActivity parentActivity) {
Intent intent = new Intent(parentActivity, ShareSettingsActivity.class);
intent.putExtra("plugin_display_name", getDisplayName());
intent.putExtra("plugin_key", getPluginKey());
parentActivity.startActivity(intent);
}
static void queuedSendUriList(Context context, final Device device, final ArrayList uriList) {
//Read all the data early, as we only have permissions to do it while the activity is alive
final ArrayList toSend = new ArrayList<>();
for (Uri uri : uriList) {
toSend.add(uriToNetworkPackage(context, uri));
}
//Callback that shows a progress notification
final NotificationUpdateCallback notificationUpdateCallback = new NotificationUpdateCallback(context, device, toSend);
//Do the sending in background
new Thread(new Runnable() {
@Override
public void run() {
//Actually send the files
try {
for (NetworkPackage np : toSend) {
boolean success = device.sendPackageBlocking(np, notificationUpdateCallback);
if (!success) {
Log.e("SharePlugin", "Error sending files");
return;
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
//Create the network package from the URI
private static NetworkPackage uriToNetworkPackage(final Context context, final Uri uri) {
try {
ContentResolver cr = context.getContentResolver();
InputStream inputStream = cr.openInputStream(uri);
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_SHARE_REQUEST);
long size = -1;
if (uri.getScheme().equals("file")) {
// file:// is a non media uri, so we cannot query the ContentProvider
np.set("filename", uri.getLastPathSegment());
try {
size = new File(uri.getPath()).length();
} catch (Exception e) {
Log.e("SendFileActivity", "Could not obtain file size");
e.printStackTrace();
}
} else {
// Probably a content:// uri, so we query the Media content provider
Cursor cursor = null;
try {
String[] proj = {MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.SIZE, MediaStore.MediaColumns.DISPLAY_NAME};
cursor = cr.query(uri, proj, null, null, null);
int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA);
cursor.moveToFirst();
String path = cursor.getString(column_index);
np.set("filename", Uri.parse(path).getLastPathSegment());
size = new File(path).length();
} catch (Exception unused) {
Log.w("SendFileActivity", "Could not resolve media to a file, trying to get info as media");
try {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME);
cursor.moveToFirst();
String name = cursor.getString(column_index);
np.set("filename", name);
} catch (Exception e) {
e.printStackTrace();
Log.e("SendFileActivity", "Could not obtain file name");
}
try {
int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE);
cursor.moveToFirst();
//For some reason this size can differ from the actual file size!
size = cursor.getInt(column_index);
} catch (Exception e) {
Log.e("SendFileActivity", "Could not obtain file size");
e.printStackTrace();
}
} finally {
try {
cursor.close();
} catch (Exception e) {
}
}
}
np.setPayload(inputStream, size);
return np;
} catch (Exception e) {
Log.e("SendFileActivity", "Exception sending files");
e.printStackTrace();
return null;
}
}
- public static void share(Intent intent, Device device){
+ public static void share(Intent intent, Device device) {
Bundle extras = intent.getExtras();
if (extras != null) {
if (extras.containsKey(Intent.EXTRA_STREAM)) {
try {
ArrayList uriList;
if (!Intent.ACTION_SEND.equals(intent.getAction())) {
uriList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
} else {
Uri uri = extras.getParcelable(Intent.EXTRA_STREAM);
uriList = new ArrayList<>();
uriList.add(uri);
}
SharePlugin.queuedSendUriList(device.getContext(), device, uriList);
} catch (Exception e) {
Log.e("ShareActivity", "Exception");
e.printStackTrace();
}
} else if (extras.containsKey(Intent.EXTRA_TEXT)) {
String text = extras.getString(Intent.EXTRA_TEXT);
String subject = extras.getString(Intent.EXTRA_SUBJECT);
//Hack: Detect shared youtube videos, so we can open them in the browser instead of as text
if (subject != null && subject.endsWith("YouTube")) {
int index = text.indexOf(": http://youtu.be/");
if (index > 0) {
text = text.substring(index + 2); //Skip ": "
}
}
boolean isUrl;
try {
new URL(text);
isUrl = true;
} catch (Exception e) {
isUrl = false;
}
NetworkPackage np = new NetworkPackage(SharePlugin.PACKAGE_TYPE_SHARE_REQUEST);
if (isUrl) {
np.set("url", text);
} else {
np.set("text", text);
}
device.sendPackage(np);
}
}
}
@Override
public String[] getSupportedPackageTypes() {
return new String[]{PACKAGE_TYPE_SHARE_REQUEST};
}
@Override
public String[] getOutgoingPackageTypes() {
return new String[]{PACKAGE_TYPE_SHARE_REQUEST};
}
@Override
public String[] getOptionalPermissions() {
return new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE};
}
}
diff --git a/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareSettingsActivity.java b/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareSettingsActivity.java
index 68a046c1..b20ab99e 100644
--- a/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareSettingsActivity.java
+++ b/src/org/kde/kdeconnect/Plugins/SharePlugin/ShareSettingsActivity.java
@@ -1,125 +1,125 @@
package org.kde.kdeconnect.Plugins.SharePlugin;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.preference.CheckBoxPreference;
import android.preference.Preference;
import android.preference.PreferenceManager;
import android.support.v4.provider.DocumentFile;
import android.util.Log;
import org.kde.kdeconnect.UserInterface.PluginSettingsActivity;
import java.io.File;
public class ShareSettingsActivity extends PluginSettingsActivity {
private final static String PREFERENCE_CUSTOMIZE_DESTINATION = "share_destination_custom";
private final static String PREFERENCE_DESTINATION = "share_destination_folder_uri";
private static final int RESULT_PICKER = Activity.RESULT_FIRST_USER;
private Preference filePicker;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final CheckBoxPreference customDownloads = (CheckBoxPreference) findPreference("share_destination_custom");
filePicker = findPreference("share_destination_folder_preference");
if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)) {
customDownloads.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
updateFilePickerStatus((Boolean) newValue);
return true;
}
});
filePicker.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, RESULT_PICKER);
return true;
}
});
} else {
customDownloads.setEnabled(false);
filePicker.setEnabled(false);
}
boolean customized = PreferenceManager.getDefaultSharedPreferences(this).getBoolean(PREFERENCE_CUSTOMIZE_DESTINATION, false);
updateFilePickerStatus(customized);
}
void updateFilePickerStatus(boolean enabled) {
filePicker.setEnabled(enabled);
String path = PreferenceManager.getDefaultSharedPreferences(this).getString(PREFERENCE_DESTINATION, null);
if (enabled && path != null) {
filePicker.setSummary(Uri.parse(path).getPath());
} else {
filePicker.setSummary(getDefaultDestinationDirectory().getAbsolutePath());
}
}
public static File getDefaultDestinationDirectory() {
return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
}
public static boolean isCustomDestinationEnabled(Context context) {
return PreferenceManager.getDefaultSharedPreferences(context).getBoolean(PREFERENCE_CUSTOMIZE_DESTINATION, false);
}
//Will return the appropriate directory, whether it is customized or not
public static DocumentFile getDestinationDirectory(Context context) {
if (PreferenceManager.getDefaultSharedPreferences(context).getBoolean(PREFERENCE_CUSTOMIZE_DESTINATION, false)) {
String path = PreferenceManager.getDefaultSharedPreferences(context).getString(PREFERENCE_DESTINATION, null);
if (path != null) {
//There should be no way to enter here on api level < kitkat
DocumentFile treeDocumentFile = DocumentFile.fromTreeUri(context, Uri.parse(path));
if (treeDocumentFile.canWrite()) { //Checks for FLAG_DIR_SUPPORTS_CREATE on directories
return treeDocumentFile;
} else {
//Maybe permission was revoked
Log.w("SharePlugin", "Share destination is not writable, falling back to default path.");
}
}
}
try {
getDefaultDestinationDirectory().mkdirs();
- } catch(Exception e) {
+ } catch (Exception e) {
e.printStackTrace();
}
return DocumentFile.fromFile(getDefaultDestinationDirectory());
}
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
if (requestCode == RESULT_PICKER
&& resultCode == Activity.RESULT_OK
&& resultData != null) {
Uri uri = resultData.getData();
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION |
- Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
Preference filePicker = findPreference("share_destination_folder_preference");
filePicker.setSummary(uri.getPath());
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
prefs.edit().putString(PREFERENCE_DESTINATION, uri.toString()).apply();
}
}
}
diff --git a/src/org/kde/kdeconnect/Plugins/TelepathyPlugin/TelepathyPlugin.java b/src/org/kde/kdeconnect/Plugins/TelepathyPlugin/TelepathyPlugin.java
index 8ea837aa..f02ce203 100644
--- a/src/org/kde/kdeconnect/Plugins/TelepathyPlugin/TelepathyPlugin.java
+++ b/src/org/kde/kdeconnect/Plugins/TelepathyPlugin/TelepathyPlugin.java
@@ -1,204 +1,204 @@
/*
* Copyright 2014 Albert Vaca Cintora
*
* 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.TelepathyPlugin;
import android.Manifest;
import android.content.pm.PackageManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.telephony.SmsManager;
import android.util.Log;
import java.util.ArrayList;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.Plugins.TelephonyPlugin.TelephonyPlugin;
import org.kde.kdeconnect_tp.R;
public class TelepathyPlugin extends Plugin {
public final static String PACKAGE_TYPE_SMS_REQUEST = "kdeconnect.sms.request";
private int telepathyPermissionExplanation = R.string.telepathy_permission_explanation;
@Override
public boolean onCreate() {
permissionExplanation = telepathyPermissionExplanation;
return true;
}
@Override
public String getDisplayName() {
return context.getResources().getString(R.string.pref_plugin_telepathy);
}
@Override
public String getDescription() {
return context.getResources().getString(R.string.pref_plugin_telepathy_desc);
}
@Override
public void onDestroy() {
}
@Override
public boolean onPackageReceived(NetworkPackage np) {
if (!np.getType().equals(PACKAGE_TYPE_SMS_REQUEST)) {
return false;
}
if (np.getBoolean("sendSms")) {
String phoneNo = np.getString("phoneNumber");
String sms = np.getString("messageBody");
try {
int permissionCheck = ContextCompat.checkSelfPermission(context,
Manifest.permission.SEND_SMS);
- if(permissionCheck == PackageManager.PERMISSION_GRANTED) {
+ if (permissionCheck == PackageManager.PERMISSION_GRANTED) {
SmsManager smsManager = SmsManager.getDefault();
ArrayList parts = smsManager.divideMessage(sms);
// If this message turns out to fit in a single SMS, sendMultpartTextMessage
// properly handles that case
smsManager.sendMultipartTextMessage(phoneNo, null, parts, null, null);
- } else if(permissionCheck == PackageManager.PERMISSION_DENIED){
+ } else if (permissionCheck == PackageManager.PERMISSION_DENIED) {
// TODO Request Permission SEND_SMS
}
//TODO: Notify other end
} catch (Exception e) {
//TODO: Notify other end
Log.e("TelepathyPlugin", e.getMessage());
e.printStackTrace();
}
}
/*
if (np.getBoolean("requestContacts")) {
ArrayList vCards = new ArrayList();
Cursor cursor = context.getContentResolver().query(
Contacts.CONTENT_URI,
null,
Contacts.HAS_PHONE_NUMBER + " > 0 ",
null,
null
);
if (cursor != null && cursor.moveToFirst()) {
try {
do {
String lookupKey = cursor.getString(cursor.getColumnIndex(Contacts.LOOKUP_KEY));
Uri uri = Uri.withAppendedPath(Contacts.CONTENT_VCARD_URI, lookupKey);
AssetFileDescriptor fd = null;
try {
fd = context.getContentResolver()
.openAssetFileDescriptor(uri, "r");
FileInputStream fis = fd.createInputStream();
byte[] b = new byte[(int) fd.getDeclaredLength()];
fis.read(b);
String vCard = new String(b);
vCards.add(vCard);
} catch (FileNotFoundException e) {
e.printStackTrace();
Log.e("RequestContacts", e.getMessage());
} catch (Exception e) {
e.printStackTrace();
Log.e("RequestContacts", e.getMessage());
} finally {
if (fd != null) fd.close();
}
} while (cursor.moveToNext());
Log.e("Contacts","Size: "+vCards.size());
} catch (Exception e) {
e.printStackTrace();
Log.e("RequestContacts", e.getMessage());
} finally {
cursor.close();
}
}
NetworkPackage answer = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_TELEPHONY);
answer.set("contacts",vCards);
device.sendPackage(answer);
}
*/
/*
if (np.getBoolean("requestNumbers")) {
ArrayList numbers = new ArrayList();
Cursor cursor = context.getContentResolver().query(
CommonDataKinds.Phone.CONTENT_URI,
new String[]{
CommonDataKinds.Phone.DISPLAY_NAME,
CommonDataKinds.Phone.NUMBER
},
Contacts.HAS_PHONE_NUMBER + " > 0 ",
null,
null
);
if (cursor != null && cursor.moveToFirst()) {
try {
do {
String number = cursor.getString(cursor.getColumnIndex(CommonDataKinds.Phone.NUMBER));
String name = cursor.getString(cursor.getColumnIndex(CommonDataKinds.Phone.DISPLAY_NAME));
numbers.add(number);
} while (cursor.moveToNext());
Log.e("Numbers","Size: "+numbers.size());
} catch (Exception e) {
e.printStackTrace();
Log.e("RequestContacts", e.getMessage());
} finally {
cursor.close();
}
}
NetworkPackage answer = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_TELEPHONY);
answer.set("numbers",numbers);
device.sendPackage(answer);
}
*/
return true;
}
@Override
public String[] getSupportedPackageTypes() {
return new String[]{PACKAGE_TYPE_SMS_REQUEST, TelephonyPlugin.PACKAGE_TYPE_TELEPHONY_REQUEST};
}
@Override
public String[] getOutgoingPackageTypes() {
return new String[]{};
}
@Override
public String[] getRequiredPermissions() {
return new String[]{Manifest.permission.SEND_SMS/*, Manifest.permission.READ_CONTACTS*/};
}
}
diff --git a/src/org/kde/kdeconnect/Plugins/TelephonyPlugin/TelephonyPlugin.java b/src/org/kde/kdeconnect/Plugins/TelephonyPlugin/TelephonyPlugin.java
index f62c9bb6..c3632f2b 100644
--- a/src/org/kde/kdeconnect/Plugins/TelephonyPlugin/TelephonyPlugin.java
+++ b/src/org/kde/kdeconnect/Plugins/TelephonyPlugin/TelephonyPlugin.java
@@ -1,338 +1,338 @@
/*
* Copyright 2014 Albert Vaca Cintora
*
* 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.TelephonyPlugin;
import android.Manifest;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.media.AudioManager;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.content.ContextCompat;
import android.telephony.PhoneNumberUtils;
import android.telephony.SmsMessage;
import android.telephony.TelephonyManager;
import android.util.Log;
import org.kde.kdeconnect.Helpers.ContactsHelper;
import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect_tp.BuildConfig;
import org.kde.kdeconnect_tp.R;
import java.util.ArrayList;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
public class TelephonyPlugin extends Plugin {
private final static String PACKAGE_TYPE_TELEPHONY = "kdeconnect.telephony";
public final static String PACKAGE_TYPE_TELEPHONY_REQUEST = "kdeconnect.telephony.request";
private static final String KEY_PREF_BLOCKED_NUMBERS = "telephony_blocked_numbers";
private int lastState = TelephonyManager.CALL_STATE_IDLE;
private NetworkPackage lastPackage = null;
private boolean isMuted = false;
private final BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
//Log.e("TelephonyPlugin","Telephony event: " + action);
if ("android.provider.Telephony.SMS_RECEIVED".equals(action)) {
final Bundle bundle = intent.getExtras();
if (bundle == null) return;
final Object[] pdus = (Object[]) bundle.get("pdus");
ArrayList messages = new ArrayList<>();
for (Object pdu : pdus) {
// I hope, but am not sure, that the pdus array is in the order that the parts
// of the SMS message should be
// If it is not, I believe the pdu contains the information necessary to put it
// in order, but in my testing the order seems to be correct, so I won't worry
// about it now.
messages.add(SmsMessage.createFromPdu((byte[]) pdu));
}
smsBroadcastReceived(messages);
} else if (TelephonyManager.ACTION_PHONE_STATE_CHANGED.equals(action)) {
String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE);
int intState = TelephonyManager.CALL_STATE_IDLE;
if (state.equals(TelephonyManager.EXTRA_STATE_RINGING))
intState = TelephonyManager.CALL_STATE_RINGING;
else if (state.equals(TelephonyManager.EXTRA_STATE_OFFHOOK))
intState = TelephonyManager.CALL_STATE_OFFHOOK;
String number = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
if (number == null)
number = intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER);
final int finalIntState = intState;
final String finalNumber = number;
callBroadcastReceived(finalIntState, finalNumber);
}
}
};
@Override
public String getDisplayName() {
return context.getResources().getString(R.string.pref_plugin_telephony);
}
@Override
public String getDescription() {
return context.getResources().getString(R.string.pref_plugin_telephony_desc);
}
private void callBroadcastReceived(int state, String phoneNumber) {
if (isNumberBlocked(phoneNumber))
return;
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_TELEPHONY);
int permissionCheck = ContextCompat.checkSelfPermission(context,
Manifest.permission.READ_CONTACTS);
if (permissionCheck == PackageManager.PERMISSION_GRANTED) {
Map contactInfo = ContactsHelper.phoneNumberLookup(context, phoneNumber);
if (contactInfo.containsKey("name")) {
np.set("contactName", contactInfo.get("name"));
}
if (contactInfo.containsKey("photoID")) {
String photoUri = contactInfo.get("photoID");
if (photoUri != null) {
try {
String base64photo = ContactsHelper.photoId64Encoded(context, photoUri);
if (base64photo != null && !base64photo.isEmpty()) {
np.set("phoneThumbnail", base64photo);
}
} catch (Exception e) {
Log.e("TelephonyPlugin", "Failed to get contact photo");
}
}
}
} else {
np.set("contactName", phoneNumber);
}
if (phoneNumber != null) {
np.set("phoneNumber", phoneNumber);
}
switch (state) {
case TelephonyManager.CALL_STATE_RINGING:
if (isMuted) {
AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
am.setStreamVolume(AudioManager.STREAM_RING, AudioManager.ADJUST_UNMUTE, 0);
} else {
am.setStreamMute(AudioManager.STREAM_RING, false);
}
isMuted = false;
}
np.set("event", "ringing");
device.sendPackage(np);
break;
case TelephonyManager.CALL_STATE_OFFHOOK: //Ongoing call
np.set("event", "talking");
device.sendPackage(np);
break;
case TelephonyManager.CALL_STATE_IDLE:
if (lastState != TelephonyManager.CALL_STATE_IDLE && lastPackage != null) {
//Resend a cancel of the last event (can either be "ringing" or "talking")
lastPackage.set("isCancel", "true");
device.sendPackage(lastPackage);
if (isMuted) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
if (isMuted) {
AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
am.setStreamVolume(AudioManager.STREAM_RING, AudioManager.ADJUST_UNMUTE, 0);
} else {
am.setStreamMute(AudioManager.STREAM_RING, false);
}
isMuted = false;
}
}
}, 500);
}
//Emit a missed call notification if needed
if (lastState == TelephonyManager.CALL_STATE_RINGING) {
np.set("event", "missedCall");
np.set("phoneNumber", lastPackage.getString("phoneNumber", null));
np.set("contactName", lastPackage.getString("contactName", null));
device.sendPackage(np);
}
}
break;
}
lastPackage = np;
lastState = state;
}
private void smsBroadcastReceived(ArrayList messages) {
if (BuildConfig.DEBUG) {
if (!(messages.size() > 0)) {
throw new AssertionError("This method requires at least one message");
}
}
NetworkPackage np = new NetworkPackage(PACKAGE_TYPE_TELEPHONY);
np.set("event", "sms");
StringBuilder messageBody = new StringBuilder();
for (int index = 0; index < messages.size(); index++) {
messageBody.append(messages.get(index).getMessageBody());
}
np.set("messageBody", messageBody.toString());
String phoneNumber = messages.get(0).getOriginatingAddress();
if (isNumberBlocked(phoneNumber))
return;
int permissionCheck = ContextCompat.checkSelfPermission(context,
Manifest.permission.READ_CONTACTS);
if (permissionCheck == PackageManager.PERMISSION_GRANTED) {
Map contactInfo = ContactsHelper.phoneNumberLookup(context, phoneNumber);
if (contactInfo.containsKey("name")) {
np.set("contactName", contactInfo.get("name"));
}
if (contactInfo.containsKey("photoID")) {
np.set("phoneThumbnail", ContactsHelper.photoId64Encoded(context, contactInfo.get("photoID")));
}
}
if (phoneNumber != null) {
np.set("phoneNumber", phoneNumber);
}
device.sendPackage(np);
}
@Override
public boolean onCreate() {
IntentFilter filter = new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
filter.setPriority(500);
filter.addAction(TelephonyManager.ACTION_PHONE_STATE_CHANGED);
context.registerReceiver(receiver, filter);
permissionExplanation = R.string.telephony_permission_explanation;
optionalPermissionExplanation = R.string.telephony_optional_permission_explanation;
return true;
}
@Override
public void onDestroy() {
context.unregisterReceiver(receiver);
}
@Override
public boolean onPackageReceived(NetworkPackage np) {
if (np.getString("action").equals("mute")) {
if (!isMuted) {
AudioManager am = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
am.setStreamVolume(AudioManager.STREAM_RING, AudioManager.ADJUST_MUTE, 0);
} else {
am.setStreamMute(AudioManager.STREAM_RING, true);
}
isMuted = true;
}
}
//Do nothing
return true;
}
private boolean isNumberBlocked(String number) {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
String[] blockedNumbers = sharedPref.getString(KEY_PREF_BLOCKED_NUMBERS, "").split("\n");
- for (String s: blockedNumbers) {
+ for (String s : blockedNumbers) {
if (PhoneNumberUtils.compare(number, s))
return true;
}
return false;
}
@Override
public String[] getSupportedPackageTypes() {
return new String[]{PACKAGE_TYPE_TELEPHONY_REQUEST};
}
@Override
public String[] getOutgoingPackageTypes() {
return new String[]{PACKAGE_TYPE_TELEPHONY};
}
@Override
public String[] getRequiredPermissions() {
return new String[]{Manifest.permission.READ_PHONE_STATE, Manifest.permission.READ_SMS};
}
@Override
public String[] getOptionalPermissions() {
return new String[]{Manifest.permission.READ_CONTACTS};
}
@Override
public boolean hasSettings() {
return true;
}
}
diff --git a/src/org/kde/kdeconnect/UserInterface/AppCompatPreferenceActivity.java b/src/org/kde/kdeconnect/UserInterface/AppCompatPreferenceActivity.java
index d626f3fc..0cd4cc07 100644
--- a/src/org/kde/kdeconnect/UserInterface/AppCompatPreferenceActivity.java
+++ b/src/org/kde/kdeconnect/UserInterface/AppCompatPreferenceActivity.java
@@ -1,141 +1,141 @@
/*
* Copyright (C) 2014 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kde.kdeconnect.UserInterface;
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatDelegate;
import android.support.v7.widget.Toolbar;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import org.kde.kdeconnect.BackgroundService;
/**
* A {@link android.preference.PreferenceActivity} which implements and proxies the necessary calls
* to be used with AppCompat.
- *
+ *
* This technique can be used with an {@link android.app.Activity} class, not just
* {@link android.preference.PreferenceActivity}.
*/
public abstract class AppCompatPreferenceActivity extends PreferenceActivity {
private AppCompatDelegate mDelegate;
@Override
protected void onCreate(Bundle savedInstanceState) {
getDelegate().installViewFactory();
getDelegate().onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
getDelegate().onPostCreate(savedInstanceState);
}
public ActionBar getSupportActionBar() {
return getDelegate().getSupportActionBar();
}
public void setSupportActionBar(@Nullable Toolbar toolbar) {
getDelegate().setSupportActionBar(toolbar);
}
@NonNull
@Override
public MenuInflater getMenuInflater() {
return getDelegate().getMenuInflater();
}
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
@Override
public void setContentView(View view) {
getDelegate().setContentView(view);
}
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().setContentView(view, params);
}
@Override
public void addContentView(View view, ViewGroup.LayoutParams params) {
getDelegate().addContentView(view, params);
}
@Override
protected void onPostResume() {
super.onPostResume();
getDelegate().onPostResume();
}
@Override
protected void onTitleChanged(CharSequence title, int color) {
super.onTitleChanged(title, color);
getDelegate().setTitle(title);
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getDelegate().onConfigurationChanged(newConfig);
}
@Override
protected void onDestroy() {
super.onDestroy();
getDelegate().onDestroy();
}
@Override
public void invalidateOptionsMenu() {
getDelegate().invalidateOptionsMenu();
}
private AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, null);
}
return mDelegate;
}
@Override
protected void onStart() {
super.onStart();
BackgroundService.addGuiInUseCounter(this);
}
@Override
protected void onStop() {
super.onStop();
BackgroundService.removeGuiInUseCounter(this);
getDelegate().onStop();
}
}
diff --git a/src/org/kde/kdeconnect/UserInterface/CustomDevicesActivity.java b/src/org/kde/kdeconnect/UserInterface/CustomDevicesActivity.java
index 1884cbcf..2a85222d 100644
--- a/src/org/kde/kdeconnect/UserInterface/CustomDevicesActivity.java
+++ b/src/org/kde/kdeconnect/UserInterface/CustomDevicesActivity.java
@@ -1,203 +1,203 @@
/*
* Copyright 2014 Achilleas Koutsou
*
* 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.UserInterface;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.os.Build;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v7.app.AppCompatActivity;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect_tp.R;
import java.util.ArrayList;
public class CustomDevicesActivity extends AppCompatActivity {
private static final String LOG_ID = "CustomDevicesActivity";
- public static final String KEY_CUSTOM_DEVLIST_PREFERENCE = "device_list_preference";
+ public static final String KEY_CUSTOM_DEVLIST_PREFERENCE = "device_list_preference";
private static final String IP_DELIM = ",";
private ListView list;
private ArrayList ipAddressList = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initializeDeviceList(this);
setContentView(R.layout.custom_ip_list);
- list = (ListView)findViewById(android.R.id.list);
+ list = (ListView) findViewById(android.R.id.list);
list.setOnItemClickListener(onClickListener);
list.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, ipAddressList));
findViewById(android.R.id.button1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
addNewDevice();
}
});
- EditText ipEntryBox = (EditText)findViewById(R.id.ip_edittext);
+ EditText ipEntryBox = (EditText) findViewById(R.id.ip_edittext);
ipEntryBox.setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_SEND) {
addNewDevice();
return true;
}
return false;
}
});
}
boolean dialogAlreadyShown = false;
private AdapterView.OnItemClickListener onClickListener = new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView> parent, View view, final int position, final long id) {
if (dialogAlreadyShown) {
return;
}
// remove touched item after confirmation
DialogInterface.OnClickListener confirmationListener = new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE:
ipAddressList.remove(position);
saveList();
break;
case DialogInterface.BUTTON_NEGATIVE:
break;
}
}
};
AlertDialog.Builder builder = new AlertDialog.Builder(CustomDevicesActivity.this);
builder.setMessage("Delete " + ipAddressList.get(position) + " ?");
builder.setPositiveButton("Yes", confirmationListener);
builder.setNegativeButton("No", confirmationListener);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { //DismissListener
dialogAlreadyShown = true;
builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
dialogAlreadyShown = false;
}
});
}
builder.show();
}
};
private void addNewDevice() {
- EditText ipEntryBox = (EditText)findViewById(R.id.ip_edittext);
+ EditText ipEntryBox = (EditText) findViewById(R.id.ip_edittext);
String enteredText = ipEntryBox.getText().toString().trim();
if (!enteredText.isEmpty()) {
// don't add empty string (after trimming)
ipAddressList.add(enteredText);
}
saveList();
// clear entry box
ipEntryBox.setText("");
InputMethodManager inputManager = (InputMethodManager)
getSystemService(Context.INPUT_METHOD_SERVICE);
View focus = getCurrentFocus();
if (focus != null) {
inputManager.hideSoftInputFromWindow(focus.getWindowToken(),
InputMethodManager.HIDE_NOT_ALWAYS);
}
}
private void saveList() {
// add entry to list and save to preferences (unless empty)
String serialized = "";
if (!ipAddressList.isEmpty()) {
serialized = serializeIpList(ipAddressList);
}
PreferenceManager.getDefaultSharedPreferences(CustomDevicesActivity.this).edit().putString(
KEY_CUSTOM_DEVLIST_PREFERENCE, serialized).commit();
- ((ArrayAdapter)list.getAdapter()).notifyDataSetChanged();
+ ((ArrayAdapter) list.getAdapter()).notifyDataSetChanged();
}
public static String serializeIpList(ArrayList ipList) {
String serialized = "";
for (String ipAddress : ipList) {
- serialized += IP_DELIM+ipAddress;
+ serialized += IP_DELIM + ipAddress;
}
// remove first delimiter
serialized = serialized.substring(IP_DELIM.length());
return serialized;
}
public static ArrayList deserializeIpList(String serialized) {
ArrayList ipList = new ArrayList<>();
for (String ipAddress : serialized.split(IP_DELIM)) {
ipList.add(ipAddress);
}
return ipList;
}
- private void initializeDeviceList(Context context){
+ private void initializeDeviceList(Context context) {
String deviceListPrefs = PreferenceManager.getDefaultSharedPreferences(context).getString(
KEY_CUSTOM_DEVLIST_PREFERENCE, "");
- if(deviceListPrefs.isEmpty()){
+ if (deviceListPrefs.isEmpty()) {
PreferenceManager.getDefaultSharedPreferences(context).edit().putString(
KEY_CUSTOM_DEVLIST_PREFERENCE,
deviceListPrefs).commit();
} else {
ipAddressList = deserializeIpList(deviceListPrefs);
}
}
@Override
protected void onStart() {
super.onStart();
BackgroundService.addGuiInUseCounter(this);
}
@Override
protected void onStop() {
super.onStop();
BackgroundService.removeGuiInUseCounter(this);
}
}
diff --git a/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java b/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java
index 896cdbcd..568ab61d 100644
--- a/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java
+++ b/src/org/kde/kdeconnect/UserInterface/DeviceFragment.java
@@ -1,542 +1,542 @@
/*
* Copyright 2014 Albert Vaca Cintora
*
* 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.UserInterface;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.NetworkHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.UserInterface.List.CustomItem;
import org.kde.kdeconnect.UserInterface.List.ListAdapter;
import org.kde.kdeconnect.UserInterface.List.PluginItem;
import org.kde.kdeconnect.UserInterface.List.SmallEntryItem;
import org.kde.kdeconnect_tp.R;
import java.util.ArrayList;
import java.util.Collection;
import java.util.ConcurrentModificationException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* Main view. Displays the current device and its plugins
*/
public class DeviceFragment extends Fragment {
static final String ARG_DEVICE_ID = "deviceId";
static final String ARG_FROM_DEVICE_LIST = "fromDeviceList";
View rootView;
static String mDeviceId; //Static because if we get here by using the back button in the action bar, the extra deviceId will not be set.
Device device;
MaterialActivity mActivity;
ArrayList pluginListItems;
public DeviceFragment() {
}
public DeviceFragment(String deviceId, boolean fromDeviceList) {
Bundle args = new Bundle();
args.putString(ARG_DEVICE_ID, deviceId);
args.putBoolean(ARG_FROM_DEVICE_LIST, fromDeviceList);
this.setArguments(args);
}
public DeviceFragment(String deviceId, MaterialActivity activity) {
this.mActivity = activity;
Bundle args = new Bundle();
args.putString(ARG_DEVICE_ID, deviceId);
this.setArguments(args);
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
mActivity = ((MaterialActivity) getActivity());
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
rootView = inflater.inflate(R.layout.activity_device, container, false);
final String deviceId = getArguments().getString(ARG_DEVICE_ID);
if (deviceId != null) {
mDeviceId = deviceId;
}
setHasOptionsMenu(true);
//Log.e("DeviceFragment", "device: " + deviceId);
BackgroundService.RunCommand(mActivity, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
device = service.getDevice(mDeviceId);
if (device == null) {
Log.e("DeviceFragment", "Trying to display a device fragment but the device is not present");
mActivity.onDeviceSelected(null);
return;
}
mActivity.getSupportActionBar().setTitle(device.getName());
device.addPairingCallback(pairingCallback);
device.addPluginsChangedListener(pluginsChangedListener);
refreshUI();
}
});
final Button pairButton = (Button) rootView.findViewById(R.id.pair_button);
pairButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
pairButton.setVisibility(View.GONE);
((TextView) rootView.findViewById(R.id.pair_message)).setText("");
rootView.findViewById(R.id.pair_progress).setVisibility(View.VISIBLE);
BackgroundService.RunCommand(mActivity, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
device = service.getDevice(deviceId);
if (device == null) return;
device.requestPairing();
}
});
}
});
rootView.findViewById(R.id.accept_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
BackgroundService.RunCommand(mActivity, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
if (device != null) {
device.acceptPairing();
rootView.findViewById(R.id.pairing_buttons).setVisibility(View.GONE);
}
}
});
}
});
rootView.findViewById(R.id.reject_button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
BackgroundService.RunCommand(mActivity, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
if (device != null) {
//Remove listener so buttons don't show for a while before changing the view
device.removePluginsChangedListener(pluginsChangedListener);
device.removePairingCallback(pairingCallback);
device.rejectPairing();
}
mActivity.onDeviceSelected(null);
}
});
}
});
return rootView;
}
final Device.PluginsChangedListener pluginsChangedListener = new Device.PluginsChangedListener() {
@Override
public void onPluginsChanged(final Device device) {
refreshUI();
}
};
@Override
public void onDestroyView() {
BackgroundService.RunCommand(mActivity, new BackgroundService.InstanceCallback() {
@Override
public void onServiceStart(BackgroundService service) {
Device device = service.getDevice(mDeviceId);
if (device == null) return;
device.removePluginsChangedListener(pluginsChangedListener);
device.removePairingCallback(pairingCallback);
}
});
super.onDestroyView();
}
@Override
public void onPrepareOptionsMenu(Menu menu) {
//Log.e("DeviceFragment", "onPrepareOptionsMenu");
super.onPrepareOptionsMenu(menu);
menu.clear();
if (device == null) {
return;
}
//Plugins button list
final Collection plugins = device.getLoadedPlugins().values();
for (final Plugin p : plugins) {
if (!p.displayInContextMenu()) {
continue;
}
menu.add(p.getActionName()).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
p.startMainActivity(mActivity);
return true;
}
});
}
menu.add(R.string.device_menu_plugins).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
Intent intent = new Intent(mActivity, SettingsActivity.class);
intent.putExtra("deviceId", mDeviceId);
startActivity(intent);
return true;
}
});
if (device.isReachable()) {
menu.add(R.string.encryption_info_title).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
Context context = mActivity;
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setTitle(context.getResources().getString(R.string.encryption_info_title));
builder.setPositiveButton(context.getResources().getString(R.string.ok), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.dismiss();
}
});
if (device.certificate == null) {
builder.setMessage(R.string.encryption_info_msg_no_ssl);
} else {
builder.setMessage(context.getResources().getString(R.string.my_device_fingerprint) + "\n" + SslHelper.getCertificateHash(SslHelper.certificate) + "\n\n"
+ context.getResources().getString(R.string.remote_device_fingerprint) + "\n" + SslHelper.getCertificateHash(device.certificate));
}
builder.create().show();
return true;
}
});
}
if (device.isPaired()) {
menu.add(R.string.device_menu_unpair).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
//Remove listener so buttons don't show for a while before changing the view
device.removePluginsChangedListener(pluginsChangedListener);
device.removePairingCallback(pairingCallback);
device.unpair();
mActivity.onDeviceSelected(null);
return true;
}
});
}
}
@Override
public void onResume() {
super.onResume();
getView().setFocusableInTouchMode(true);
getView().requestFocus();
getView().setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_BACK) {
boolean fromDeviceList = getArguments().getBoolean(ARG_FROM_DEVICE_LIST, false);
// Handle back button so we go to the list of devices in case we came from there
if (fromDeviceList) {
mActivity.onDeviceSelected(null);
return true;
}
}
return false;
}
});
}
void refreshUI() {
//Log.e("DeviceFragment", "refreshUI");
if (device == null || rootView == null) {
return;
}
//Once in-app, there is no point in keep displaying the notification if any
device.hidePairingNotification();
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (device.isPairRequestedByPeer()) {
((TextView) rootView.findViewById(R.id.pair_message)).setText(R.string.pair_requested);
rootView.findViewById(R.id.pair_progress).setVisibility(View.GONE);
rootView.findViewById(R.id.pair_button).setVisibility(View.GONE);
rootView.findViewById(R.id.pair_request).setVisibility(View.VISIBLE);
} else {
boolean paired = device.isPaired();
boolean reachable = device.isReachable();
boolean onData = NetworkHelper.isOnMobileNetwork(getContext());
rootView.findViewById(R.id.pairing_buttons).setVisibility(paired ? View.GONE : View.VISIBLE);
rootView.findViewById(R.id.not_reachable_message).setVisibility((paired && !reachable && !onData) ? View.VISIBLE : View.GONE);
rootView.findViewById(R.id.on_data_message).setVisibility((paired && !reachable && onData) ? View.VISIBLE : View.GONE);
try {
pluginListItems = new ArrayList<>();
//Plugins button list
final Collection plugins = device.getLoadedPlugins().values();
for (final Plugin p : plugins) {
if (!p.hasMainActivity()) continue;
if (p.displayInContextMenu()) continue;
pluginListItems.add(new PluginItem(p, new View.OnClickListener() {
@Override
public void onClick(View v) {
p.startMainActivity(mActivity);
}
}));
}
createPluginsList(device.getFailedPlugins(), R.string.plugins_failed_to_load, new PluginClickListener() {
@Override
void action() {
plugin.getErrorDialog(mActivity).show();
}
});
createPluginsList(device.getPluginsWithoutPermissions(), R.string.plugins_need_permission, new PluginClickListener() {
@Override
void action() {
plugin.getPermissionExplanationDialog(mActivity).show();
}
});
createPluginsList(device.getPluginsWithoutOptionalPermissions(), R.string.plugins_need_optional_permission, new PluginClickListener() {
@Override
void action() {
plugin.getOptionalPermissionExplanationDialog(mActivity).show();
}
});
ListView buttonsList = (ListView) rootView.findViewById(R.id.buttons_list);
ListAdapter adapter = new ListAdapter(mActivity, pluginListItems);
buttonsList.setAdapter(adapter);
mActivity.invalidateOptionsMenu();
} catch (IllegalStateException e) {
e.printStackTrace();
//Ignore: The activity was closed while we were trying to update it
} catch (ConcurrentModificationException e) {
Log.e("DeviceActivity", "ConcurrentModificationException");
this.run(); //Try again
}
}
}
});
}
final Device.PairingCallback pairingCallback = new Device.PairingCallback() {
@Override
public void incomingRequest() {
refreshUI();
}
@Override
public void pairingSuccessful() {
refreshUI();
}
@Override
public void pairingFailed(final String error) {
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (rootView == null) return;
((TextView) rootView.findViewById(R.id.pair_message)).setText(error);
rootView.findViewById(R.id.pair_progress).setVisibility(View.GONE);
rootView.findViewById(R.id.pair_button).setVisibility(View.VISIBLE);
rootView.findViewById(R.id.pair_request).setVisibility(View.GONE);
refreshUI();
}
});
}
@Override
public void unpaired() {
mActivity.runOnUiThread(new Runnable() {
@Override
public void run() {
if (rootView == null) return;
((TextView) rootView.findViewById(R.id.pair_message)).setText(R.string.device_not_paired);
rootView.findViewById(R.id.pair_progress).setVisibility(View.GONE);
rootView.findViewById(R.id.pair_button).setVisibility(View.VISIBLE);
rootView.findViewById(R.id.pair_request).setVisibility(View.GONE);
refreshUI();
}
});
}
};
public static void acceptPairing(final String devId, final MaterialActivity activity) {
final DeviceFragment frag = new DeviceFragment(devId, activity);
BackgroundService.RunCommand(activity, new BackgroundService.InstanceCallback() {
public void onServiceStart(BackgroundService service) {
Device dev = service.getDevice(devId);
if (dev == null) {
- Log.w("rejectPairing", "Device no longer exists: "+devId);
+ Log.w("rejectPairing", "Device no longer exists: " + devId);
return;
}
activity.getSupportActionBar().setTitle(dev.getName());
dev.addPairingCallback(frag.pairingCallback);
dev.addPluginsChangedListener(frag.pluginsChangedListener);
frag.device = dev;
frag.device.acceptPairing();
frag.refreshUI();
}
});
}
public static void rejectPairing(final String devId, final MaterialActivity activity) {
final DeviceFragment frag = new DeviceFragment(devId, activity);
BackgroundService.RunCommand(activity, new BackgroundService.InstanceCallback() {
public void onServiceStart(BackgroundService service) {
Device dev = service.getDevice(devId);
if (dev == null) {
- Log.w("rejectPairing", "Device no longer exists: "+devId);
+ Log.w("rejectPairing", "Device no longer exists: " + devId);
return;
}
activity.getSupportActionBar().setTitle(dev.getName());
dev.addPairingCallback(frag.pairingCallback);
dev.addPluginsChangedListener(frag.pluginsChangedListener);
frag.device = dev;
//Remove listener so buttons don't show for a while before changing the view
frag.device.removePluginsChangedListener(frag.pluginsChangedListener);
frag.device.removePairingCallback(frag.pairingCallback);
frag.device.rejectPairing();
activity.onDeviceSelected(null);
frag.refreshUI();
}
});
}
void createPluginsList(ConcurrentHashMap plugins, int headerText, PluginClickListener onClickListener) {
if (!plugins.isEmpty()) {
TextView header = new TextView(mActivity);
header.setPadding(
0,
((int) (28 * getResources().getDisplayMetrics().density)),
0,
((int) (8 * getResources().getDisplayMetrics().density))
);
header.setOnClickListener(null);
header.setOnLongClickListener(null);
header.setText(headerText);
pluginListItems.add(new CustomItem(header));
for (Map.Entry entry : plugins.entrySet()) {
String pluginKey = entry.getKey();
final Plugin plugin = entry.getValue();
if (device.isPluginEnabled(pluginKey)) {
if (plugin == null) {
pluginListItems.add(new SmallEntryItem(pluginKey));
} else {
PluginClickListener listener = onClickListener.clone();
listener.plugin = plugin;
pluginListItems.add(new SmallEntryItem(plugin.getDisplayName(), listener));
}
}
}
}
}
private abstract class PluginClickListener implements View.OnClickListener, Cloneable {
Plugin plugin;
@Override
public void onClick(View v) {
action();
}
@Override
- public PluginClickListener clone(){
+ public PluginClickListener clone() {
try {
return (PluginClickListener) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
abstract void action();
}
}
diff --git a/src/org/kde/kdeconnect/UserInterface/List/CustomItem.java b/src/org/kde/kdeconnect/UserInterface/List/CustomItem.java
index 927cac3d..00ecf3a6 100644
--- a/src/org/kde/kdeconnect/UserInterface/List/CustomItem.java
+++ b/src/org/kde/kdeconnect/UserInterface/List/CustomItem.java
@@ -1,39 +1,39 @@
/*
* Copyright 2014 Albert Vaca Cintora
*
* 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.UserInterface.List;
import android.view.LayoutInflater;
import android.view.View;
public class CustomItem implements ListAdapter.Item {
- private final View view;
+ private final View view;
- public CustomItem(View v) {
+ public CustomItem(View v) {
this.view = v;
- }
+ }
@Override
public View inflateView(LayoutInflater layoutInflater) {
return view;
}
}
diff --git a/src/org/kde/kdeconnect/UserInterface/List/EntryItem.java b/src/org/kde/kdeconnect/UserInterface/List/EntryItem.java
index 4ceaf72a..16b10629 100644
--- a/src/org/kde/kdeconnect/UserInterface/List/EntryItem.java
+++ b/src/org/kde/kdeconnect/UserInterface/List/EntryItem.java
@@ -1,62 +1,62 @@
/*
* Copyright 2014 Albert Vaca Cintora
*
* 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.UserInterface.List;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import org.kde.kdeconnect_tp.R;
public class EntryItem implements ListAdapter.Item {
- protected final String title;
- protected final String subtitle;
+ protected final String title;
+ protected final String subtitle;
public EntryItem(String title) {
this.title = title;
this.subtitle = null;
}
public EntryItem(String title, String subtitle) {
- this.title = title;
+ this.title = title;
this.subtitle = subtitle;
- }
+ }
@Override
public View inflateView(LayoutInflater layoutInflater) {
View v = layoutInflater.inflate(R.layout.list_item_entry, null);
- TextView titleView = (TextView)v.findViewById(R.id.list_item_entry_title);
+ TextView titleView = (TextView) v.findViewById(R.id.list_item_entry_title);
if (titleView != null) titleView.setText(title);
if (subtitle != null) {
TextView subtitleView = (TextView) v.findViewById(R.id.list_item_entry_summary);
if (subtitleView != null) {
subtitleView.setVisibility(View.VISIBLE);
subtitleView.setText(subtitle);
}
}
return v;
}
}
diff --git a/src/org/kde/kdeconnect/UserInterface/List/ListAdapter.java b/src/org/kde/kdeconnect/UserInterface/List/ListAdapter.java
index 9603997d..0b1020a6 100644
--- a/src/org/kde/kdeconnect/UserInterface/List/ListAdapter.java
+++ b/src/org/kde/kdeconnect/UserInterface/List/ListAdapter.java
@@ -1,53 +1,53 @@
/*
* Copyright 2014 Albert Vaca Cintora
*
* 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.UserInterface.List;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import java.util.ArrayList;
public class ListAdapter extends ArrayAdapter {
public interface Item {
View inflateView(LayoutInflater layoutInflater);
}
- private final ArrayList- items;
- private final LayoutInflater layoutInflater;
+ private final ArrayList
- items;
+ private final LayoutInflater layoutInflater;
- public ListAdapter(Context context, ArrayList
- items) {
- super(context, 0, items);
+ public ListAdapter(Context context, ArrayList
- items) {
+ super(context, 0, items);
this.items = items;
- layoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- }
+ layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+ }
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- final Item i = items.get(position);
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ final Item i = items.get(position);
return i.inflateView(layoutInflater);
}
}
diff --git a/src/org/kde/kdeconnect/UserInterface/List/PairingDeviceItem.java b/src/org/kde/kdeconnect/UserInterface/List/PairingDeviceItem.java
index 4e4c7581..2cb77a8b 100644
--- a/src/org/kde/kdeconnect/UserInterface/List/PairingDeviceItem.java
+++ b/src/org/kde/kdeconnect/UserInterface/List/PairingDeviceItem.java
@@ -1,87 +1,87 @@
/*
* Copyright 2014 Albert Vaca Cintora
*
* 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.UserInterface.List;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect_tp.R;
public class PairingDeviceItem implements ListAdapter.Item {
public interface Callback {
void pairingClicked(Device d);
}
private final Callback callback;
private final Device device;
public PairingDeviceItem(Device device, Callback callback) {
this.device = device;
this.callback = callback;
}
public Device getDevice() {
return this.device;
}
@Override
public View inflateView(LayoutInflater layoutInflater) {
final View v = layoutInflater.inflate(R.layout.list_item_with_icon_entry, null);
ImageView icon = (ImageView) v.findViewById(R.id.list_item_entry_icon);
icon.setImageDrawable(device.getIcon());
TextView titleView = (TextView) v.findViewById(R.id.list_item_entry_title);
titleView.setText(device.getName());
if (device.compareProtocolVersion() != 0) {
- TextView summaryView = (TextView)v.findViewById(R.id.list_item_entry_summary);
+ TextView summaryView = (TextView) v.findViewById(R.id.list_item_entry_summary);
if (device.compareProtocolVersion() > 0) {
summaryView.setText(R.string.protocol_version_newer);
summaryView.setVisibility(View.VISIBLE);
} else {
//FIXME: Uncoment when we decide old versions are old enough to notify the user.
summaryView.setVisibility(View.GONE);
/*
summaryView.setText(R.string.protocol_version_older);
summaryView.setVisibility(View.VISIBLE);
*/
}
} else {
v.findViewById(R.id.list_item_entry_summary).setVisibility(View.GONE);
}
v.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
callback.pairingClicked(device);
}
});
return v;
}
}
diff --git a/src/org/kde/kdeconnect/UserInterface/List/PluginItem.java b/src/org/kde/kdeconnect/UserInterface/List/PluginItem.java
index a9d83c5b..515e3d8d 100644
--- a/src/org/kde/kdeconnect/UserInterface/List/PluginItem.java
+++ b/src/org/kde/kdeconnect/UserInterface/List/PluginItem.java
@@ -1,57 +1,57 @@
/*
* Copyright 2014 Albert Vaca Cintora
*
* 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.UserInterface.List;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect_tp.R;
public class PluginItem implements ListAdapter.Item {
- private final Plugin plugin;
+ private final Plugin plugin;
private final View.OnClickListener clickListener;
- public PluginItem(Plugin p, View.OnClickListener clickListener) {
- this.plugin = p;
+ public PluginItem(Plugin p, View.OnClickListener clickListener) {
+ this.plugin = p;
this.clickListener = clickListener;
- }
+ }
@Override
public View inflateView(final LayoutInflater layoutInflater) {
View v = layoutInflater.inflate(R.layout.list_item_with_icon_entry, null);
- TextView titleView = (TextView)v.findViewById(R.id.list_item_entry_title);
+ TextView titleView = (TextView) v.findViewById(R.id.list_item_entry_title);
titleView.setText(plugin.getActionName());
- ImageView imageView = (ImageView)v.findViewById(R.id.list_item_entry_icon);
+ ImageView imageView = (ImageView) v.findViewById(R.id.list_item_entry_icon);
imageView.setImageDrawable(plugin.getIcon());
v.setOnClickListener(clickListener);
return v;
}
}
diff --git a/src/org/kde/kdeconnect/UserInterface/List/SectionItem.java b/src/org/kde/kdeconnect/UserInterface/List/SectionItem.java
index a592b48e..83e059bb 100644
--- a/src/org/kde/kdeconnect/UserInterface/List/SectionItem.java
+++ b/src/org/kde/kdeconnect/UserInterface/List/SectionItem.java
@@ -1,58 +1,58 @@
/*
* Copyright 2014 Albert Vaca Cintora
*
* 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.UserInterface.List;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import org.kde.kdeconnect_tp.R;
public class SectionItem implements ListAdapter.Item {
- private final String title;
+ private final String title;
public boolean isSectionEmpty;
- public SectionItem(String title) {
- this.title = title;
+ public SectionItem(String title) {
+ this.title = title;
this.isSectionEmpty = false;
- }
+ }
@Override
public View inflateView(LayoutInflater layoutInflater) {
View v = layoutInflater.inflate(R.layout.list_item_category, null);
//Make it not selectable
v.setOnClickListener(null);
v.setOnLongClickListener(null);
TextView sectionView = (TextView) v.findViewById(R.id.list_item_category_text);
sectionView.setText(title);
if (isSectionEmpty) {
v.findViewById(R.id.list_item_category_empty_placeholder).setVisibility(View.VISIBLE);
}
return v;
}
}
diff --git a/src/org/kde/kdeconnect/UserInterface/List/SmallEntryItem.java b/src/org/kde/kdeconnect/UserInterface/List/SmallEntryItem.java
index b3f6670f..f8538943 100644
--- a/src/org/kde/kdeconnect/UserInterface/List/SmallEntryItem.java
+++ b/src/org/kde/kdeconnect/UserInterface/List/SmallEntryItem.java
@@ -1,61 +1,61 @@
/*
* Copyright 2014 Albert Vaca Cintora
*
* 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.UserInterface.List;
import android.support.v4.content.ContextCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import org.kde.kdeconnect_tp.R;
public class SmallEntryItem implements ListAdapter.Item {
- private final String title;
+ private final String title;
private final View.OnClickListener clickListener;
- public SmallEntryItem(String title) {
- this.title = title;
+ public SmallEntryItem(String title) {
+ this.title = title;
this.clickListener = null;
- }
+ }
public SmallEntryItem(String title, View.OnClickListener clickListener) {
this.title = title;
this.clickListener = clickListener;
}
@Override
public View inflateView(LayoutInflater layoutInflater) {
View v = layoutInflater.inflate(android.R.layout.simple_list_item_1, null);
- TextView titleView = (TextView)v.findViewById(android.R.id.text1);
+ TextView titleView = (TextView) v.findViewById(android.R.id.text1);
if (titleView != null) {
titleView.setText(title);
if (clickListener != null) {
titleView.setOnClickListener(clickListener);
v.setBackgroundDrawable(ContextCompat.getDrawable(layoutInflater.getContext(), R.drawable.abc_list_selector_holo_dark));
}
}
return v;
}
}
diff --git a/src/org/kde/kdeconnect/UserInterface/MaterialActivity.java b/src/org/kde/kdeconnect/UserInterface/MaterialActivity.java
index e3673b64..d5f28f67 100644
--- a/src/org/kde/kdeconnect/UserInterface/MaterialActivity.java
+++ b/src/org/kde/kdeconnect/UserInterface/MaterialActivity.java
@@ -1,349 +1,349 @@
package org.kde.kdeconnect.UserInterface;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.NavigationView;
import android.support.v4.app.Fragment;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.kde.kdeconnect_tp.R;
import java.util.Collection;
import java.util.HashMap;
public class MaterialActivity extends AppCompatActivity {
private static final String STATE_SELECTED_DEVICE = "selected_device";
public static final int RESULT_NEEDS_RELOAD = Activity.RESULT_FIRST_USER;
public static final String PAIR_REQUEST_STATUS = "pair_req_status";
public static final String PAIRING_ACCEPTED = "accepted";
public static final String PAIRING_REJECTED = "rejected";
private NavigationView mNavigationView;
private DrawerLayout mDrawerLayout;
private String mCurrentDevice;
private SharedPreferences preferences;
private final HashMap