Warning, /network/kdeconnect-ios/KDE Connect/KDE Connect/ObjC Backend/Device.m is written in an unsupported language. File is not indexed.

0001 /*
0002  * SPDX-FileCopyrightText: 2014 YANG Qiao <yangqiao0505@me.com>
0003  *                         2020 Weixuan Xiao <veyx.shaw@gmail.com>
0004  *                         2021 Lucas Wang <lucas.wang@tuta.io>
0005  *
0006  * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
0007  */
0008 
0009 // Original header below:
0010 //Copyright 29/4/14  YANG Qiao yangqiao0505@me.com
0011 //kdeconnect is distributed under two licenses.
0012 //
0013 //* The Mozilla Public License (MPL) v2.0
0014 //
0015 //or
0016 //
0017 //* The General Public License (GPL) v2.1
0018 //
0019 //----------------------------------------------------------------------
0020 //
0021 //Software distributed under these licenses is distributed on an "AS
0022 //IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
0023 //implied. See the License for the specific language governing rights
0024 //and limitations under the License.
0025 //kdeconnect is distributed under both the GPL and the MPL. The MPL
0026 //notice, reproduced below, covers the use of either of the licenses.
0027 //
0028 //---------------------------------------------------------------------
0029 
0030 #import "Device.h"
0031 //#import "BackgroundService.h"
0032 #import "KDE_Connect-Swift.h"
0033 @import os.log;
0034 static const NSTimeInterval kPairingTimeout = 30.0;
0035 
0036 @implementation Device {
0037     NSMutableDictionary<NetworkPackageType, id<Plugin>> *_plugins;
0038     NSMutableDictionary<NetworkPackageType, NSNumber *> *_pluginsEnableStatus;
0039     os_log_t logger;
0040 }
0041 
0042 @synthesize _id;
0043 @synthesize _name;
0044 @synthesize _pairStatus;
0045 @synthesize _protocolVersion;
0046 @synthesize _type;
0047 @synthesize deviceDelegate;
0048 @synthesize _links;
0049 - (void)setPlugins:(NSDictionary<NetworkPackageType, NSNumber *> *)plugins
0050 {
0051     _plugins = [[NSMutableDictionary alloc] initWithDictionary:plugins];
0052 }
0053 @synthesize _failedPlugins;
0054 @synthesize _incomingCapabilities;
0055 @synthesize _outgoingCapabilities;
0056 //@synthesize _testDevice;
0057 
0058 - (void)setPluginsEnableStatus:(NSDictionary<NetworkPackageType, NSNumber *> *)pluginsEnableStatus
0059 {
0060     _pluginsEnableStatus = [[NSMutableDictionary alloc] initWithDictionary:pluginsEnableStatus];
0061 }
0062 @synthesize _SHA256HashFormatted;
0063 
0064 // TODO: plugins should be saving their own preferences
0065 // Plugin-specific persistent data are stored in the Device object. Plugin objects contain runtime
0066 // data only and are therefore NOT stored persistently
0067 // Remote Input
0068 @synthesize _cursorSensitivity;
0069 // Presenter
0070 @synthesize _pointerSensitivity;
0071 
0072 - (instancetype)initWithID:(NSString *)deviceID
0073                       type:(DeviceType)deviceType
0074                       name:(NSString *)deviceName
0075       incomingCapabilities:(NSArray<NSString *> *)incomingCapabilities
0076       outgoingCapabilities:(NSArray<NSString *> *)outgoingCapabilities
0077            protocolVersion:(NSInteger)protocolVersion
0078             deviceDelegate:(id<DeviceDelegate>)deviceDelegate
0079 {
0080     if (self = [super init]) {
0081         logger = os_log_create([NSString kdeConnectOSLogSubsystem].UTF8String,
0082                                NSStringFromClass([self class]).UTF8String);
0083         _id = deviceID;
0084         _type = deviceType;
0085         _name = deviceName;
0086         _incomingCapabilities = incomingCapabilities;
0087         _outgoingCapabilities = outgoingCapabilities;
0088         _links = [NSMutableArray arrayWithCapacity:1];
0089         _plugins = [NSMutableDictionary dictionaryWithCapacity:1];
0090         _failedPlugins = [NSMutableArray arrayWithCapacity:1];
0091         _protocolVersion = protocolVersion;
0092         _pluginsEnableStatus = [NSMutableDictionary dictionary];
0093         self.deviceDelegate = deviceDelegate;
0094         _cursorSensitivity = 3.0;
0095         _hapticStyle = 0;
0096         _pointerSensitivity = 3.0;
0097         [self reloadPlugins];
0098     }
0099     return self;
0100 }
0101 
0102 - (instancetype)initWithNetworkPackage:(NetworkPackage *)np
0103                                   link:(BaseLink*)link
0104                               delegate:(id<DeviceDelegate>)deviceDelegate {
0105     if (self = [self initWithID:[np objectForKey:@"deviceId"]
0106                            type:[Device Str2DeviceType:[np objectForKey:@"deviceType"]]
0107                            name:[np objectForKey:@"deviceName"]
0108            incomingCapabilities:[np objectForKey:@"incomingCapabilities"]
0109            outgoingCapabilities:[np objectForKey:@"outgoingCapabilities"]
0110                 protocolVersion:[np integerForKey:@"protocolVersion"]
0111                  deviceDelegate:deviceDelegate]) {
0112         [self addLink:link];
0113     }
0114     return self;
0115 }
0116 
0117 - (os_log_type_t)debugLogLevel {
0118     if ([SelfDeviceData shared].isDebuggingDiscovery) {
0119         return OS_LOG_TYPE_INFO;
0120     }
0121     return OS_LOG_TYPE_DEBUG;
0122 }
0123 
0124 - (NSInteger) compareProtocolVersion
0125 {
0126     return 0;
0127 }
0128 
0129 #pragma mark Link-related Functions
0130 
0131 - (void)updateInfoWithNetworkPackage:(NetworkPackage*)np {
0132     if (_protocolVersion!=[np integerForKey:@"protocolVersion"]) {
0133         os_log_with_type(logger, self.debugLogLevel, "using different protocol version");
0134     }
0135     _id=[np objectForKey:@"deviceId"];
0136     _name=[np objectForKey:@"deviceName"];
0137     _type=[Device Str2DeviceType:[np objectForKey:@"deviceType"]];
0138     _incomingCapabilities=[np objectForKey:@"incomingCapabilities"];
0139     _outgoingCapabilities=[np objectForKey:@"outgoingCapabilities"];
0140 }
0141 
0142 - (void)addLink:(BaseLink*)Link {
0143     os_log_with_type(logger, OS_LOG_TYPE_INFO, "add link to %{mask.hash}@",_id);
0144     NSUInteger count;
0145     @synchronized (_links) {
0146         [_links addObject:Link];
0147         //[self saveSetting];
0148         [Link setLinkDelegate:self];
0149         count = [_links count];
0150     }
0151     if (count == 1) {
0152         os_log_with_type(logger, self.debugLogLevel, "one link available");
0153         if (deviceDelegate) {
0154             [deviceDelegate onDeviceReachableStatusChanged:self];
0155         }
0156         // If a remembered device is online with its first link, ask for its battery status
0157         [self updateBatteryStatus];
0158     }
0159 }
0160 
0161 // FIXME: This ain't it, doesn't get called when connection is cut (e.g wifi off) from the remote device
0162 - (void) onLinkDestroyed:(BaseLink *)link
0163 {
0164     os_log_with_type(logger, self.debugLogLevel, "device on link destroyed");
0165     NSUInteger count;
0166     @synchronized (_links) {
0167         [_links removeObject:link];
0168         count = [_links count];
0169     }
0170     os_log_with_type(logger, self.debugLogLevel, "remove link ; %lu remaining", count);
0171     if (count == 0) {
0172         os_log_with_type(logger, self.debugLogLevel, "no available link");
0173         if (deviceDelegate) {
0174             [deviceDelegate onDeviceReachableStatusChanged:self];
0175             // No, we don't want to remove the plugins because IF the device is coming back online later, we want to still have to ready
0176             //[_plugins removeAllObjects];
0177             //[_failedPlugins removeAllObjects];
0178         }
0179     }
0180     if (deviceDelegate) {
0181         [deviceDelegate onLinkDestroyed:link];
0182     }
0183 }
0184 
0185 - (BOOL) sendPackage:(NetworkPackage *)np tag:(long)tag
0186 {
0187     os_log_with_type(logger, self.debugLogLevel, "device send package");
0188     @synchronized (_links) {
0189         for (BaseLink *link in _links) {
0190             if ([link sendPackage:np tag:tag]) {
0191                 return true;
0192             }
0193         }
0194     }
0195     return false;
0196 }
0197 
0198 - (void)onPackage:(NetworkPackage *)np sentWithPackageTag:(long)tag {
0199     os_log_with_type(logger, self.debugLogLevel, "device on send success");
0200     if (tag==PACKAGE_TAG_PAIR) {
0201         if (_pairStatus==RequestedByPeer) {
0202             [self setAsPaired];
0203         }
0204     } else if (tag == PACKAGE_TAG_PAYLOAD){
0205         os_log_with_type(logger, self.debugLogLevel, "Last payload sent successfully, sending next one");
0206         for (id<Plugin> plugin in [_plugins allValues]) {
0207             if ([plugin respondsToSelector:@selector(onPackage:sentWithPackageTag:)]) {
0208                 [plugin onPackage:np sentWithPackageTag:tag];
0209             }
0210         }
0211     }
0212 }
0213 
0214 - (void)onPackage:(NetworkPackage *)np sendWithPackageTag:(long)tag
0215   failedWithError:(NSError *)error {
0216     switch (tag) {
0217         case PACKAGE_TAG_PAYLOAD:
0218             for (id<Plugin> plugin in [_plugins allValues]) {
0219                 if ([plugin respondsToSelector:@selector(onPackage:sendWithPackageTag:failedWithError:)]) {
0220                     [plugin onPackage:np sendWithPackageTag:tag
0221                       failedWithError:error];
0222                 }
0223             }
0224             break;
0225         default:
0226             break;
0227     }
0228 }
0229 
0230 - (void)onSendingPayload:(KDEFileTransferItem *)payload {
0231     for (id<Plugin> plugin in [_plugins allValues]) {
0232         if ([plugin respondsToSelector:@selector(onSendingPayload:)]) {
0233             [plugin onSendingPayload:payload];
0234         }
0235     }
0236 }
0237 
0238 - (void)willReceivePayload:(KDEFileTransferItem *)payload
0239   totalNumOfFilesToReceive:(long)numberOfFiles {
0240     for (id<Plugin> plugin in [_plugins allValues]) {
0241         if ([plugin respondsToSelector:@selector(willReceivePayload:totalNumOfFilesToReceive:)]) {
0242             [plugin willReceivePayload:payload totalNumOfFilesToReceive:numberOfFiles];
0243         }
0244     }
0245 }
0246 
0247 - (void)onReceivingPayload:(KDEFileTransferItem *)payload {
0248     for (id<Plugin> plugin in [_plugins allValues]) {
0249         if ([plugin respondsToSelector:@selector(onReceivingPayload:)]) {
0250             [plugin onReceivingPayload:payload];
0251         }
0252     }
0253 }
0254 
0255 - (void)onReceivingPayload:(KDEFileTransferItem *)payload
0256            failedWithError:(NSError *)error {
0257     for (id<Plugin> plugin in [_plugins allValues]) {
0258         if ([plugin respondsToSelector:@selector(onReceivingPayload:failedWithError:)]) {
0259             [plugin onReceivingPayload:payload failedWithError:error];
0260         }
0261     }
0262 }
0263 
0264 - (void)onPackageReceived:(NetworkPackage *)np {
0265     os_log_with_type(logger, self.debugLogLevel, "device on package received");
0266     if ([np.type isEqualToString:NetworkPackageTypePair]) {
0267         os_log_with_type(logger, self.debugLogLevel, "Pair package received");
0268         BOOL wantsPair=[np boolForKey:@"pair"];
0269         if (wantsPair==[self isPaired]) {
0270             os_log_with_type(logger, self.debugLogLevel, "already done, paired:%d",wantsPair);
0271             if (_pairStatus==Requested) {
0272                 os_log_with_type(logger, self.debugLogLevel, "canceled by other peer");
0273                 dispatch_async(dispatch_get_main_queue(), ^{
0274                     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(requestPairingTimeout:) object:nil];
0275                 });
0276                 _pairStatus=NotPaired;
0277                 if (deviceDelegate) {
0278                     [deviceDelegate onDevicePairRejected:self];
0279                 }
0280             } else if (wantsPair) {
0281                 [self acceptPairing];
0282             }
0283             return;
0284         }
0285         if (wantsPair) {
0286             os_log_with_type(logger, self.debugLogLevel, "pair request");
0287             if ((_pairStatus)==Requested) {
0288                 dispatch_async(dispatch_get_main_queue(), ^{
0289                     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(requestPairingTimeout:) object:nil];
0290                 });
0291                 [self setAsPaired];
0292             }
0293             else{
0294                 _pairStatus=RequestedByPeer;
0295                 if (deviceDelegate) {
0296                     [deviceDelegate onDevicePairRequest:self];
0297                 }
0298             }
0299         } else {
0300             //NSLog(@"unpair request");
0301             if (_pairStatus==Requested) {
0302                 //NSLog(@"canceled by other peer");
0303                 _pairStatus=NotPaired;
0304                 dispatch_async(dispatch_get_main_queue(), ^{
0305                     [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(requestPairingTimeout:) object:nil];
0306                 });
0307             } else if (_pairStatus==Paired) {
0308                 // okay to call unpair directly because other is reachable
0309                 [self unpair];
0310                 if (deviceDelegate) {
0311                     [deviceDelegate onDeviceUnpaired:self];
0312                 }
0313             }
0314         }
0315     } else if ([self isPaired]) {
0316         // TODO: Instead of looping through all the Obj-C plugins here, calls Plugin handling function elsewhere in Swift
0317         os_log_with_type(logger, OS_LOG_TYPE_INFO, "received a plugin package: %{public}@", np.type);
0318         for (id<Plugin> plugin in [_plugins allValues]) {
0319             [plugin onDevicePackageReceivedWithNp:np];
0320         }
0321         //[PluginsService goThroughHostPluginsForReceivingWithNp:np];
0322     } else {
0323         // old iOS implementations send battery request while the devices are unpaired
0324         os_log_with_type(logger, OS_LOG_TYPE_DEFAULT,
0325                          "not paired, ignore package of %{public}@, unpair the device",
0326                          np.type);
0327         // remembered devices should have became paired, okay to call unpair.
0328         [self unpair];
0329     }
0330 }
0331 
0332 - (BOOL) isReachable
0333 {
0334     // synchronize not very helpful
0335     return [_links count] != 0;
0336 }
0337 
0338 - (void) loadSetting
0339 {
0340 }
0341 
0342 - (void) saveSetting
0343 {
0344 }
0345 
0346 #pragma mark Pairing-related Functions
0347 - (BOOL) isPaired
0348 {
0349     return _pairStatus==Paired; // || _testDevice
0350 }
0351 
0352 - (BOOL) isPaireRequested
0353 {
0354     return _pairStatus==Requested;
0355 }
0356 
0357 - (void) setAsPaired
0358 {
0359     _pairStatus=Paired;
0360     //NSLog(@"paired with %@",_name);
0361     [self saveSetting];
0362     // Request and update battery status for a newly paired device
0363     [self updateBatteryStatus];
0364     if (deviceDelegate) {
0365         [deviceDelegate onDevicePairSuccess:self];
0366     }
0367 }
0368 
0369 - (void) requestPairing
0370 {
0371     if (![self isReachable]) {
0372         os_log_with_type(logger, OS_LOG_TYPE_ERROR, "device failed:not reachable");
0373         return;
0374     }
0375     if (_pairStatus==Paired) {
0376         os_log_with_type(logger, OS_LOG_TYPE_DEFAULT, "device failed:already paired");
0377         return;
0378     }
0379     if (_pairStatus==Requested) {
0380         os_log_with_type(logger, OS_LOG_TYPE_DEFAULT, "device failed:already requested");
0381         return;
0382     }
0383     if (_pairStatus==RequestedByPeer) {
0384         os_log_with_type(logger, self.debugLogLevel, "device accept pair request");
0385     }
0386     else{
0387         os_log_with_type(logger, self.debugLogLevel, "device request pairing");
0388         _pairStatus=Requested;
0389         dispatch_async(dispatch_get_main_queue(), ^{
0390             [self performSelector:@selector(requestPairingTimeout:) withObject:nil afterDelay:kPairingTimeout];
0391         });
0392     }
0393     NetworkPackage* np=[NetworkPackage createPairPackage];
0394     [self sendPackage:np tag:PACKAGE_TAG_PAIR];
0395 }
0396 
0397 - (void)requestPairingTimeout:(id)sender {
0398     os_log_with_type(logger, OS_LOG_TYPE_ERROR, "device request pairing timeout");
0399     if (_pairStatus==Requested) {
0400         _pairStatus=NotPaired;
0401         os_log_with_type(logger, self.debugLogLevel, "pairing timeout");
0402         if (deviceDelegate) {
0403             [deviceDelegate onDevicePairTimeout:self];
0404         }
0405         // okay to call unpair directly because only invocation of this method
0406         // is scheduled when we initiate a pairing (and cancelled otherwise)
0407         // thus can't be a remembered or paired device.
0408         [self unpair];
0409     }
0410 }
0411 
0412 /// Change the status of the Device to unpaired WITHOUT sending out an unpair packet.
0413 - (void)setAsUnpaired {
0414     _pairStatus=NotPaired;
0415 }
0416 
0417 - (void)unpair {
0418     os_log_with_type(logger, self.debugLogLevel, "device unpair");
0419     _pairStatus=NotPaired;
0420 
0421     NetworkPackage* np=[[NetworkPackage alloc] initWithType:NetworkPackageTypePair];
0422     [np setBool:false forKey:@"pair"];
0423     [self sendPackage:np tag:PACKAGE_TAG_UNPAIR];
0424 }
0425 
0426 - (void) acceptPairing
0427 {
0428     os_log_with_type(logger, self.debugLogLevel, "device accepted pair request");
0429     NetworkPackage* np=[NetworkPackage createPairPackage];
0430     [self sendPackage:np tag:PACKAGE_TAG_PAIR];
0431 }
0432 
0433 #pragma mark Plugins-related Functions
0434 
0435 - (void)updateBatteryStatus {
0436     if ((_pluginsEnableStatus[NetworkPackageTypeBatteryRequest] != nil)
0437         && (_pluginsEnableStatus[NetworkPackageTypeBatteryRequest])) {
0438         id<Plugin> plugin = [_plugins objectForKey:NetworkPackageTypeBatteryRequest];
0439         if ([plugin respondsToSelector:@selector(sendBatteryStatusRequest)]) {
0440             [plugin performSelector:@selector(sendBatteryStatusRequest)];
0441         }
0442         // For backward compatibility reasons, we should update the other device
0443         if ([plugin respondsToSelector:@selector(sendBatteryStatusOut)]) {
0444             [plugin performSelector:@selector(sendBatteryStatusOut)];
0445         }
0446     }
0447 }
0448 
0449 - (void) reloadPlugins
0450 {
0451 //    if (![self isReachable]) {
0452 //        return;
0453 //    }
0454     
0455     os_log_with_type(logger, self.debugLogLevel, "device reload plugins");
0456     [_plugins removeAllObjects];
0457     [_failedPlugins removeAllObjects];
0458     [_pluginsEnableStatus removeAllObjects];
0459     
0460     for (NSString* pluginID in _incomingCapabilities) {
0461         if ([pluginID isEqualToString:NetworkPackageTypePing]) {
0462             [_plugins setObject:[[Ping alloc] initWithControlDevice:self] forKey:NetworkPackageTypePing];
0463             [_pluginsEnableStatus setValue:@TRUE forKey:NetworkPackageTypePing];
0464             
0465         } else if ([pluginID isEqualToString:NetworkPackageTypeShare]) {
0466             [_plugins setObject:[[Share alloc] initWithControlDevice:self] forKey:NetworkPackageTypeShare];
0467             [_pluginsEnableStatus setValue:@TRUE forKey:NetworkPackageTypeShare];
0468             
0469         } else if ([pluginID isEqualToString:NetworkPackageTypeFindMyPhoneRequest]) {
0470             [_plugins setObject:[[FindMyPhone alloc] initWithControlDevice:self] forKey:NetworkPackageTypeFindMyPhoneRequest];
0471             [_pluginsEnableStatus setValue:@TRUE forKey:NetworkPackageTypeFindMyPhoneRequest];
0472             
0473         } else if ([pluginID isEqualToString:NetworkPackageTypeBatteryRequest]) {
0474             [_plugins setObject:[[Battery alloc] initWithControlDevice:self] forKey:NetworkPackageTypeBatteryRequest];
0475             [_pluginsEnableStatus setValue:@TRUE forKey:NetworkPackageTypeBatteryRequest];
0476             
0477         } else if ([pluginID isEqualToString:NetworkPackageTypeClipboard]) {
0478             [_plugins setObject:[[Clipboard alloc] initWithControlDevice:self] forKey:NetworkPackageTypeClipboard];
0479             [_pluginsEnableStatus setValue:@TRUE forKey:NetworkPackageTypeClipboard];
0480             
0481         } else if ([pluginID isEqualToString:NetworkPackageTypeMousePadRequest]) {
0482             [_plugins setObject:[[RemoteInput alloc] initWithControlDevice:self] forKey:NetworkPackageTypeMousePadRequest];
0483             [_pluginsEnableStatus setValue:@TRUE forKey:NetworkPackageTypeMousePadRequest];
0484             
0485         } else if ([pluginID isEqualToString:NetworkPackageTypePresenter]) {
0486             [_plugins setObject:[[Presenter alloc] initWithControlDevice:self] forKey:NetworkPackageTypePresenter];
0487             [_pluginsEnableStatus setValue:@TRUE forKey:NetworkPackageTypePresenter];
0488         }
0489     }
0490     
0491     // for the capabilities that are ONLY in the outgoing section of KDE Connect iOS
0492     for (NSString* pluginID in _outgoingCapabilities) {
0493         if ([pluginID isEqualToString:NetworkPackageTypeRunCommand]) {
0494             [_plugins setObject:[[RunCommand alloc] initWithControlDevice:self] forKey:NetworkPackageTypeRunCommand];
0495             [_pluginsEnableStatus setValue:@TRUE forKey:NetworkPackageTypeRunCommand];
0496             
0497         }
0498     }
0499     
0500     
0501 //    //NSLog(@"device reload plugins");
0502 //    [_failedPlugins removeAllObjects];
0503 //    PluginFactory* pluginFactory=[PluginFactory sharedInstance];
0504 //    NSArray* pluginNames=[pluginFactory getAvailablePlugins];
0505 //    NSSortDescriptor *sd = [[NSSortDescriptor alloc] initWithKey:nil ascending:YES];
0506 //    pluginNames=[pluginNames sortedArrayUsingDescriptors:[NSArray arrayWithObject:sd]];
0507 //    SettingsStore* _devSettings=[[SettingsStore alloc] initWithPath:_id];
0508 //    for (NSString* pluginName in pluginNames) {
0509 //        if ([_devSettings objectForKey:pluginName]!=nil && ![_devSettings boolForKey:pluginName]) {
0510 //            [[_plugins objectForKey:pluginName] stop];
0511 //            [_plugins removeObjectForKey:pluginName];
0512 //            [_failedPlugins addObject:pluginName];
0513 //            continue;
0514 //        }
0515 //        [_plugins removeObjectForKey:pluginName];
0516 //        Plugin* plugin=[pluginFactory instantiatePluginForDevice:self pluginName:pluginName];
0517 //        if (plugin)
0518 //            [_plugins setValue:plugin forKey:pluginName];
0519 //        else
0520 //            [_failedPlugins addObject:pluginName];
0521 //    }
0522 }
0523 
0524 //- (NSArray*) getPluginViews:(UIViewController*)vc
0525 //{
0526 //    NSMutableArray* views=[NSMutableArray arrayWithCapacity:1];
0527 //    for (Plugin* plugin in [_plugins allValues]) {
0528 //        UIView* view=[plugin getView:vc];
0529 //        if (view) {
0530 //            [views addObject:view];
0531 //        }
0532 //    }
0533 //    return views;
0534 //}
0535 
0536 #pragma mark enum tools
0537 + (NSString*)DeviceType2Str:(DeviceType)type
0538 {
0539     switch (type) {
0540         case DeviceTypeDesktop:
0541             return @"desktop";
0542         case DeviceTypeLaptop:
0543             return @"laptop";
0544         case DeviceTypePhone:
0545             return @"phone";
0546         case DeviceTypeTablet:
0547             return @"tablet";
0548         case DeviceTypeTv:
0549             return @"tv";
0550         case DeviceTypeUnknown:
0551             return @"unknown";
0552     }
0553 }
0554 + (DeviceType)Str2DeviceType:(NSString*)str
0555 {
0556     if ([str isEqualToString:@"desktop"]) {
0557         return DeviceTypeDesktop;
0558     }
0559     if ([str isEqualToString:@"laptop"]) {
0560         return DeviceTypeLaptop;
0561     }
0562     if ([str isEqualToString:@"phone"]) {
0563         return DeviceTypePhone;
0564     }
0565     if ([str isEqualToString:@"tablet"]) {
0566         return DeviceTypeTablet;
0567     }
0568     if ([str isEqualToString:@"tv"]) {
0569         return DeviceTypeTv;
0570     }
0571     return DeviceTypeUnknown;
0572 }
0573 
0574 #pragma mark En/Decoding Methods
0575 - (void)encodeWithCoder:(nonnull NSCoder *)coder {
0576     [coder encodeObject:_id forKey:@"_id"];
0577     [coder encodeObject:_name forKey:@"_name"];
0578     [coder encodeInteger:_type forKey:@"_type"];
0579     [coder encodeInteger:_protocolVersion forKey:@"_protocolVersion"];
0580     [coder encodeInteger:_pairStatus forKey:@"_pairStatus"];
0581     [coder encodeObject:_incomingCapabilities forKey:@"_incomingCapabilities"];
0582     [coder encodeObject:_outgoingCapabilities forKey:@"_outgoingCapabilities"];
0583     [coder encodeObject:_pluginsEnableStatus forKey:@"_pluginsEnableStatus"];
0584     [coder encodeFloat:_cursorSensitivity forKey:@"_cursorSensitivity"];
0585     [coder encodeInteger:_hapticStyle forKey:@"_hapticStyle"];
0586     [coder encodeFloat:_pointerSensitivity forKey:@"_pointerSensitivity"];
0587     [coder encodeObject:_SHA256HashFormatted forKey:@"_SHA256HashFormatted"];
0588 }
0589 
0590 - (nullable instancetype)initWithCoder:(nonnull NSCoder *)coder {
0591     if (self = [super init]) {
0592         _id = [coder decodeObjectForKey:@"_id"];
0593         _name = [coder decodeObjectForKey:@"_name"];
0594         _type = [coder decodeIntegerForKey:@"_type"];
0595         _protocolVersion = [coder decodeIntegerForKey:@"_protocolVersion"];
0596         _pairStatus = [coder decodeIntegerForKey:@"_pairStatus"];
0597         _incomingCapabilities = [coder decodeArrayOfObjectsOfClass:[NSString class] forKey:@"_incomingCapabilities"];
0598         _outgoingCapabilities = [coder decodeArrayOfObjectsOfClass:[NSString class] forKey:@"_outgoingCapabilities"];
0599         _pluginsEnableStatus = (NSMutableDictionary*)[(NSDictionary*)[coder decodeDictionaryWithKeysOfClass:[NSString class] objectsOfClass:[NSNumber class] forKey:@"_pluginsEnableStatus"] mutableCopy];
0600         _cursorSensitivity = [coder decodeFloatForKey:@"_cursorSensitivity"];
0601         _hapticStyle = [coder decodeIntegerForKey:@"_hapticStyle"];
0602         _pointerSensitivity = [coder decodeFloatForKey:@"_pointerSensitivity"];
0603         _SHA256HashFormatted = [coder decodeObjectForKey:@"_SHA256HashFormatted"];
0604         
0605         // To be set later in backgroundServices
0606         deviceDelegate = nil;
0607         
0608         // To be populated later
0609         _plugins = [NSMutableDictionary dictionary];
0610         _failedPlugins = [NSMutableArray array];
0611         _links = [NSMutableArray array];
0612         [self reloadPlugins];
0613     }
0614     return self;
0615 }
0616 
0617 + (BOOL)supportsSecureCoding {
0618     return YES;
0619 }
0620 
0621 @end