├── Device.uno ├── LICENSE ├── README.md ├── device_include.unoproj └── example ├── Assets └── background.jpg ├── Device-package.unoproj ├── MainView.ux └── devices.json /Device.uno: -------------------------------------------------------------------------------- 1 | using Uno; 2 | using Uno.UX; 3 | using Uno.Text; 4 | using Uno.Collections; 5 | using Uno.Compiler.ExportTargetInterop; 6 | using Uno.Permissions; 7 | using Uno.Threading; 8 | 9 | using Fuse; 10 | using Fuse.Scripting; 11 | using Fuse.Reactive; 12 | 13 | [extern(Android) ForeignInclude(Language.Java, "android.app.Activity", 14 | "android.provider.Settings", 15 | "android.telephony.TelephonyManager", 16 | "android.content.*", 17 | "android.net.wifi.*", 18 | "java.security.*", 19 | "java.util.regex.*", 20 | "java.util.*", 21 | "java.net.*", 22 | "java.nio.*", 23 | "java.io.*")] 24 | 25 | [extern(iOS) ForeignInclude(Language.ObjC, "sys/types.h", "sys/sysctl.h")] 26 | 27 | [UXGlobalModule] 28 | public sealed class Device : NativeModule { 29 | static readonly Device _instance; 30 | 31 | static string cachedUUID; 32 | static string cachedVendorName; 33 | static string cachedModelName; 34 | static string cachedSystemName; 35 | static string cachedSystemVersion; 36 | static string cachedSDKVersion; 37 | static double cachedNumProcessorCores = 0f; 38 | 39 | static extern(Android) Promise _authorizePromise; 40 | 41 | public Device() : base() { 42 | if (_instance != null) return; 43 | Uno.UX.Resource.SetGlobalKey(_instance = this, "Device"); 44 | 45 | AddMember(new NativeProperty< string, object >("vendor", Vendor)); 46 | AddMember(new NativeProperty< string, object >("model", Model)); 47 | AddMember(new NativeProperty< string, object >("system", System)); 48 | AddMember(new NativeProperty< string, object >("systemVersion", SystemVersion)); 49 | AddMember(new NativeProperty< string, object >("SDKVersion", SDKVersion)); 50 | AddMember(new NativeProperty< double, object >("cores", NumProcessorCores)); 51 | AddMember(new NativeProperty< double, object >("displayScale", PixelsPerPoint)); 52 | AddMember(new NativeProperty< bool, object >("isRetina", IsRetina)); 53 | AddMember(new NativeProperty< string, object >("UUID", UUID)); 54 | 55 | // async getUUID implementation 56 | AddMember(new NativePromise< string, string >("getUUID", AsyncUUID, null)); 57 | 58 | // [language]-[region]-[variants] (e.g. zh-EN-Hans, en-US, etc.) 59 | AddMember(new NativeProperty< string, object >("locale", GetCurrentLocale)); 60 | } 61 | 62 | public Future AsyncUUID(object[] args) { 63 | return AsyncUUIDImpl(); 64 | } 65 | 66 | public static extern(!Android) Future AsyncUUIDImpl() { 67 | var p = new Promise(); 68 | p.Resolve(GetUUID()); 69 | return p; 70 | } 71 | 72 | public static extern(Android) Future AsyncUUIDImpl() { 73 | if (_authorizePromise == null) { 74 | _authorizePromise = new Promise(); 75 | Permissions.Request(Permissions.Android.READ_PHONE_STATE).Then(AuthorizeResolved, AuthorizeRejected); 76 | } 77 | return _authorizePromise; 78 | } 79 | 80 | private static extern(Android) void AuthorizeResolved(PlatformPermission permission) { 81 | if (cachedUUID == null) { 82 | cachedUUID = GetUUID(); 83 | } 84 | _authorizePromise.Resolve(cachedUUID); 85 | } 86 | 87 | private static extern(Android) void AuthorizeRejected(Exception reason) { 88 | _authorizePromise.Reject(reason); 89 | } 90 | 91 | public static extern(!Android) string UUID() { 92 | if (cachedUUID == null) { 93 | cachedUUID = GetUUID(); 94 | } 95 | return cachedUUID; 96 | } 97 | 98 | public static extern(Android) string UUID() { 99 | if (cachedUUID == null) { 100 | debug_log("Permissions not granted. Consider using Device.getUUID() instead."); 101 | return ""; 102 | } 103 | return cachedUUID; 104 | } 105 | 106 | 107 | public static string Vendor() { 108 | if (cachedVendorName == null) { 109 | cachedVendorName = GetVendor(); 110 | } 111 | return cachedVendorName; 112 | } 113 | 114 | public static string Model() { 115 | if (cachedModelName == null) { 116 | cachedModelName = GetModel(); 117 | } 118 | return cachedModelName; 119 | } 120 | 121 | public static string System() { 122 | if (cachedSystemName == null) { 123 | cachedSystemName = GetSystem(); 124 | } 125 | return cachedSystemName; 126 | } 127 | 128 | public static string SystemVersion() { 129 | if (cachedSystemVersion == null) { 130 | cachedSystemVersion = GetSystemVersion(); 131 | } 132 | return cachedSystemVersion; 133 | } 134 | 135 | public static string SDKVersion() { 136 | if (cachedSDKVersion == null) { 137 | cachedSDKVersion = GetSDKVersion(); 138 | } 139 | return cachedSDKVersion; 140 | } 141 | 142 | public static double NumProcessorCores() { 143 | if (cachedNumProcessorCores == 0f) { 144 | cachedNumProcessorCores = (double)GetNumProcessorCores(); 145 | } 146 | return cachedNumProcessorCores; 147 | } 148 | 149 | public static bool IsRetina() { 150 | return Device.PixelsPerPoint() > 1f; 151 | } 152 | 153 | public static double PixelsPerPoint() { 154 | return App.Current.RootViewport.PixelsPerPoint; 155 | } 156 | 157 | 158 | // UUID platform specific implementations 159 | [Foreign(Language.Java)] 160 | [Require("AndroidManifest.RootElement", "")] 161 | [Require("AndroidManifest.RootElement", "")] 162 | [Require("AndroidManifest.RootElement", "")] 163 | private static extern(Android) string GetUUID() 164 | @{ 165 | final android.app.Activity context = com.fuse.Activity.getRootActivity(); 166 | final TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE); 167 | final String deviceId = "" + tm.getDeviceId(); 168 | final String serialNum = "" + tm.getSimSerialNumber(); 169 | final String androidId = "" + android.provider.Settings.Secure.getString( 170 | context.getContentResolver(), 171 | android.provider.Settings.Secure.ANDROID_ID 172 | ); 173 | 174 | int macAdressId; 175 | 176 | try { 177 | // try to get MAC-address via NetworkInterface 178 | final InetAddress ip = InetAddress.getLocalHost(); 179 | final NetworkInterface network = NetworkInterface.getByInetAddress(ip); 180 | macAdressId = network.getHardwareAddress().hashCode(); 181 | } catch (Throwable e) { 182 | // else get MAC-address via WifiManager 183 | final WifiManager wifiManager = (WifiManager)context.getSystemService(Context.WIFI_SERVICE); 184 | final boolean wifiEnabled = wifiManager.isWifiEnabled(); 185 | 186 | if (!wifiEnabled) 187 | wifiManager.setWifiEnabled(true); 188 | 189 | macAdressId = wifiManager.getConnectionInfo().getMacAddress().hashCode(); 190 | 191 | if (!wifiEnabled) 192 | wifiManager.setWifiEnabled(false); 193 | } 194 | 195 | byte[] bytes = ByteBuffer.allocate(16) 196 | .putInt(androidId.hashCode()) 197 | .putInt(macAdressId) 198 | .putInt(serialNum.hashCode()) 199 | .putInt(deviceId.hashCode()) 200 | .array(); 201 | 202 | return UUID.nameUUIDFromBytes(bytes).toString().toUpperCase(); 203 | @} 204 | 205 | [Foreign(Language.ObjC)] 206 | private static extern(iOS) string GetUUID() 207 | @{ 208 | return [[[UIDevice currentDevice] identifierForVendor] UUIDString]; 209 | @} 210 | 211 | 212 | [Foreign(Language.Java)] 213 | public static extern(Android) string GetCurrentLocale() 214 | @{ 215 | Locale loc = java.util.Locale.getDefault(); 216 | 217 | final char separator = '-'; 218 | String language = loc.getLanguage(); 219 | String region = loc.getCountry(); 220 | String variant = loc.getVariant(); 221 | 222 | // special case for Norwegian Nynorsk since "NY" cannot be a variant as per BCP 47 223 | // this goes before the string matching since "NY" wont pass the variant checks 224 | if (language.equals("no") && region.equals("NO") && variant.equals("NY")) { 225 | language = "nn"; 226 | region = "NO"; 227 | variant = ""; 228 | } 229 | 230 | if (language.isEmpty() || !language.matches("\\p{Alpha}{2,8}")) { 231 | language = "und"; // "und" for Undetermined 232 | } else if (language.equals("iw")) { 233 | language = "he"; // correct deprecated "Hebrew" 234 | } else if (language.equals("in")) { 235 | language = "id"; // correct deprecated "Indonesian" 236 | } else if (language.equals("ji")) { 237 | language = "yi"; // correct deprecated "Yiddish" 238 | } 239 | 240 | // ensure valid country code, if not well formed, it's omitted 241 | if (!region.matches("\\p{Alpha}{2}|\\p{Digit}{3}")) { 242 | region = ""; 243 | } 244 | 245 | // variant subtags that begin with a letter must be at least 5 characters long 246 | if (!variant.matches("\\p{Alnum}{5,8}|\\p{Digit}\\p{Alnum}{3}")) { 247 | variant = ""; 248 | } 249 | 250 | StringBuilder bcp47Tag = new StringBuilder(language); 251 | if (!region.isEmpty()) { 252 | bcp47Tag.append(separator).append(region); 253 | } 254 | 255 | if (!variant.isEmpty()) { 256 | bcp47Tag.append(separator).append(variant); 257 | } 258 | 259 | return bcp47Tag.toString(); 260 | @} 261 | 262 | [Foreign(Language.ObjC)] 263 | private static extern(iOS) string GetCurrentLocale() 264 | @{ 265 | NSString* language = NSLocale.preferredLanguages[0]; 266 | 267 | if (language.length <= 2) { 268 | NSLocale* locale = NSLocale.currentLocale; 269 | NSString* localeId = locale.localeIdentifier; 270 | NSRange underscoreIndex = [localeId rangeOfString: @"_" options: NSBackwardsSearch]; 271 | NSRange atSignIndex = [localeId rangeOfString: @"@"]; 272 | 273 | if (underscoreIndex.location != NSNotFound) { 274 | if (atSignIndex.length == 0) 275 | language = [NSString stringWithFormat: @"%@%@", language, [localeId substringFromIndex: underscoreIndex.location]]; 276 | else { 277 | NSRange localeRange = NSMakeRange(underscoreIndex.location, atSignIndex.location - underscoreIndex.location); 278 | language = [NSString stringWithFormat: @"%@%@", language, [localeId substringWithRange: localeRange]]; 279 | } 280 | } 281 | } 282 | 283 | return [language stringByReplacingOccurrencesOfString: @"_" withString: @"-"]; 284 | @} 285 | 286 | // iOS's foreign implementations 287 | 288 | [Foreign(Language.ObjC)] 289 | private static extern(iOS) string GetVendor() 290 | @{ 291 | return @"Apple"; 292 | @} 293 | 294 | [Foreign(Language.ObjC)] 295 | private static extern(iOS) string GetModel() 296 | @{ 297 | size_t hardwareModelSize; 298 | sysctlbyname("hw.machine", NULL, &hardwareModelSize, NULL, 0); 299 | char* hardwareModel = (char*)malloc(hardwareModelSize); 300 | 301 | sysctlbyname("hw.machine", hardwareModel, &hardwareModelSize, NULL, 0); 302 | NSString* model = [NSString stringWithUTF8String: hardwareModel]; 303 | free(hardwareModel); 304 | 305 | return model; 306 | @} 307 | 308 | [Foreign(Language.ObjC)] 309 | private static extern(iOS) string GetSystem() 310 | @{ 311 | return @"iOS"; 312 | @} 313 | 314 | [Foreign(Language.ObjC)] 315 | private static extern(iOS) string GetSystemVersion() 316 | @{ 317 | return UIDevice.currentDevice.systemVersion; 318 | @} 319 | 320 | [Foreign(Language.ObjC)] 321 | private static extern(iOS) string GetSDKVersion() 322 | @{ 323 | return UIDevice.currentDevice.systemVersion; 324 | @} 325 | 326 | [Foreign(Language.ObjC)] 327 | private static extern(iOS) int GetNumProcessorCores() 328 | @{ 329 | uint32_t ncpu = 0; 330 | size_t size = sizeof(uint32_t); 331 | if (sysctlbyname("hw.logicalcpu", &ncpu, &size, NULL, 0) != 0) { 332 | if (sysctlbyname("hw.ncpu", &ncpu, &size, NULL, 0) != 0) { 333 | ncpu = 1; 334 | } 335 | } 336 | 337 | return (int32_t)ncpu; 338 | @} 339 | 340 | // Android's foreign implementations 341 | 342 | [Foreign(Language.Java)] 343 | private static extern(Android) string GetVendor() 344 | @{ 345 | return android.os.Build.MANUFACTURER; 346 | @} 347 | 348 | [Foreign(Language.Java)] 349 | private static extern(Android) string GetModel() 350 | @{ 351 | return android.os.Build.MODEL; 352 | @} 353 | 354 | [Foreign(Language.Java)] 355 | private static extern(Android) string GetSystem() 356 | @{ 357 | if (android.os.Build.MANUFACTURER.equals("Amazon")) { 358 | return "AmazonFireOS"; 359 | } 360 | return "Android"; 361 | @} 362 | 363 | [Foreign(Language.Java)] 364 | private static extern(Android) string GetSystemVersion() 365 | @{ 366 | return android.os.Build.VERSION.RELEASE; 367 | @} 368 | 369 | [Foreign(Language.Java)] 370 | private static extern(Android) string GetSDKVersion() 371 | @{ 372 | return android.os.Build.VERSION.SDK; 373 | @} 374 | 375 | 376 | [Foreign(Language.Java)] 377 | private static extern(Android) int GetNumProcessorCores() 378 | @{ 379 | int cores = 1; 380 | if (android.os.Build.VERSION.SDK_INT >= 17) { 381 | cores = Runtime.getRuntime().availableProcessors(); 382 | } else { 383 | try { 384 | Process proc = Runtime.getRuntime().exec("/usr/bin/nproc"); 385 | BufferedReader input = new BufferedReader(new InputStreamReader(proc.getInputStream())); 386 | 387 | String line; 388 | while ((line = input.readLine()) != null) 389 | cores = line.length() > 0 ? Integer.parseInt(line) : 1; 390 | 391 | input.close(); 392 | proc.waitFor(); 393 | 394 | } catch (Throwable e) {} 395 | } 396 | 397 | // in some devices any method return wrong huge number so we fix that case 398 | if (cores > 8) { 399 | cores = 4; 400 | } 401 | 402 | return cores; 403 | @} 404 | 405 | 406 | // Preview's implementations 407 | 408 | // UUID generate randomly every launch time 409 | private static extern(!Mobile) string GetUUID() { 410 | // According to RFC 4122 version 4 411 | Random rnd = new Random((int)(Time.FrameTime + 34525)); 412 | byte[] bytes = new byte[16]; 413 | const string chars = "abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUWXYZ0123456789"; 414 | int len = chars.Length; 415 | for (int i = 0; i < 16; ++i) 416 | bytes[i] = (byte)(chars[rnd.NextInt(len)]); 417 | 418 | bytes[6] = (bytes[6] & 0xF) | 0x40; 419 | bytes[8] = (bytes[8] & 0x3F) | 0x80; 420 | 421 | StringBuilder result = new StringBuilder(); 422 | for (int i = 0; i < 16; ++i) 423 | result.Append(String.Format("{0:X}", bytes[i])); 424 | 425 | return result.ToString().Insert(8, "-").Insert(13, "-") 426 | .Insert(18, "-").Insert(23, "-"); 427 | } 428 | 429 | public static extern(!Mobile) string GetCurrentLocale() { 430 | return "en-EN"; 431 | } 432 | 433 | private static extern(!Mobile) string GetVendor() { 434 | return "Fusetools"; 435 | } 436 | 437 | private static extern(!Mobile) string GetModel() { 438 | return "Preview"; 439 | } 440 | 441 | private static extern(!Mobile) string GetSystem() { 442 | return "Fuse"; 443 | } 444 | 445 | private static extern(!Mobile) string GetSystemVersion() { 446 | return ""; 447 | } 448 | 449 | private static extern(!Mobile) string GetSDKVersion() { 450 | return ""; 451 | } 452 | 453 | private static extern(!Mobile) uint GetNumProcessorCores() { 454 | return 1; 455 | } 456 | } 457 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Maxim Shaydo aka MaxGraey 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | fuse-device 2 | ============ 3 | 4 | Use the basic Device functions such as UUID and current localization from Fuse. 5 | 6 | 7 | ## Installation 8 | 9 | Using [fusepm](https://github.com/bolav/fusepm) 10 | 11 | $ fusepm install https://github.com/MaxGraey/fuse-device 12 | 13 | Or manually copy **Device.uno** to your project and add link in .unoproj [see example](https://github.com/MaxGraey/fuse-device/tree/master/example) 14 | 15 | ### Support 16 | - **iOS** 17 | - **Android** 18 | - **Preview** 19 | 20 | 21 | ### JavaScript 22 | 23 | ```js 24 | var Device = require('Device'); 25 | 26 | console.log('Current device language: ' + Device.locale); // format in BCP-47 for all mobile platforms 27 | // output example: en-US 28 | 29 | console.log('UUID:' + Device.UUID); 30 | // output example: 31 | // UUID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX 32 | 33 | console.log('Vendor name: ' + Device.vendor); 34 | console.log('Model name: ' + Device.model); 35 | console.log('System: ' + Device.system); 36 | console.log('System version: ' + Device.systemVersion); 37 | console.log('System SDK ver: ' + Device.SDKVersion); 38 | console.log('Logical processors: ' + Device.cores); 39 | console.log('is retina?: ' + Device.isRetina); 40 | ``` 41 | 42 | Since reading UUID in Android requires a run-time permission to `READ_PHONE_STATE`, 43 | the UUID might not always be accessible via `Device.UUID` directly. To work around that, 44 | there is a `getUUID()` method that returns the UUID in a promise. 45 | 46 | For convenience, `getUUID()` is available on all target platforms, but it only triggers the permission request on Android. 47 | 48 | ```js 49 | var Device = require('Device'); 50 | if (Device.UUID == '') { 51 | Device.getUUID().then(function(uuid) { 52 | console.log('UUID: ' + uuid); 53 | // output example: 54 | // UUID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX 55 | }).catch(function(error) { 56 | console.log('UUID error: ' + error); 57 | // output example: 58 | // UUID error: Permissions could not be requested or granted. 59 | }); 60 | } 61 | ``` 62 | -------------------------------------------------------------------------------- /device_include.unoproj: -------------------------------------------------------------------------------- 1 | { 2 | "RootNamespace": "", 3 | "Packages": [ 4 | "Fuse", 5 | "FuseJS", 6 | "Uno.Permissions" 7 | ], 8 | "Includes": [ 9 | "*.uno" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /example/Assets/background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MaxGraey/fuse-device/55f4c7cd62bdbcba9e054e22fa4dd17322f9f4f8/example/Assets/background.jpg -------------------------------------------------------------------------------- /example/Device-package.unoproj: -------------------------------------------------------------------------------- 1 | { 2 | "Title": "Device Package", 3 | "Name": "devicepackage", 4 | "Version": "0.0.1", 5 | "RootNamespace": "", 6 | "Copyright": "Copyright © 2016 $(Publisher)", 7 | "Publisher": "Maxim Shaydo", 8 | "Packages": [ 9 | "Fuse", 10 | "FuseJS", 11 | "Uno.Permissions" 12 | ], 13 | "Includes": [ 14 | "*", 15 | "../Device.uno" 16 | ], 17 | "Mobile": { 18 | "Orientations": "PortraitUpsideDown" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /example/MainView.ux: -------------------------------------------------------------------------------- 1 | 2 | 3 | var Observable = require('FuseJS/Observable'); 4 | var Device = require('Device'); 5 | 6 | console.log('UUID:', Device.UUID); 7 | console.log('locale:', Device.locale); 8 | console.log('system:', Device.system + " " + Device.systemVersion); 9 | console.log('SDK:', Device.SDKVersion); 10 | console.log('device:', Device.vendor + " " + Device.model); 11 | console.log('cores:', Device.cores); 12 | console.log('retina:', Device.isRetina); 13 | 14 | var asyncUUID = Observable(""); 15 | setTimeout(function() { 16 | if (Device.UUID == '') { 17 | Device.getUUID().then(function(uuid) { 18 | console.log('getUUID: ' + uuid); 19 | asyncUUID.value = uuid; 20 | }).catch(function(error) { 21 | console.log('getUUID error: ' + error); 22 | }); 23 | } else { 24 | asyncUUID.value = Device.UUID; 25 | } 26 | }, 2000); 27 | 28 | module.exports = { 29 | name: Device.vendor + " " + Device.model, 30 | UUID: Device.UUID, 31 | asyncUUID: asyncUUID, 32 | locale: Device.locale, 33 | cores: Device.cores, 34 | isRetina: Device.isRetina 35 | }; 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /example/devices.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "Name" : "iPhone 6", 4 | "Width" : 750, 5 | "Height" : 1334, 6 | "PixelsPerPoint": 2, 7 | "PhysicalPixelsPerInch" : 326, 8 | "IsDefault" : true 9 | }, 10 | { 11 | "Name" : "iPhone 6 Plus", 12 | "Width" : 1242, 13 | "Height" : 2208, 14 | "PixelsPerPoint" : 3, 15 | "PhysicalPixelsPerInch" : 401, 16 | "PhysicalPixelsPerPixel" : 0.8695652173913 17 | }, 18 | { 19 | "Name" : "iPhone 5/5S", 20 | "Width" : 640, 21 | "Height" : 1136, 22 | "PixelsPerPoint" : 2, 23 | "PhysicalPixelsPerInch" : 326 24 | }, 25 | { 26 | "Name" : "iPhone 4/4S", 27 | "Width" : 640, 28 | "Height" : 960, 29 | "PixelsPerPoint" : 2, 30 | "PhysicalPixelsPerInch" : 326 31 | }, 32 | { 33 | "Name" : "iPad Retina", 34 | "Width" : 2048, 35 | "Height" : 1536, 36 | "PixelsPerPoint" : 2, 37 | "PhysicalPixelsPerInch" : 264, 38 | "DefaultOrientation": "Landscape" 39 | }, 40 | { 41 | "Name" : "Sony Xperia Z3", 42 | "Width" : 1080, 43 | "Height" : 1920, 44 | "PixelsPerPoint": 3, 45 | "PhysicalPixelsPerInch": 423.64, 46 | "DefaultOrientation": "Landscape" 47 | }, 48 | { 49 | "Name" : "Samsung Galaxy S6 Edge", 50 | "Width" : 1440, 51 | "Height" : 2560, 52 | "PixelsPerPoint": 3, 53 | "PhysicalPixelsPerInch": 577 54 | }, 55 | { 56 | "Name" : "Google Nexus 9", 57 | "Width" : 2048, 58 | "Height" : 1536, 59 | "PixelsPerPoint": 1.5, 60 | "PhysicalPixelsPerInch": 281, 61 | "DefaultOrientation": "Landscape" 62 | }, 63 | ] --------------------------------------------------------------------------------