mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-09-02 07:05:09 +00:00
Compare commits
19 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
64fd08f3ac | ||
|
e67e43efa1 | ||
|
fda08929af | ||
|
2cb025e368 | ||
|
cf808c03ba | ||
|
2b52cd1547 | ||
|
3d4bf643d4 | ||
|
00b6677aa4 | ||
|
bf0cab9ef2 | ||
|
1c3e6f84a7 | ||
|
c8dbbb1fe8 | ||
|
a17b75264d | ||
|
7276e60aa4 | ||
|
7877d2803c | ||
|
b1c4b6e1e9 | ||
|
eb801fa535 | ||
|
ac4aaf1b39 | ||
|
9840a39992 | ||
|
e73c18d2e3 |
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.kde.kdeconnect_tp"
|
||||
android:versionCode="1800"
|
||||
android:versionName="1.8.0">
|
||||
android:versionCode="1820"
|
||||
android:versionName="1.8.2">
|
||||
|
||||
<supports-screens
|
||||
android:anyDensity="true"
|
||||
@@ -146,7 +146,8 @@
|
||||
<activity
|
||||
android:name="org.kde.kdeconnect.Plugins.MprisPlugin.MprisActivity"
|
||||
android:label="@string/remote_control"
|
||||
android:parentActivityName="org.kde.kdeconnect.UserInterface.MainActivity">
|
||||
android:parentActivityName="org.kde.kdeconnect.UserInterface.MainActivity"
|
||||
android:launchMode="singleTop">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value="org.kde.kdeconnect.UserInterface.MainActivity" />
|
||||
|
10
build.gradle
10
build.gradle
@@ -1,10 +1,7 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
maven {
|
||||
url 'https://maven.google.com/'
|
||||
name 'Google'
|
||||
}
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.0.1'
|
||||
@@ -73,10 +70,7 @@ dependencies {
|
||||
|
||||
repositories {
|
||||
jcenter()
|
||||
maven {
|
||||
url 'https://maven.google.com/'
|
||||
name 'Google'
|
||||
}
|
||||
google()
|
||||
}
|
||||
|
||||
implementation 'com.android.support:support-v4:25.4.0'
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:width="192dp"
|
||||
android:height="192dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
|
@@ -11,6 +11,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:visibility="gone"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ProgressBar
|
||||
|
@@ -82,8 +82,6 @@
|
||||
<string name="incoming_file_text">%1s</string>
|
||||
<string name="outgoing_file_title">Шаљем фајл на %1s</string>
|
||||
<string name="outgoing_files_title">Шаљем фајлове на %1s</string>
|
||||
<string name="outgoing_file_text">%1s</string>
|
||||
<string name="outgoing_files_text">Послато %1$d од %2$d фајлова</string>
|
||||
<string name="received_file_title">Примљен фајл са %1s</string>
|
||||
<string name="received_file_fail_title">Неуспео пријем фајла са %1s</string>
|
||||
<string name="received_file_text">Тапните да отворите „%1s“</string>
|
||||
|
@@ -15,7 +15,7 @@
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.kde.kdeconnect.Backends.LanBackend;
|
||||
@@ -27,6 +27,7 @@ import android.preference.PreferenceManager;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import org.kde.kdeconnect.Backends.BaseLink;
|
||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||
import org.kde.kdeconnect.BackgroundService;
|
||||
import org.kde.kdeconnect.Device;
|
||||
@@ -59,6 +60,13 @@ import javax.net.ssl.HandshakeCompletedEvent;
|
||||
import javax.net.ssl.HandshakeCompletedListener;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
/**
|
||||
* This BaseLinkProvider creates {@link LanLink}s to other devices on the same
|
||||
* WiFi network. The first packet sent over a socket must be an
|
||||
* {@link NetworkPacket#createIdentityPacket(Context)}.
|
||||
*
|
||||
* @see #identityPacketReceived(NetworkPacket, Socket, LanLink.ConnectionStarted)
|
||||
*/
|
||||
public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDisconnectedCallback {
|
||||
|
||||
public static final int MIN_VERSION_WITH_SSL_SUPPORT = 6;
|
||||
@@ -157,14 +165,14 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
Log.e("KDE/LanLinkProvider", "Cannot connect to " + address);
|
||||
e.printStackTrace();
|
||||
if (!reverseConnectionBlackList.contains(address)) {
|
||||
Log.w("KDE/LanLinkProvider","Blacklisting "+address);
|
||||
Log.w("KDE/LanLinkProvider", "Blacklisting " + address);
|
||||
reverseConnectionBlackList.add(address);
|
||||
new Timer().schedule(new TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
reverseConnectionBlackList.remove(address);
|
||||
}
|
||||
}, 5*1000);
|
||||
}, 5 * 1000);
|
||||
|
||||
// Try to cause a reverse connection
|
||||
onNetworkChange();
|
||||
@@ -180,6 +188,18 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when a new 'identity' packet is received. Those are passed here by
|
||||
* {@link #tcpPacketReceived(Socket)} and {@link #udpPacketReceived(DatagramPacket)}.
|
||||
* <p>
|
||||
* If the remote device should be connected, this calls {@link #addLink}.
|
||||
* Otherwise, if there was an Exception, we unpair from that device.
|
||||
* </p>
|
||||
*
|
||||
* @param identityPacket identity of a remote device
|
||||
* @param socket a new Socket, which should be used to receive packets from the remote device
|
||||
* @param connectionStarted which side started this connection
|
||||
*/
|
||||
private void identityPacketReceived(final NetworkPacket identityPacket, final Socket socket, final LanLink.ConnectionStarted connectionStarted) {
|
||||
|
||||
String myId = DeviceHelper.getDeviceId(context);
|
||||
@@ -213,20 +233,20 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
});
|
||||
}
|
||||
|
||||
Log.i("KDE/LanLinkProvider","Starting SSL handshake with " + identityPacket.getString("deviceName") + " trusted:"+isDeviceTrusted);
|
||||
Log.i("KDE/LanLinkProvider", "Starting SSL handshake with " + identityPacket.getString("deviceName") + " trusted:" + isDeviceTrusted);
|
||||
|
||||
final SSLSocket sslsocket = SslHelper.convertToSslSocket(context, socket, deviceId, isDeviceTrusted, clientMode);
|
||||
sslsocket.addHandshakeCompletedListener(new HandshakeCompletedListener() {
|
||||
@Override
|
||||
public void handshakeCompleted(HandshakeCompletedEvent event) {
|
||||
String mode = clientMode? "client" : "server";
|
||||
String mode = clientMode ? "client" : "server";
|
||||
try {
|
||||
Certificate certificate = event.getPeerCertificates()[0];
|
||||
identityPacket.set("certificate", Base64.encodeToString(certificate.getEncoded(), 0));
|
||||
Log.i("KDE/LanLinkProvider","Handshake as " + mode + " successful with " + identityPacket.getString("deviceName") + " secured with " + event.getCipherSuite());
|
||||
Log.i("KDE/LanLinkProvider", "Handshake as " + mode + " successful with " + identityPacket.getString("deviceName") + " secured with " + event.getCipherSuite());
|
||||
addLink(identityPacket, sslsocket, connectionStarted);
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/LanLinkProvider","Handshake as " + mode + " failed with " + identityPacket.getString("deviceName"));
|
||||
Log.e("KDE/LanLinkProvider", "Handshake as " + mode + " failed with " + identityPacket.getString("deviceName"));
|
||||
e.printStackTrace();
|
||||
BackgroundService.RunCommand(context, new BackgroundService.InstanceCallback() {
|
||||
@Override
|
||||
@@ -246,7 +266,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
try {
|
||||
sslsocket.startHandshake();
|
||||
} catch (Exception e) {
|
||||
Log.e("KDE/LanLinkProvider","Handshake failed with " + identityPacket.getString("deviceName"));
|
||||
Log.e("KDE/LanLinkProvider", "Handshake failed with " + identityPacket.getString("deviceName"));
|
||||
e.printStackTrace();
|
||||
|
||||
//String[] ciphers = sslsocket.getSupportedCipherSuites();
|
||||
@@ -265,6 +285,19 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or update a link in the {@link #visibleComputers} map. This method is synchronized, which ensures that only one
|
||||
* link is operated on at a time.
|
||||
* <p>
|
||||
* Without synchronization, the call to {@link SslHelper#parseCertificate(byte[])} in
|
||||
* {@link Device#addLink(NetworkPacket, BaseLink)} crashes on some devices running Oreo 8.1 (SDK level 27).
|
||||
* </p>
|
||||
*
|
||||
* @param identityPacket representation of remote device
|
||||
* @param socket a new Socket, which should be used to receive packets from the remote device
|
||||
* @param connectionOrigin which side started this connection
|
||||
* @throws IOException if an exception is thrown by {@link LanLink#reset(Socket, LanLink.ConnectionStarted)}
|
||||
*/
|
||||
private synchronized void addLink(final NetworkPacket identityPacket, Socket socket, LanLink.ConnectionStarted connectionOrigin) throws IOException {
|
||||
|
||||
String deviceId = identityPacket.getString("deviceId");
|
||||
@@ -313,7 +346,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
Log.e("LanLinkProvider", "UdpReceive exception");
|
||||
}
|
||||
}
|
||||
Log.w("UdpListener","Stopping UDP listener");
|
||||
Log.w("UdpListener", "Stopping UDP listener");
|
||||
}
|
||||
}).start();
|
||||
return server;
|
||||
@@ -347,13 +380,13 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
|
||||
static ServerSocket openServerSocketOnFreePort(int minPort) throws IOException {
|
||||
int tcpPort = minPort;
|
||||
while(tcpPort < MAX_PORT) {
|
||||
while (tcpPort < MAX_PORT) {
|
||||
try {
|
||||
ServerSocket candidateServer = new ServerSocket();
|
||||
candidateServer.bind(new InetSocketAddress(tcpPort));
|
||||
Log.i("KDE/LanLink", "Using port "+tcpPort);
|
||||
Log.i("KDE/LanLink", "Using port " + tcpPort);
|
||||
return candidateServer;
|
||||
} catch(IOException e) {
|
||||
} catch (IOException e) {
|
||||
tcpPort++;
|
||||
}
|
||||
}
|
||||
@@ -390,7 +423,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
bytes = identity.serialize().getBytes(StringsHelper.UTF8);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e("KDE/LanLinkProvider","Failed to create DatagramSocket");
|
||||
Log.e("KDE/LanLinkProvider", "Failed to create DatagramSocket");
|
||||
}
|
||||
|
||||
if (bytes != null) {
|
||||
@@ -415,6 +448,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
}
|
||||
}).start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
//Log.i("KDE/LanLinkProvider", "onStart");
|
||||
@@ -431,7 +465,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
// and newer android versions. Although devices with android version less than ICS cannot connect to other devices who also have android version less
|
||||
// than ICS because server is disabled on both
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
|
||||
Log.w("KDE/LanLinkProvider","Not starting a TCP server because it's not supported on Android < 14. Operating only as client.");
|
||||
Log.w("KDE/LanLinkProvider", "Not starting a TCP server because it's not supported on Android < 14. Operating only as client.");
|
||||
} else {
|
||||
setupTcpListener();
|
||||
}
|
||||
@@ -451,17 +485,17 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
|
||||
listening = false;
|
||||
try {
|
||||
tcpServer.close();
|
||||
} catch (Exception e){
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
udpServer.close();
|
||||
} catch (Exception e){
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
udpServerOldPort.close();
|
||||
} catch (Exception e){
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
@@ -66,6 +66,10 @@ public final class AlbumArtCache {
|
||||
* A list of urls yet to be fetched.
|
||||
*/
|
||||
private static final ArrayList<URL> fetchUrlList = new ArrayList<>();
|
||||
/**
|
||||
* A list of urls currently being fetched
|
||||
*/
|
||||
private static final ArrayList<URL> isFetchingList = new ArrayList<>();
|
||||
/**
|
||||
* A integer indicating how many fetches are in progress.
|
||||
*/
|
||||
@@ -123,7 +127,7 @@ public final class AlbumArtCache {
|
||||
* @param albumUrl The album art url
|
||||
* @return A bitmap for the album art. Can be null if not (yet) found
|
||||
*/
|
||||
public static Bitmap getAlbumArt(String albumUrl) {
|
||||
public static Bitmap getAlbumArt(String albumUrl, MprisPlugin plugin, String player) {
|
||||
//If the url is invalid, return "no album art"
|
||||
if (albumUrl == null || albumUrl.isEmpty()) {
|
||||
return null;
|
||||
@@ -138,8 +142,8 @@ public final class AlbumArtCache {
|
||||
return null;
|
||||
}
|
||||
|
||||
//We currently only support http(s) urls
|
||||
if (!url.getProtocol().equals("http") && !url.getProtocol().equals("https")) {
|
||||
//We currently only support http(s) and file urls
|
||||
if (!url.getProtocol().equals("http") && !url.getProtocol().equals("https") && !url.getProtocol().equals("file")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -163,11 +167,7 @@ public final class AlbumArtCache {
|
||||
try {
|
||||
DiskLruCache.Snapshot item = diskCache.get(urlToDiskCacheKey(albumUrl));
|
||||
if (item != null) {
|
||||
BitmapFactory.Options decodeOptions = new BitmapFactory.Options();
|
||||
decodeOptions.inScaled = false;
|
||||
decodeOptions.inDensity = 1;
|
||||
decodeOptions.inTargetDensity = 1;
|
||||
Bitmap result = BitmapFactory.decodeStream(item.getInputStream(0), null, decodeOptions);
|
||||
Bitmap result = BitmapFactory.decodeStream(item.getInputStream(0));
|
||||
item.close();
|
||||
MemoryCacheItem memItem = new MemoryCacheItem();
|
||||
if (result != null) {
|
||||
@@ -189,7 +189,20 @@ public final class AlbumArtCache {
|
||||
|
||||
/* If not found, we have not tried fetching it (recently), or a fetch is in-progress.
|
||||
Either way, just add it to the fetch queue and starting fetching it if no fetch is running. */
|
||||
fetchUrl(url);
|
||||
if ("file".equals(url.getProtocol())) {
|
||||
//Special-case file, since we need to fetch it from the remote
|
||||
if (isFetchingList.contains(url)) return null;
|
||||
|
||||
if (!plugin.askTransferAlbumArt(albumUrl, player)) {
|
||||
//It doesn't support transferring the art, so mark it as failed in the memory cache
|
||||
MemoryCacheItem cacheItem = new MemoryCacheItem();
|
||||
cacheItem.failedFetch = true;
|
||||
cacheItem.albumArt = null;
|
||||
memoryCache.put(url.toString(), cacheItem);
|
||||
}
|
||||
} else {
|
||||
fetchUrl(url);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -206,7 +219,7 @@ public final class AlbumArtCache {
|
||||
}
|
||||
|
||||
//Only fetch an URL if we're not fetching it already
|
||||
if (fetchUrlList.contains(url)) {
|
||||
if (fetchUrlList.contains(url) || isFetchingList.contains(url)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -323,8 +336,8 @@ public final class AlbumArtCache {
|
||||
memoryCache.put(url.toString(), cacheItem);
|
||||
}
|
||||
|
||||
//Remove the url from the to-fetch list
|
||||
fetchUrlList.remove(url);
|
||||
//Remove the url from the fetching list
|
||||
isFetchingList.remove(url);
|
||||
//Fetch the next url (if any)
|
||||
--numFetching;
|
||||
initiateFetch();
|
||||
@@ -338,10 +351,19 @@ public final class AlbumArtCache {
|
||||
if (numFetching >= 2) return;
|
||||
if (fetchUrlList.isEmpty()) return;
|
||||
|
||||
++numFetching;
|
||||
|
||||
//Fetch the last-requested url first, it will probably be needed first
|
||||
URL url = fetchUrlList.get(fetchUrlList.size() - 1);
|
||||
//Remove the url from the to-fetch list
|
||||
fetchUrlList.remove(url);
|
||||
|
||||
if ("file".equals(url.getProtocol())) {
|
||||
throw new AssertionError("Not file urls should be possible here!");
|
||||
}
|
||||
|
||||
//Download the album art ourselves
|
||||
++numFetching;
|
||||
//Add the url to the currently-fetching list
|
||||
isFetchingList.add(url);
|
||||
try {
|
||||
DiskLruCache.Editor cacheItem = diskCache.edit(urlToDiskCacheKey(url.toString()));
|
||||
if (cacheItem == null) {
|
||||
@@ -392,6 +414,9 @@ public final class AlbumArtCache {
|
||||
//We need the disk cache for this
|
||||
if (diskCache == null) {
|
||||
Log.e("KDE/Mpris/AlbumArtCache", "The disk cache is not intialized!");
|
||||
try {
|
||||
payload.close();
|
||||
} catch (IOException ignored) {}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -400,20 +425,46 @@ public final class AlbumArtCache {
|
||||
url = new URL(albumUrl);
|
||||
} catch (MalformedURLException e) {
|
||||
//Shouldn't happen (checked on receival of the url), but just to be sure
|
||||
try {
|
||||
payload.close();
|
||||
} catch (IOException ignored) {}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!"file".equals(url.getProtocol())) {
|
||||
//Shouldn't happen (otherwise we wouldn't have asked for the payload), but just to be sure
|
||||
try {
|
||||
payload.close();
|
||||
} catch (IOException ignored) {}
|
||||
return;
|
||||
}
|
||||
|
||||
//Only fetch the URL if we're not fetching it already
|
||||
if (fetchUrlList.contains(url)) {
|
||||
if (isFetchingList.contains(url)) {
|
||||
try {
|
||||
payload.close();
|
||||
} catch (IOException ignored) {}
|
||||
return;
|
||||
}
|
||||
|
||||
fetchUrlList.add(url);
|
||||
//Check if we already have this art
|
||||
try {
|
||||
if (memoryCache.get(albumUrl) != null || diskCache.get(urlToDiskCacheKey(albumUrl)) != null) {
|
||||
try {
|
||||
payload.close();
|
||||
} catch (IOException ignored) {}
|
||||
return;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e("KDE/Mpris/AlbumArtCache", "Disk cache problem!", e);
|
||||
try {
|
||||
payload.close();
|
||||
} catch (IOException ignored) {}
|
||||
return;
|
||||
}
|
||||
|
||||
//Add it to the currently-fetching list
|
||||
isFetchingList.add(url);
|
||||
++numFetching;
|
||||
|
||||
try {
|
||||
@@ -422,6 +473,9 @@ public final class AlbumArtCache {
|
||||
Log.e("KDE/Mpris/AlbumArtCache",
|
||||
"Two disk cache edits happened at the same time, should be impossible!");
|
||||
--numFetching;
|
||||
try {
|
||||
payload.close();
|
||||
} catch (IOException ignored) {}
|
||||
return;
|
||||
}
|
||||
|
||||
|
@@ -139,6 +139,10 @@ public class MprisActivity extends AppCompatActivity {
|
||||
}
|
||||
targetPlayer = mpris.getPlayerStatus(player);
|
||||
updatePlayerStatus(mpris);
|
||||
|
||||
if (targetPlayer.isPlaying()) {
|
||||
MprisMediaSession.getInstance().playerSelected(targetPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -246,8 +250,17 @@ public class MprisActivity extends AppCompatActivity {
|
||||
findViewById(R.id.volume_layout).setVisibility(playerStatus.isSetVolumeAllowed() ? View.VISIBLE : View.INVISIBLE);
|
||||
findViewById(R.id.rew_button).setVisibility(playerStatus.isSeekAllowed() ? View.VISIBLE : View.GONE);
|
||||
findViewById(R.id.ff_button).setVisibility(playerStatus.isSeekAllowed() ? View.VISIBLE : View.GONE);
|
||||
findViewById(R.id.next_button).setVisibility(playerStatus.isGoNextAllowed() ? View.VISIBLE : View.GONE);
|
||||
findViewById(R.id.prev_button).setVisibility(playerStatus.isGoPreviousAllowed() ? View.VISIBLE : View.GONE);
|
||||
|
||||
//Show and hide previous/next buttons simultaneously
|
||||
if (playerStatus.isGoPreviousAllowed() || playerStatus.isGoNextAllowed()) {
|
||||
findViewById(R.id.prev_button).setVisibility(View.VISIBLE);
|
||||
findViewById(R.id.prev_button).setEnabled(playerStatus.isGoPreviousAllowed());
|
||||
findViewById(R.id.next_button).setVisibility(View.VISIBLE);
|
||||
findViewById(R.id.next_button).setEnabled(playerStatus.isGoNextAllowed());
|
||||
} else {
|
||||
findViewById(R.id.prev_button).setVisibility(View.GONE);
|
||||
findViewById(R.id.next_button).setVisibility(View.GONE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -25,6 +25,7 @@ import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
@@ -253,6 +254,11 @@ public class MprisMediaSession implements SharedPreferences.OnSharedPreferenceCh
|
||||
metadata.putLong(MediaMetadataCompat.METADATA_KEY_DURATION, notificationPlayer.getLength());
|
||||
}
|
||||
|
||||
Bitmap albumArt = notificationPlayer.getAlbumArt();
|
||||
if (albumArt != null) {
|
||||
metadata.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART, albumArt);
|
||||
}
|
||||
|
||||
mediaSession.setMetadata(metadata.build());
|
||||
PlaybackStateCompat.Builder playbackState = new PlaybackStateCompat.Builder();
|
||||
|
||||
@@ -326,6 +332,10 @@ public class MprisMediaSession implements SharedPreferences.OnSharedPreferenceCh
|
||||
notification.setContentText(notificationPlayer.getPlayer());
|
||||
}
|
||||
|
||||
if (albumArt != null) {
|
||||
notification.setLargeIcon(albumArt);
|
||||
}
|
||||
|
||||
if (!notificationPlayer.isPlaying()) {
|
||||
Intent iCloseNotification = new Intent(service, MprisMediaNotificationReceiver.class);
|
||||
iCloseNotification.setAction(MprisMediaNotificationReceiver.ACTION_CLOSE_NOTIFICATION);
|
||||
@@ -405,4 +415,9 @@ public class MprisMediaSession implements SharedPreferences.OnSharedPreferenceCh
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
updateMediaNotification();
|
||||
}
|
||||
|
||||
public void playerSelected(MprisPlugin.MprisPlayer player) {
|
||||
notificationPlayer = player;
|
||||
updateMediaNotification();
|
||||
}
|
||||
}
|
||||
|
@@ -130,7 +130,7 @@ public class MprisPlugin extends Plugin {
|
||||
* @return The album art, or null if not available
|
||||
*/
|
||||
public Bitmap getAlbumArt() {
|
||||
return AlbumArtCache.getAlbumArt(albumArtUrl);
|
||||
return AlbumArtCache.getAlbumArt(albumArtUrl, MprisPlugin.this, player);
|
||||
}
|
||||
|
||||
public boolean isSetVolumeAllowed() {
|
||||
@@ -205,6 +205,7 @@ public class MprisPlugin extends Plugin {
|
||||
public final static String PACKET_TYPE_MPRIS_REQUEST = "kdeconnect.mpris.request";
|
||||
|
||||
private HashMap<String, MprisPlayer> players = new HashMap<>();
|
||||
private boolean supportAlbumArtPayload = false;
|
||||
private HashMap<String, Handler> playerStatusUpdated = new HashMap<>();
|
||||
|
||||
private HashMap<String, Handler> playerListUpdated = new HashMap<>();
|
||||
@@ -231,7 +232,6 @@ public class MprisPlugin extends Plugin {
|
||||
|
||||
@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
|
||||
@@ -266,6 +266,11 @@ public class MprisPlugin extends Plugin {
|
||||
|
||||
@Override
|
||||
public boolean onPacketReceived(NetworkPacket np) {
|
||||
if (np.getBoolean("transferringAlbumArt", false)) {
|
||||
AlbumArtCache.payloadToDiskCache(np.getString("albumArtUrl"), np.getPayload());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (np.has("player")) {
|
||||
MprisPlayer playerStatus = players.get(np.getString("player"));
|
||||
if (playerStatus != null) {
|
||||
@@ -306,6 +311,9 @@ public class MprisPlugin extends Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
//Remember if the connected device support album art payloads
|
||||
supportAlbumArtPayload = np.getBoolean("supportAlbumArtPayload", supportAlbumArtPayload);
|
||||
|
||||
List<String> newPlayerList = np.getStringList("playerList");
|
||||
if (newPlayerList != null) {
|
||||
boolean equals = true;
|
||||
@@ -463,4 +471,22 @@ public class MprisPlugin extends Plugin {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean askTransferAlbumArt(String url, String playerName) {
|
||||
//First check if the remote supports transferring album art
|
||||
if (!supportAlbumArtPayload) return false;
|
||||
if (url.isEmpty()) return false;
|
||||
|
||||
MprisPlayer player = getPlayerStatus(playerName);
|
||||
if (player == null) return false;
|
||||
|
||||
if (player.albumArtUrl.equals(url)) {
|
||||
NetworkPacket np = new NetworkPacket(PACKET_TYPE_MPRIS_REQUEST);
|
||||
np.set("player", player.getPlayer());
|
||||
np.set("albumArtUrl", url);
|
||||
device.sendPacket(np);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -26,6 +26,8 @@ import android.app.PendingIntent;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.preference.PreferenceManager;
|
||||
@@ -38,6 +40,7 @@ import org.kde.kdeconnect.Helpers.NotificationHelper;
|
||||
import org.kde.kdeconnect_tp.R;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
public class ShareNotification {
|
||||
|
||||
@@ -99,6 +102,18 @@ public class ShareNotification {
|
||||
* - 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 it's an image, try to show it in the notification
|
||||
if (mimeType.startsWith("image/")) {
|
||||
try {
|
||||
Bitmap image = BitmapFactory.decodeStream(device.getContext().getContentResolver().openInputStream(destinationUri));
|
||||
if (image != null) {
|
||||
builder.setLargeIcon(image);
|
||||
builder.setStyle(new NotificationCompat.BigPictureStyle()
|
||||
.bigPicture(image));
|
||||
}
|
||||
} catch (FileNotFoundException ignored) {}
|
||||
}
|
||||
if (!"file".equals(destinationUri.getScheme())) {
|
||||
return;
|
||||
}
|
||||
|
@@ -323,6 +323,7 @@ public class DeviceFragment extends Fragment {
|
||||
|
||||
if (device.isPairRequestedByPeer()) {
|
||||
((TextView) rootView.findViewById(R.id.pair_message)).setText(R.string.pair_requested);
|
||||
rootView.findViewById(R.id.pairing_buttons).setVisibility(View.VISIBLE);
|
||||
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);
|
||||
|
@@ -159,16 +159,6 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb
|
||||
SectionItem section;
|
||||
Resources res = getResources();
|
||||
|
||||
section = new SectionItem(res.getString(R.string.category_not_paired_devices));
|
||||
section.isSectionEmpty = true;
|
||||
items.add(section);
|
||||
for (Device device : devices) {
|
||||
if (device.isReachable() && !device.isPaired()) {
|
||||
items.add(new PairingDeviceItem(device, PairingFragment.this));
|
||||
section.isSectionEmpty = false;
|
||||
}
|
||||
}
|
||||
|
||||
section = new SectionItem(res.getString(R.string.category_connected_devices));
|
||||
section.isSectionEmpty = true;
|
||||
items.add(section);
|
||||
@@ -182,6 +172,16 @@ public class PairingFragment extends Fragment implements PairingDeviceItem.Callb
|
||||
items.remove(items.size() - 1); //Remove connected devices section if empty
|
||||
}
|
||||
|
||||
section = new SectionItem(res.getString(R.string.category_not_paired_devices));
|
||||
section.isSectionEmpty = true;
|
||||
items.add(section);
|
||||
for (Device device : devices) {
|
||||
if (device.isReachable() && !device.isPaired()) {
|
||||
items.add(new PairingDeviceItem(device, PairingFragment.this));
|
||||
section.isSectionEmpty = false;
|
||||
}
|
||||
}
|
||||
|
||||
section = new SectionItem(res.getString(R.string.category_remembered_devices));
|
||||
section.isSectionEmpty = true;
|
||||
items.add(section);
|
||||
|
Reference in New Issue
Block a user