2014-11-16 23:14:06 -08:00
/ *
* Copyright 2014 Albert Vaca Cintora < albertvaka @gmail.com >
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation ; either version 2 of
* the License or ( at your option ) version 3 or any later version
* accepted by the membership of KDE e . V . ( or its successor approved
* by the membership of KDE e . V . ) , which shall act as a proxy
* defined in Section 14 of version 3 of the license .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
* /
2013-09-05 01:37:59 +02:00
package org.kde.kdeconnect ;
2013-07-23 16:11:54 +02:00
2013-09-03 17:58:59 +02:00
import android.app.Notification ;
import android.app.NotificationManager ;
import android.app.PendingIntent ;
2013-08-16 10:31:01 +02:00
import android.content.Context ;
2013-09-03 17:58:59 +02:00
import android.content.Intent ;
2013-08-16 10:31:01 +02:00
import android.content.SharedPreferences ;
2013-09-05 01:33:54 +02:00
import android.content.res.Resources ;
2015-04-12 00:06:47 -07:00
import android.graphics.drawable.Drawable ;
2016-05-31 21:02:17 +02:00
import android.os.Build ;
2013-08-16 10:31:01 +02:00
import android.preference.PreferenceManager ;
2013-09-03 17:58:59 +02:00
import android.support.v4.app.NotificationCompat ;
2015-08-10 00:26:58 -07:00
import android.support.v4.content.ContextCompat ;
2013-09-03 17:58:59 +02:00
import android.util.Base64 ;
2013-07-23 16:11:54 +02:00
import android.util.Log ;
2016-05-31 21:02:17 +02:00
import org.json.JSONArray ;
import org.json.JSONException ;
2013-09-16 17:36:26 +02:00
import org.kde.kdeconnect.Backends.BaseLink ;
2015-08-13 14:55:00 +05:30
import org.kde.kdeconnect.Backends.BasePairingHandler ;
2016-05-31 21:02:17 +02:00
import org.kde.kdeconnect.Helpers.ObjectsHelper ;
2016-03-03 15:42:39 -08:00
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper ;
2013-09-05 01:37:59 +02:00
import org.kde.kdeconnect.Plugins.Plugin ;
import org.kde.kdeconnect.Plugins.PluginFactory ;
2015-09-11 09:24:35 -07:00
import org.kde.kdeconnect.UserInterface.MaterialActivity ;
2013-09-05 01:37:59 +02:00
import org.kde.kdeconnect_tp.R ;
2013-09-03 17:58:59 +02:00
import java.security.KeyFactory ;
import java.security.PrivateKey ;
import java.security.PublicKey ;
2015-08-24 02:03:55 +05:30
import java.security.cert.Certificate ;
2013-09-03 17:58:59 +02:00
import java.security.spec.PKCS8EncodedKeySpec ;
import java.security.spec.X509EncodedKeySpec ;
2013-07-23 16:11:54 +02:00
import java.util.ArrayList ;
2016-06-02 12:28:33 +02:00
import java.util.Arrays ;
2013-08-16 10:31:01 +02:00
import java.util.HashMap ;
2015-09-08 15:05:32 -07:00
import java.util.HashSet ;
2015-08-13 14:55:00 +05:30
import java.util.Map ;
2016-05-31 21:02:17 +02:00
import java.util.Objects ;
2013-08-16 10:31:01 +02:00
import java.util.Set ;
2015-09-14 04:19:13 -07:00
import java.util.concurrent.ConcurrentHashMap ;
2015-09-10 07:49:28 -07:00
import java.util.concurrent.CopyOnWriteArrayList ;
2013-07-23 16:11:54 +02:00
2013-09-16 17:36:26 +02:00
public class Device implements BaseLink . PackageReceiver {
2013-07-23 16:11:54 +02:00
2014-03-29 01:47:15 +01:00
private final Context context ;
2013-09-03 17:58:59 +02:00
2014-03-29 01:47:15 +01:00
private final String deviceId ;
2015-03-01 20:57:45 -08:00
private String name ;
2014-01-16 09:51:32 +04:00
public PublicKey publicKey ;
2015-08-24 02:03:55 +05:30
public Certificate certificate ;
2013-09-03 17:58:59 +02:00
private int notificationId ;
2013-10-01 03:24:42 +02:00
private int protocolVersion ;
2013-09-03 17:58:59 +02:00
2015-09-08 15:05:32 -07:00
private DeviceType deviceType ;
private PairStatus pairStatus ;
2015-09-10 07:52:37 -07:00
2015-09-10 07:49:28 -07:00
private final CopyOnWriteArrayList < PairingCallback > pairingCallback = new CopyOnWriteArrayList < > ( ) ;
2015-09-11 09:24:35 -07:00
private Map < String , BasePairingHandler > pairingHandlers = new HashMap < String , BasePairingHandler > ( ) ;
2015-09-08 15:05:32 -07:00
2015-09-10 07:49:28 -07:00
private final CopyOnWriteArrayList < BaseLink > links = new CopyOnWriteArrayList < > ( ) ;
2015-09-08 15:05:32 -07:00
2016-05-31 21:02:17 +02:00
private ArrayList < String > incomingCapabilities = new ArrayList < > ( ) ;
private ArrayList < String > outgoingCapabilities = new ArrayList < > ( ) ;
2015-09-08 15:05:32 -07:00
2015-09-14 04:19:13 -07:00
private final ConcurrentHashMap < String , Plugin > plugins = new ConcurrentHashMap < > ( ) ;
private final ConcurrentHashMap < String , Plugin > failedPlugins = new ConcurrentHashMap < > ( ) ;
2015-09-08 15:05:32 -07:00
private ArrayList < String > unsupportedPlugins = new ArrayList < > ( ) ;
private HashSet < String > supportedIncomingInterfaces = new HashSet < > ( ) ;
2015-09-12 08:49:46 -07:00
private HashSet < String > supportedOutgoingInterfaces = new HashSet < > ( ) ;
2015-09-08 15:05:32 -07:00
2015-09-10 08:12:22 -07:00
private HashMap < String , ArrayList < String > > pluginsByIncomingInterface ;
private HashMap < String , ArrayList < String > > pluginsByOutgoingInterface ;
2015-09-08 15:05:32 -07:00
private final SharedPreferences settings ;
public ArrayList < String > getUnsupportedPlugins ( ) {
return unsupportedPlugins ;
}
2015-09-10 07:49:28 -07:00
private final CopyOnWriteArrayList < PluginsChangedListener > pluginsChangedListeners = new CopyOnWriteArrayList < > ( ) ;
public interface PluginsChangedListener {
void onPluginsChanged ( Device device ) ;
}
2015-06-10 12:29:53 +05:30
public enum PairStatus {
2013-09-03 17:58:59 +02:00
NotPaired ,
Paired
}
2015-06-10 12:29:53 +05:30
public enum DeviceType {
2015-04-12 00:06:47 -07:00
Phone ,
Tablet ,
Computer ;
public static DeviceType FromString ( String s ) {
if ( " tablet " . equals ( s ) ) return Tablet ;
2015-09-09 01:11:46 -07:00
if ( " phone " . equals ( s ) ) return Phone ;
return Computer ; //Default
2015-04-12 00:06:47 -07:00
}
public String toString ( ) {
switch ( this ) {
case Tablet : return " tablet " ;
2015-09-09 01:11:46 -07:00
case Phone : return " phone " ;
default : return " desktop " ;
2015-04-12 00:06:47 -07:00
}
}
}
2013-09-03 17:58:59 +02:00
public interface PairingCallback {
2015-08-13 14:55:00 +05:30
void incomingRequest ( ) ;
void pairingSuccessful ( ) ;
void pairingFailed ( String error ) ;
void unpaired ( ) ;
2013-09-03 17:58:59 +02:00
}
2013-08-16 10:31:01 +02:00
//Remembered trusted device, we need to wait for a incoming devicelink to communicate
Device ( Context context , String deviceId ) {
2013-08-20 00:37:08 +02:00
settings = context . getSharedPreferences ( deviceId , Context . MODE_PRIVATE ) ;
2013-08-16 10:31:01 +02:00
//Log.e("Device","Constructor A");
this . context = context ;
2013-07-23 16:11:54 +02:00
this . deviceId = deviceId ;
2015-03-01 20:57:45 -08:00
this . name = settings . getString ( " deviceName " , context . getString ( R . string . unknown_device ) ) ;
2013-09-03 17:58:59 +02:00
this . pairStatus = PairStatus . Paired ;
2013-10-01 03:24:42 +02:00
this . protocolVersion = NetworkPackage . ProtocolVersion ; //We don't know it yet
2015-09-09 01:07:17 -07:00
this . deviceType = DeviceType . FromString ( settings . getString ( " deviceType " , " desktop " ) ) ;
2013-09-03 17:58:59 +02:00
try {
2016-02-12 08:37:47 -08:00
String publicKeyStr = settings . getString ( " publicKey " , null ) ;
if ( publicKeyStr ! = null ) {
byte [ ] publicKeyBytes = Base64 . decode ( publicKeyStr , 0 ) ;
publicKey = KeyFactory . getInstance ( " RSA " ) . generatePublic ( new X509EncodedKeySpec ( publicKeyBytes ) ) ;
}
2013-09-03 17:58:59 +02:00
} catch ( Exception e ) {
e . printStackTrace ( ) ;
2016-02-12 08:37:47 -08:00
Log . e ( " KDE/Device " , " Exception deserializing stored public key for device " ) ;
2013-09-03 17:58:59 +02:00
}
2013-08-16 10:31:01 +02:00
reloadPluginsFromSettings ( ) ;
}
//Device known via an incoming connection sent to us via a devicelink, we know everything but we don't trust it yet
2013-10-01 03:24:42 +02:00
Device ( Context context , NetworkPackage np , BaseLink dl ) {
2013-08-16 10:31:01 +02:00
//Log.e("Device","Constructor B");
this . context = context ;
2013-10-01 03:24:42 +02:00
this . deviceId = np . getString ( " deviceId " ) ;
2015-09-09 01:07:17 -07:00
this . name = context . getString ( R . string . unknown_device ) ; //We read it in addLink
2013-09-03 17:58:59 +02:00
this . pairStatus = PairStatus . NotPaired ;
2015-09-09 01:07:17 -07:00
this . protocolVersion = 0 ;
this . deviceType = DeviceType . Computer ;
2013-09-03 17:58:59 +02:00
this . publicKey = null ;
2013-08-16 10:31:01 +02:00
2013-10-29 19:44:07 +01:00
settings = context . getSharedPreferences ( deviceId , Context . MODE_PRIVATE ) ;
2013-10-01 03:24:42 +02:00
addLink ( np , dl ) ;
2013-07-23 16:11:54 +02:00
}
public String getName ( ) {
2013-09-05 01:33:54 +02:00
return name ! = null ? name : context . getString ( R . string . unknown_device ) ;
2013-07-23 16:11:54 +02:00
}
2015-04-12 00:06:47 -07:00
public Drawable getIcon ( )
{
2015-08-10 00:26:58 -07:00
int drawableId ;
2015-04-12 00:06:47 -07:00
switch ( deviceType ) {
2015-08-20 00:17:01 -07:00
case Phone : drawableId = R . drawable . ic_device_phone ; break ;
case Tablet : drawableId = R . drawable . ic_device_tablet ; break ;
2015-08-10 00:26:58 -07:00
default : drawableId = R . drawable . ic_device_laptop ;
2015-04-12 00:06:47 -07:00
}
2015-08-10 00:26:58 -07:00
return ContextCompat . getDrawable ( context , drawableId ) ;
2015-04-12 00:06:47 -07:00
}
public DeviceType getDeviceType ( ) {
return deviceType ;
}
2013-07-23 16:11:54 +02:00
public String getDeviceId ( ) {
return deviceId ;
}
2015-06-25 04:20:03 +05:30
public Context getContext ( ) {
return context ;
}
2013-10-01 03:24:42 +02:00
//Returns 0 if the version matches, < 0 if it is older or > 0 if it is newer
public int compareProtocolVersion ( ) {
return protocolVersion - NetworkPackage . ProtocolVersion ;
}
2013-09-03 17:58:59 +02:00
//
// Pairing-related functions
//
public boolean isPaired ( ) {
return pairStatus = = PairStatus . Paired ;
2013-08-16 10:31:01 +02:00
}
2015-08-13 14:55:00 +05:30
/* Asks all pairing handlers that, is pair requested? */
2013-09-03 17:58:59 +02:00
public boolean isPairRequested ( ) {
2015-08-13 14:55:00 +05:30
boolean pairRequested = false ;
for ( BasePairingHandler ph : pairingHandlers . values ( ) ) {
pairRequested = pairRequested | | ph . isPairRequested ( ) ;
}
return pairRequested ;
}
/* Asks all pairing handlers that, is pair requested by peer? */
public boolean isPairRequestedByPeer ( ) {
boolean pairRequestedByPeer = false ;
for ( BasePairingHandler ph : pairingHandlers . values ( ) ) {
pairRequestedByPeer = pairRequestedByPeer | | ph . isPairRequestedByPeer ( ) ;
}
return pairRequestedByPeer ;
2013-08-16 10:31:01 +02:00
}
2013-09-03 17:58:59 +02:00
public void addPairingCallback ( PairingCallback callback ) {
pairingCallback . add ( callback ) ;
}
2015-08-13 14:55:00 +05:30
2013-09-03 17:58:59 +02:00
public void removePairingCallback ( PairingCallback callback ) {
pairingCallback . remove ( callback ) ;
}
2013-08-16 10:31:01 +02:00
2013-09-03 17:58:59 +02:00
public void requestPairing ( ) {
2013-08-16 10:31:01 +02:00
2013-09-05 01:33:54 +02:00
Resources res = context . getResources ( ) ;
2015-06-14 19:23:02 -07:00
switch ( pairStatus ) {
case Paired :
for ( PairingCallback cb : pairingCallback ) {
cb . pairingFailed ( res . getString ( R . string . error_already_paired ) ) ;
}
return ;
case NotPaired :
;
2013-09-03 17:58:59 +02:00
}
2015-06-14 19:23:02 -07:00
2013-09-03 17:58:59 +02:00
if ( ! isReachable ( ) ) {
2013-10-29 19:44:07 +01:00
for ( PairingCallback cb : pairingCallback ) {
cb . pairingFailed ( res . getString ( R . string . error_not_reachable ) ) ;
}
2013-09-03 17:58:59 +02:00
return ;
}
2013-08-16 10:31:01 +02:00
2015-08-13 14:55:00 +05:30
for ( BasePairingHandler ph : pairingHandlers . values ( ) ) {
ph . requestPairing ( ) ;
2015-07-01 03:25:16 +05:30
}
2013-09-03 17:58:59 +02:00
}
2015-08-13 14:55:00 +05:30
public void unpair ( ) {
for ( BasePairingHandler ph : pairingHandlers . values ( ) ) {
ph . unpair ( ) ;
}
unpairInternal ( ) ; // Even if there are no pairing handlers, unpair
2013-09-03 17:58:59 +02:00
}
2015-08-13 14:55:00 +05:30
/ * *
* This method does not send an unpair package , instead it unpairs internally by deleting trusted device info . . Likely to be called after sending package from
* pairing handler
* /
private void unpairInternal ( ) {
2013-09-03 17:58:59 +02:00
2015-08-13 14:55:00 +05:30
//Log.e("Device","Unpairing (unpairInternal)");
2013-09-03 17:58:59 +02:00
pairStatus = PairStatus . NotPaired ;
SharedPreferences preferences = context . getSharedPreferences ( " trusted_devices " , Context . MODE_PRIVATE ) ;
2015-01-31 00:16:06 -08:00
preferences . edit ( ) . remove ( deviceId ) . apply ( ) ;
2013-09-03 17:58:59 +02:00
2015-08-13 14:55:00 +05:30
// FIXME : We delete all device info here, but the xml file still persists
2015-06-25 04:20:03 +05:30
SharedPreferences devicePreferences = context . getSharedPreferences ( deviceId , Context . MODE_PRIVATE ) ;
devicePreferences . edit ( ) . clear ( ) . apply ( ) ;
2013-09-03 17:58:59 +02:00
for ( PairingCallback cb : pairingCallback ) cb . unpaired ( ) ;
reloadPluginsFromSettings ( ) ;
}
2015-08-13 14:55:00 +05:30
/* This method should be called after pairing is done from pairing handler. Calling this method again should not create any problem as most of the things will get over writter*/
2013-10-29 19:44:07 +01:00
private void pairingDone ( ) {
2013-09-03 17:58:59 +02:00
2013-11-06 21:13:37 +01:00
//Log.e("Device", "Storing as trusted, deviceId: "+deviceId);
2015-09-12 13:21:06 -07:00
hidePairingNotification ( ) ;
2013-11-06 21:13:37 +01:00
2013-09-03 17:58:59 +02:00
pairStatus = PairStatus . Paired ;
//Store as trusted device
SharedPreferences preferences = context . getSharedPreferences ( " trusted_devices " , Context . MODE_PRIVATE ) ;
2015-01-31 00:16:06 -08:00
preferences . edit ( ) . putBoolean ( deviceId , true ) . apply ( ) ;
2013-09-03 17:58:59 +02:00
2015-07-01 03:25:16 +05:30
SharedPreferences . Editor editor = context . getSharedPreferences ( deviceId , Context . MODE_PRIVATE ) . edit ( ) ;
editor . putString ( " deviceName " , name ) ;
editor . putString ( " deviceType " , deviceType . toString ( ) ) ;
editor . apply ( ) ;
2013-08-16 10:31:01 +02:00
reloadPluginsFromSettings ( ) ;
2013-10-29 19:44:07 +01:00
for ( PairingCallback cb : pairingCallback ) {
cb . pairingSuccessful ( ) ;
}
}
2015-08-13 14:55:00 +05:30
/* This method is called after accepting pair request form GUI */
2013-10-29 19:44:07 +01:00
public void acceptPairing ( ) {
2015-06-25 04:20:03 +05:30
Log . i ( " KDE/Device " , " Accepted pair request started by the other device " ) ;
2015-08-13 14:55:00 +05:30
for ( BasePairingHandler ph : pairingHandlers . values ( ) ) {
ph . acceptPairing ( ) ;
2015-07-01 03:25:16 +05:30
}
2013-09-03 17:58:59 +02:00
}
2015-08-13 14:55:00 +05:30
/* This method is called after rejecting pairing from GUI */
2013-09-03 17:58:59 +02:00
public void rejectPairing ( ) {
2015-07-01 03:25:16 +05:30
Log . i ( " KDE/Device " , " Rejected pair request started by the other device " ) ;
2013-09-03 17:58:59 +02:00
2013-11-06 21:13:37 +01:00
//Log.e("Device","Unpairing (rejectPairing)");
2013-09-03 17:58:59 +02:00
pairStatus = PairStatus . NotPaired ;
2015-08-13 14:55:00 +05:30
for ( BasePairingHandler ph : pairingHandlers . values ( ) ) {
ph . rejectPairing ( ) ;
2015-06-25 04:20:03 +05:30
}
2013-09-03 17:58:59 +02:00
2013-10-29 19:44:07 +01:00
for ( PairingCallback cb : pairingCallback ) {
cb . pairingFailed ( context . getString ( R . string . error_canceled_by_user ) ) ;
}
2013-09-03 17:58:59 +02:00
2013-08-16 10:31:01 +02:00
}
2015-08-13 14:55:00 +05:30
//
// Notification related methods used during pairing
//
public int getNotificationId ( ) {
return notificationId ;
}
public void displayPairingNotification ( ) {
2015-12-02 09:46:22 -08:00
hidePairingNotification ( ) ;
2015-08-13 14:55:00 +05:30
notificationId = ( int ) System . currentTimeMillis ( ) ;
2015-09-11 09:24:35 -07:00
Intent intent = new Intent ( getContext ( ) , MaterialActivity . class ) ;
2015-08-13 14:55:00 +05:30
intent . putExtra ( " deviceId " , getDeviceId ( ) ) ;
intent . putExtra ( " notificationId " , notificationId ) ;
PendingIntent pendingIntent = PendingIntent . getActivity ( getContext ( ) , 0 , intent , PendingIntent . FLAG_ONE_SHOT ) ;
Resources res = getContext ( ) . getResources ( ) ;
Notification noti = new NotificationCompat . Builder ( getContext ( ) )
. setContentTitle ( res . getString ( R . string . pairing_request_from , getName ( ) ) )
. setContentText ( res . getString ( R . string . tap_to_answer ) )
. setContentIntent ( pendingIntent )
. setTicker ( res . getString ( R . string . pair_requested ) )
2015-09-11 09:24:35 -07:00
. setSmallIcon ( R . drawable . ic_notification )
2015-08-13 14:55:00 +05:30
. setAutoCancel ( true )
. setDefaults ( Notification . DEFAULT_ALL )
. build ( ) ;
2013-08-16 10:31:01 +02:00
2015-08-13 14:55:00 +05:30
final NotificationManager notificationManager = ( NotificationManager ) getContext ( ) . getSystemService ( Context . NOTIFICATION_SERVICE ) ;
try {
2015-12-02 09:46:22 -08:00
BackgroundService . addGuiInUseCounter ( context ) ;
2015-08-13 14:55:00 +05:30
notificationManager . notify ( notificationId , noti ) ;
} catch ( Exception e ) {
//4.1 will throw an exception about not having the VIBRATE permission, ignore it.
//https://android.googlesource.com/platform/frameworks/base/+/android-4.2.1_r1.2%5E%5E!/
}
}
2015-12-02 09:46:22 -08:00
public void hidePairingNotification ( ) {
2015-08-13 14:55:00 +05:30
final NotificationManager notificationManager = ( NotificationManager ) getContext ( ) . getSystemService ( Context . NOTIFICATION_SERVICE ) ;
notificationManager . cancel ( notificationId ) ;
2015-12-02 09:46:22 -08:00
BackgroundService . removeGuiInUseCounter ( context ) ;
2015-08-13 14:55:00 +05:30
}
2013-08-16 10:31:01 +02:00
//
2013-09-03 17:58:59 +02:00
// ComputerLink-related functions
2013-08-16 10:31:01 +02:00
//
2013-09-03 17:58:59 +02:00
public boolean isReachable ( ) {
return ! links . isEmpty ( ) ;
}
2013-10-01 03:24:42 +02:00
public void addLink ( NetworkPackage identityPackage , BaseLink link ) {
2015-04-04 11:37:12 -07:00
//FilesHelper.LogOpenFileCount();
2013-10-01 03:24:42 +02:00
this . protocolVersion = identityPackage . getInt ( " protocolVersion " ) ;
2013-08-16 10:31:01 +02:00
2015-03-01 20:57:45 -08:00
if ( identityPackage . has ( " deviceName " ) ) {
this . name = identityPackage . getString ( " deviceName " , this . name ) ;
SharedPreferences . Editor editor = settings . edit ( ) ;
editor . putString ( " deviceName " , this . name ) ;
editor . apply ( ) ;
}
2015-04-12 00:06:47 -07:00
if ( identityPackage . has ( " deviceType " ) ) {
2015-09-09 01:07:17 -07:00
this . deviceType = DeviceType . FromString ( identityPackage . getString ( " deviceType " , " desktop " ) ) ;
2015-04-12 00:06:47 -07:00
}
2015-06-20 04:09:02 +05:30
if ( identityPackage . has ( " certificate " ) ) {
String certificateString = identityPackage . getString ( " certificate " ) ;
try {
byte [ ] certificateBytes = Base64 . decode ( certificateString , 0 ) ;
2016-03-03 15:42:39 -08:00
certificate = SslHelper . parseCertificate ( certificateBytes ) ;
2015-06-28 20:07:05 +05:30
Log . i ( " KDE/Device " , " Got certificate " ) ;
2015-06-20 04:09:02 +05:30
} catch ( Exception e ) {
e . printStackTrace ( ) ;
Log . e ( " KDE/Device " , " Error getting certificate " ) ;
}
}
2015-03-01 20:57:45 -08:00
2013-07-23 16:11:54 +02:00
links . add ( link ) ;
2013-08-16 10:31:01 +02:00
2013-09-16 17:36:26 +02:00
try {
SharedPreferences globalSettings = PreferenceManager . getDefaultSharedPreferences ( context ) ;
byte [ ] privateKeyBytes = Base64 . decode ( globalSettings . getString ( " privateKey " , " " ) , 0 ) ;
PrivateKey privateKey = KeyFactory . getInstance ( " RSA " ) . generatePrivate ( new PKCS8EncodedKeySpec ( privateKeyBytes ) ) ;
link . setPrivateKey ( privateKey ) ;
} catch ( Exception e ) {
e . printStackTrace ( ) ;
2015-04-04 11:37:12 -07:00
Log . e ( " KDE/Device " , " Exception reading our own private key " ) ; //Should not happen
2013-09-16 17:36:26 +02:00
}
2015-04-04 11:37:12 -07:00
Log . i ( " KDE/Device " , " addLink " + link . getLinkProvider ( ) . getName ( ) + " -> " + getName ( ) + " active links: " + links . size ( ) ) ;
2013-08-19 23:59:13 +02:00
2015-08-13 14:55:00 +05:30
if ( ! pairingHandlers . containsKey ( link . getName ( ) ) ) {
BasePairingHandler . PairingHandlerCallback callback = new BasePairingHandler . PairingHandlerCallback ( ) {
@Override
public void incomingRequest ( ) {
for ( PairingCallback cb : pairingCallback ) {
cb . incomingRequest ( ) ;
}
}
@Override
public void pairingDone ( ) {
Device . this . pairingDone ( ) ;
}
@Override
public void pairingFailed ( String error ) {
for ( PairingCallback cb : pairingCallback ) {
cb . pairingFailed ( error ) ;
}
}
@Override
public void unpaired ( ) {
unpairInternal ( ) ;
}
} ;
pairingHandlers . put ( link . getName ( ) , link . getPairingHandler ( this , callback ) ) ;
}
pairingHandlers . get ( link . getName ( ) ) . setLink ( link ) ;
2014-09-23 16:41:57 +02:00
/ *
2013-09-16 17:36:26 +02:00
Collections . sort ( links , new Comparator < BaseLink > ( ) {
2013-07-23 16:11:54 +02:00
@Override
2013-09-16 17:36:26 +02:00
public int compare ( BaseLink o , BaseLink o2 ) {
2013-07-23 16:11:54 +02:00
return o2 . getLinkProvider ( ) . getPriority ( ) - o . getLinkProvider ( ) . getPriority ( ) ;
}
} ) ;
2014-09-23 16:41:57 +02:00
* /
2013-07-23 16:11:54 +02:00
2013-08-16 10:31:01 +02:00
link . addPackageReceiver ( this ) ;
if ( links . size ( ) = = 1 ) {
reloadPluginsFromSettings ( ) ;
}
2013-07-23 16:11:54 +02:00
}
2013-09-16 17:36:26 +02:00
public void removeLink ( BaseLink link ) {
2015-04-04 11:37:12 -07:00
//FilesHelper.LogOpenFileCount();
2015-08-13 14:55:00 +05:30
/* Remove pairing handler corresponding to that link too if it was the only link*/
boolean linkPresent = false ;
for ( BaseLink bl : links ) {
if ( bl . getName ( ) . equals ( link . getName ( ) ) ) {
linkPresent = true ;
break ;
}
}
if ( ! linkPresent ) {
pairingHandlers . remove ( link . getName ( ) ) ;
}
2013-08-16 10:31:01 +02:00
link . removePackageReceiver ( this ) ;
2013-07-23 16:11:54 +02:00
links . remove ( link ) ;
2015-06-25 04:20:03 +05:30
Log . i ( " KDE/Device " , " removeLink: " + link . getLinkProvider ( ) . getName ( ) + " -> " + getName ( ) + " active links: " + links . size ( ) ) ;
2013-08-16 10:31:01 +02:00
if ( links . isEmpty ( ) ) {
reloadPluginsFromSettings ( ) ;
}
2013-07-23 16:11:54 +02:00
}
2013-08-16 10:31:01 +02:00
@Override
public void onPackageReceived ( NetworkPackage np ) {
2013-09-03 17:58:59 +02:00
2015-09-08 15:05:32 -07:00
if ( NetworkPackage . PACKAGE_TYPE_PAIR . equals ( np . getType ( ) ) ) {
2013-09-03 17:58:59 +02:00
2015-06-25 04:20:03 +05:30
Log . i ( " KDE/Device " , " Pair package " ) ;
2015-08-15 18:44:49 +05:30
2015-08-13 14:55:00 +05:30
for ( BasePairingHandler ph : pairingHandlers . values ( ) ) {
try {
ph . packageReceived ( np ) ;
} catch ( Exception e ) {
2016-02-12 08:37:47 -08:00
e . printStackTrace ( ) ;
Log . e ( " PairingPackageReceived " , " Exception " ) ;
2013-09-03 17:58:59 +02:00
}
}
2016-05-31 21:02:17 +02:00
} else if ( NetworkPackage . PACKAGE_TYPE_CAPABILITIES . equals ( np . getType ( ) ) ) {
ArrayList < String > newIncomingCapabilities = np . getStringList ( " IncomingCapabilities " ) ;
ArrayList < String > newOutgoingCapabilities = np . getStringList ( " OutgoingCapabilities " ) ;
if ( ! ObjectsHelper . equals ( newIncomingCapabilities , incomingCapabilities ) | |
! ObjectsHelper . equals ( newOutgoingCapabilities , outgoingCapabilities ) ) {
incomingCapabilities = newIncomingCapabilities ;
outgoingCapabilities = newOutgoingCapabilities ;
reloadPluginsFromSettings ( ) ;
}
2013-09-03 17:58:59 +02:00
2015-03-22 22:44:17 -07:00
} else if ( isPaired ( ) ) {
2013-09-03 17:58:59 +02:00
2016-06-02 15:22:21 +02:00
//If capabilities are not supported, iterate all plugins
Collection < String > targetPlugins = ( protocolVersion > 6 ) ? pluginsByIncomingInterface . get ( np . getType ( ) ) : plugins . keySet ( ) ;
2016-06-02 13:56:46 +02:00
if ( targetPlugins ! = null & & ! targetPlugins . isEmpty ( ) ) {
2016-05-31 21:02:17 +02:00
for ( String pluginKey : targetPlugins ) {
Plugin plugin = plugins . get ( pluginKey ) ;
try {
plugin . onPackageReceived ( np ) ;
} catch ( Exception e ) {
e . printStackTrace ( ) ;
Log . e ( " KDE/Device " , " Exception in " + plugin . getPluginKey ( ) + " 's onPackageReceived() " ) ;
}
2014-06-18 22:53:52 +02:00
}
2016-06-02 13:56:46 +02:00
} else {
Log . e ( " Device " , " Ignoring packet with type " + np . getType ( ) + " because no plugin can handle it " ) ;
2013-09-03 17:58:59 +02:00
}
2015-03-22 22:44:17 -07:00
} else {
2015-09-09 03:22:34 -07:00
//Log.e("KDE/onPackageReceived","Device not paired, will pass package to unpairedPackageListeners");
2015-03-22 22:44:17 -07:00
2015-08-13 14:55:00 +05:30
// If it is pair package, it should be captured by "if" at start
// If not and device is paired, it should be captured by isPaired
// Else unpair, this handles the situation when one device unpairs, but other dont know like unpairing when wi-fi is off
unpair ( ) ;
2015-03-22 22:44:17 -07:00
2016-06-02 15:22:21 +02:00
//If capabilities are not supported, iterate all plugins
Collection < String > targetPlugins = ( protocolVersion > 6 ) ? pluginsByIncomingInterface . get ( np . getType ( ) ) : plugins . keySet ( ) ;
2016-06-02 13:56:46 +02:00
if ( targetPlugins ! = null & & ! targetPlugins . isEmpty ( ) ) {
2016-05-31 21:02:17 +02:00
for ( String pluginKey : targetPlugins ) {
Plugin plugin = plugins . get ( pluginKey ) ;
try {
plugin . onUnpairedDevicePackageReceived ( np ) ;
} catch ( Exception e ) {
e . printStackTrace ( ) ;
Log . e ( " KDE/Device " , " Exception in " + plugin . getDisplayName ( ) + " 's onPackageReceived() in unPairedPackageListeners " ) ;
}
2015-08-19 21:27:27 +05:30
}
2016-06-02 13:56:46 +02:00
} else {
Log . e ( " Device " , " Ignoring packet with type " + np . getType ( ) + " because no plugin can handle it " ) ;
2015-08-19 21:27:27 +05:30
}
2013-08-16 10:31:01 +02:00
}
2013-09-03 17:58:59 +02:00
2013-07-23 16:11:54 +02:00
}
2015-01-29 23:57:58 -08:00
public static abstract class SendPackageStatusCallback {
protected abstract void onSuccess ( ) ;
protected abstract void onFailure ( Throwable e ) ;
protected void onProgressChanged ( int percent ) { }
private boolean success = false ;
public void sendSuccess ( ) {
success = true ;
onSuccess ( ) ;
}
public void sendFailure ( Throwable e ) {
if ( e ! = null ) {
e . printStackTrace ( ) ;
2015-04-04 11:37:12 -07:00
Log . e ( " KDE/sendPackage " , " Exception: " + e . getMessage ( ) ) ;
2015-01-29 23:57:58 -08:00
} else {
2015-04-04 11:37:12 -07:00
Log . e ( " KDE/sendPackage " , " Unknown (null) exception " ) ;
2015-01-29 23:57:58 -08:00
}
onFailure ( e ) ;
}
public void sendProgress ( int percent ) { onProgressChanged ( percent ) ; }
2013-09-27 02:58:16 +02:00
}
2013-07-31 18:02:22 +02:00
2015-01-29 23:57:58 -08:00
2013-09-27 02:58:16 +02:00
public void sendPackage ( NetworkPackage np ) {
2015-01-29 23:57:58 -08:00
sendPackage ( np , new SendPackageStatusCallback ( ) {
@Override
protected void onSuccess ( ) { }
@Override
protected void onFailure ( Throwable e ) { }
} ) ;
2013-09-27 02:58:16 +02:00
}
2013-10-05 17:25:59 +02:00
//Async
2015-01-15 22:57:06 -08:00
public void sendPackage ( final NetworkPackage np , final SendPackageStatusCallback callback ) {
2013-09-03 17:58:59 +02:00
2015-01-29 23:57:58 -08:00
//Log.e("sendPackage", "Sending package...");
//Log.e("sendPackage", np.serialize());
2013-10-29 19:44:07 +01:00
2015-01-31 00:16:06 -08:00
final Throwable backtrace = new Throwable ( ) ;
2013-09-27 00:07:14 +02:00
new Thread ( new Runnable ( ) {
2013-07-23 16:11:54 +02:00
@Override
2013-09-27 00:07:14 +02:00
public void run ( ) {
2013-09-16 17:36:26 +02:00
2016-05-31 21:00:18 +02:00
boolean useEncryption = ( protocolVersion < 6 & & ( ! np . getType ( ) . equals ( NetworkPackage . PACKAGE_TYPE_PAIR ) & & isPaired ( ) ) ) ;
2013-09-16 17:36:26 +02:00
2015-01-29 23:57:58 -08:00
//Make a copy to avoid concurrent modification exception if the original list changes
2015-09-10 07:49:28 -07:00
for ( final BaseLink link : links ) {
2015-04-04 14:41:22 -07:00
if ( link = = null ) continue ; //Since we made a copy, maybe somebody destroyed the link in the meanwhile
2015-01-29 23:57:58 -08:00
if ( useEncryption ) {
link . sendPackageEncrypted ( np , callback , publicKey ) ;
} else {
link . sendPackage ( np , callback ) ;
2013-09-27 02:58:16 +02:00
}
2015-01-29 23:57:58 -08:00
if ( callback . success ) break ; //If the link didn't call sendSuccess(), try the next one
2013-10-05 17:25:59 +02:00
}
2013-09-16 17:36:26 +02:00
2015-01-31 00:16:06 -08:00
if ( ! callback . success ) {
2015-09-10 07:49:28 -07:00
Log . e ( " KDE/sendPackage " , " No device link (of " + links . size ( ) + " available) could send the package. Package " + np . getType ( ) + " to " + name + " lost! " ) ;
2015-01-31 00:16:06 -08:00
backtrace . printStackTrace ( ) ;
}
2013-07-23 16:11:54 +02:00
}
2013-09-27 00:07:14 +02:00
} ) . start ( ) ;
2013-09-03 17:58:59 +02:00
2013-07-23 16:11:54 +02:00
}
2013-08-16 10:31:01 +02:00
//
// Plugin-related functions
//
2015-06-06 00:38:51 -07:00
public < T extends Plugin > T getPlugin ( Class < T > pluginClass ) {
return ( T ) getPlugin ( Plugin . getPluginKey ( pluginClass ) ) ;
2015-01-10 00:31:07 -08:00
}
2015-06-06 00:38:51 -07:00
public < T extends Plugin > T getPlugin ( Class < T > pluginClass , boolean includeFailed ) {
return ( T ) getPlugin ( Plugin . getPluginKey ( pluginClass ) , includeFailed ) ;
}
public Plugin getPlugin ( String pluginKey ) {
return getPlugin ( pluginKey , false ) ;
}
public Plugin getPlugin ( String pluginKey , boolean includeFailed ) {
Plugin plugin = plugins . get ( pluginKey ) ;
2015-01-10 00:31:07 -08:00
if ( includeFailed & & plugin = = null ) {
2015-06-06 00:38:51 -07:00
plugin = failedPlugins . get ( pluginKey ) ;
2015-01-10 00:31:07 -08:00
}
return plugin ;
2013-08-16 10:31:01 +02:00
}
2015-09-12 07:29:45 -07:00
private synchronized boolean addPlugin ( final String pluginKey ) {
2015-06-06 00:38:51 -07:00
Plugin existing = plugins . get ( pluginKey ) ;
2013-08-16 10:31:01 +02:00
if ( existing ! = null ) {
2015-09-08 15:05:32 -07:00
//Log.w("KDE/addPlugin","plugin already present:" + pluginKey);
2016-01-05 20:35:52 +01:00
return true ;
2013-08-16 10:31:01 +02:00
}
2015-06-06 00:38:51 -07:00
final Plugin plugin = PluginFactory . instantiatePluginForDevice ( context , pluginKey , this ) ;
2013-08-16 10:31:01 +02:00
if ( plugin = = null ) {
2015-06-06 00:38:51 -07:00
Log . e ( " KDE/addPlugin " , " could not instantiate plugin: " + pluginKey ) ;
2015-08-10 00:26:58 -07:00
failedPlugins . put ( pluginKey , null ) ;
2015-09-12 07:29:45 -07:00
return false ;
2013-08-16 10:31:01 +02:00
}
2015-09-12 07:29:45 -07:00
boolean success ;
try {
success = plugin . onCreate ( ) ;
} catch ( Exception e ) {
success = false ;
e . printStackTrace ( ) ;
Log . e ( " KDE/addPlugin " , " Exception loading plugin " + pluginKey ) ;
}
2013-09-03 17:58:59 +02:00
2015-09-12 07:29:45 -07:00
if ( success ) {
//Log.e("addPlugin","added " + pluginKey);
failedPlugins . remove ( pluginKey ) ;
plugins . put ( pluginKey , plugin ) ;
} else {
Log . e ( " KDE/addPlugin " , " plugin failed to load " + pluginKey ) ;
plugins . remove ( pluginKey ) ;
failedPlugins . put ( pluginKey , plugin ) ;
}
2013-08-19 19:57:29 +02:00
2015-09-12 07:29:45 -07:00
return success ;
2013-08-16 10:31:01 +02:00
}
2015-06-06 00:38:51 -07:00
private synchronized boolean removePlugin ( String pluginKey ) {
2013-08-19 19:57:29 +02:00
2015-06-06 00:38:51 -07:00
Plugin plugin = plugins . remove ( pluginKey ) ;
Plugin failedPlugin = failedPlugins . remove ( pluginKey ) ;
2013-08-19 19:57:29 +02:00
2013-08-16 10:31:01 +02:00
if ( plugin = = null ) {
2013-08-19 19:57:29 +02:00
if ( failedPlugin = = null ) {
2014-10-10 12:38:11 -07:00
//Not found
2013-08-19 19:57:29 +02:00
return false ;
}
plugin = failedPlugin ;
2013-08-16 10:31:01 +02:00
}
try {
plugin . onDestroy ( ) ;
2015-06-06 00:38:51 -07:00
//Log.e("removePlugin","removed " + pluginKey);
2013-08-16 10:31:01 +02:00
} catch ( Exception e ) {
e . printStackTrace ( ) ;
2015-06-06 00:38:51 -07:00
Log . e ( " KDE/removePlugin " , " Exception calling onDestroy for plugin " + pluginKey ) ;
2013-08-16 10:31:01 +02:00
}
return true ;
}
2015-06-06 00:38:51 -07:00
public void setPluginEnabled ( String pluginKey , boolean value ) {
settings . edit ( ) . putBoolean ( pluginKey , value ) . apply ( ) ;
2015-09-08 15:05:32 -07:00
reloadPluginsFromSettings ( ) ;
2013-08-16 10:31:01 +02:00
}
2015-06-06 00:38:51 -07:00
public boolean isPluginEnabled ( String pluginKey ) {
boolean enabledByDefault = PluginFactory . getPluginInfo ( context , pluginKey ) . isEnabledByDefault ( ) ;
boolean enabled = settings . getBoolean ( pluginKey , enabledByDefault ) ;
2013-08-19 19:57:29 +02:00
return enabled ;
}
2014-09-23 16:40:34 +02:00
public boolean hasPluginsLoaded ( ) {
2014-09-29 18:50:25 -07:00
return ! plugins . isEmpty ( ) | | ! failedPlugins . isEmpty ( ) ;
2014-09-23 16:40:34 +02:00
}
2013-08-19 19:57:29 +02:00
2013-08-16 10:31:01 +02:00
public void reloadPluginsFromSettings ( ) {
2013-08-19 19:57:29 +02:00
failedPlugins . clear ( ) ;
2013-08-16 10:31:01 +02:00
Set < String > availablePlugins = PluginFactory . getAvailablePlugins ( ) ;
2015-09-08 15:05:32 -07:00
ArrayList < String > newUnsupportedPlugins = new ArrayList < > ( ) ;
HashSet < String > newSupportedIncomingInterfaces = new HashSet < > ( ) ;
2015-09-12 08:49:46 -07:00
HashSet < String > newSupportedOutgoingInterfaces = new HashSet < > ( ) ;
2015-09-08 15:05:32 -07:00
HashMap < String , ArrayList < String > > newPluginsByIncomingInterface = new HashMap < > ( ) ;
HashMap < String , ArrayList < String > > newPluginsByOutgoingInterface = new HashMap < > ( ) ;
2016-05-31 21:02:17 +02:00
final boolean supportsCapabilities = ( protocolVersion > = 6 ) ;
2015-09-12 12:25:22 +02:00
2015-09-08 15:05:32 -07:00
for ( String pluginKey : availablePlugins ) {
PluginFactory . PluginInfo pluginInfo = PluginFactory . getPluginInfo ( context , pluginKey ) ;
Set < String > incomingInterfaces = pluginInfo . getSupportedPackageTypes ( ) ;
Set < String > outgoingInterfaces = pluginInfo . getOutgoingPackageTypes ( ) ;
boolean pluginEnabled = false ;
boolean listenToUnpaired = pluginInfo . listenToUnpaired ( ) ;
2015-08-27 13:06:24 +05:30
if ( ( isPaired ( ) | | listenToUnpaired ) & & isReachable ( ) ) {
2015-09-08 15:05:32 -07:00
pluginEnabled = isPluginEnabled ( pluginKey ) ;
2013-08-16 10:31:01 +02:00
}
2015-08-27 13:06:24 +05:30
2015-09-12 08:49:46 -07:00
//TODO: Check for plugins that will fail to load before checking the capabilities
2015-09-08 15:05:32 -07:00
2015-09-12 12:25:22 +02:00
if ( supportsCapabilities & & ( ! incomingInterfaces . isEmpty ( ) | | ! outgoingInterfaces . isEmpty ( ) ) ) {
2015-09-08 15:05:32 -07:00
HashSet < String > supportedOut = new HashSet < > ( outgoingInterfaces ) ;
supportedOut . retainAll ( incomingCapabilities ) ; //Intersection
HashSet < String > supportedIn = new HashSet < > ( incomingInterfaces ) ;
supportedIn . retainAll ( outgoingCapabilities ) ;
if ( supportedOut . isEmpty ( ) & & supportedIn . isEmpty ( ) ) {
newUnsupportedPlugins . add ( pluginKey ) ;
2015-09-12 08:49:46 -07:00
if ( pluginEnabled ) {
//We still want to announce this capability, to prevent a deadlock
newSupportedOutgoingInterfaces . addAll ( outgoingInterfaces ) ;
newSupportedIncomingInterfaces . addAll ( incomingInterfaces ) ;
pluginEnabled = false ;
}
2015-09-08 15:05:32 -07:00
}
}
if ( pluginEnabled ) {
2015-09-12 07:29:45 -07:00
boolean success = addPlugin ( pluginKey ) ;
2015-09-08 15:05:32 -07:00
2015-09-12 07:29:45 -07:00
if ( success ) {
2015-09-12 08:06:12 -07:00
newSupportedIncomingInterfaces . addAll ( incomingInterfaces ) ;
2015-09-12 08:49:46 -07:00
newSupportedOutgoingInterfaces . addAll ( outgoingInterfaces ) ;
2015-09-12 08:06:12 -07:00
2015-09-12 07:29:45 -07:00
for ( String packageType : incomingInterfaces ) {
ArrayList < String > plugins = newPluginsByIncomingInterface . get ( packageType ) ;
if ( plugins = = null ) plugins = new ArrayList < > ( ) ;
plugins . add ( pluginKey ) ;
newPluginsByIncomingInterface . put ( packageType , plugins ) ;
}
2015-09-12 08:06:12 -07:00
2015-09-12 07:29:45 -07:00
for ( String packageType : outgoingInterfaces ) {
ArrayList < String > plugins = newPluginsByOutgoingInterface . get ( packageType ) ;
if ( plugins = = null ) plugins = new ArrayList < > ( ) ;
plugins . add ( pluginKey ) ;
newPluginsByOutgoingInterface . put ( packageType , plugins ) ;
}
2015-09-08 15:05:32 -07:00
}
2013-08-16 10:31:01 +02:00
} else {
2015-06-06 00:38:51 -07:00
removePlugin ( pluginKey ) ;
2013-08-16 10:31:01 +02:00
}
2015-09-08 15:05:32 -07:00
}
boolean capabilitiesChanged = false ;
if ( ! newSupportedIncomingInterfaces . equals ( supportedIncomingInterfaces ) | |
2015-09-12 08:49:46 -07:00
! newSupportedOutgoingInterfaces . equals ( supportedOutgoingInterfaces ) ) {
2015-09-08 15:05:32 -07:00
capabilitiesChanged = true ;
2013-08-16 10:31:01 +02:00
}
2015-09-08 15:05:32 -07:00
pluginsByOutgoingInterface = newPluginsByOutgoingInterface ;
pluginsByIncomingInterface = newPluginsByIncomingInterface ;
supportedIncomingInterfaces = newSupportedIncomingInterfaces ;
2015-09-12 08:49:46 -07:00
supportedOutgoingInterfaces = newSupportedOutgoingInterfaces ;
2015-09-08 15:05:32 -07:00
unsupportedPlugins = newUnsupportedPlugins ;
2016-06-02 12:28:33 +02:00
Log . i ( " ReloadPlugins " , " not loading " + Arrays . toString ( unsupportedPlugins . toArray ( ) ) + " because of unmatched capabilities " ) ;
2015-09-12 12:24:11 +02:00
onPluginsChanged ( ) ;
2015-09-08 15:05:32 -07:00
2016-05-31 17:23:55 +02:00
//Only send capabilities to devices using protocol version 6 or later
if ( capabilitiesChanged & & isReachable ( ) & & isPaired ( ) & & protocolVersion > = 6 ) {
2015-09-08 15:05:32 -07:00
NetworkPackage np = new NetworkPackage ( NetworkPackage . PACKAGE_TYPE_CAPABILITIES ) ;
2015-09-11 05:20:19 -07:00
np . set ( " IncomingCapabilities " , new ArrayList < > ( newSupportedIncomingInterfaces ) ) ;
2015-09-12 08:49:46 -07:00
np . set ( " OutgoingCapabilities " , new ArrayList < > ( newSupportedOutgoingInterfaces ) ) ;
2015-09-08 15:05:32 -07:00
sendPackage ( np ) ;
2013-08-16 10:31:01 +02:00
}
}
2015-09-12 12:24:11 +02:00
public void onPluginsChanged ( ) {
for ( PluginsChangedListener listener : pluginsChangedListeners ) {
listener . onPluginsChanged ( Device . this ) ;
}
}
2015-09-14 04:19:13 -07:00
public ConcurrentHashMap < String , Plugin > getLoadedPlugins ( ) {
2013-09-03 17:58:59 +02:00
return plugins ;
}
2015-09-14 04:19:13 -07:00
public ConcurrentHashMap < String , Plugin > getFailedPlugins ( ) {
2013-08-19 19:57:29 +02:00
return failedPlugins ;
}
2013-08-16 10:31:01 +02:00
2013-08-19 19:57:29 +02:00
public void addPluginsChangedListener ( PluginsChangedListener listener ) {
pluginsChangedListeners . add ( listener ) ;
}
2013-08-16 10:31:01 +02:00
2013-08-19 19:57:29 +02:00
public void removePluginsChangedListener ( PluginsChangedListener listener ) {
pluginsChangedListeners . remove ( listener ) ;
2013-08-16 10:31:01 +02:00
}
2015-09-09 00:45:56 +02:00
public void disconnect ( ) {
for ( BaseLink link : links ) {
link . disconnect ( ) ;
}
}
2015-09-12 13:21:06 -07:00
2016-03-02 11:39:49 -08:00
public boolean deviceShouldBeKeptAlive ( ) {
2015-09-12 13:21:06 -07:00
for ( BaseLink l : links ) {
2016-03-02 11:39:49 -08:00
if ( l . linkShouldBeKeptAlive ( ) ) {
return true ;
2015-09-12 13:21:06 -07:00
}
}
2016-03-02 11:39:49 -08:00
return false ;
2015-09-12 13:21:06 -07:00
}
2013-07-23 16:11:54 +02:00
}