From 415066dea50f0ae53fc6292c93874aa7494cad1b Mon Sep 17 00:00:00 2001 From: Gabriel Wong Date: Thu, 23 Nov 2017 14:32:16 +0700 Subject: [PATCH 01/22] Create Version type --- .../KeymanEngine.xcodeproj/project.pbxproj | 4 ++ .../KMEI/KeymanEngine/Classes/Manager.swift | 47 ++++-------------- .../KeymanEngine/Classes/Model/Version.swift | 48 +++++++++++++++++++ 3 files changed, 62 insertions(+), 37 deletions(-) create mode 100644 ios/engine/KMEI/KeymanEngine/Classes/Model/Version.swift diff --git a/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj b/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj index 9bccbdf568..df242a2b1b 100644 --- a/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj +++ b/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj @@ -44,6 +44,7 @@ C040E5101F8606E300901EE4 /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C040E50F1F8606E300901EE4 /* TextField.swift */; }; C040E5121F86107E00901EE4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C040E5111F86107E00901EE4 /* AppDelegate.swift */; }; C040E5141F86108900901EE4 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C040E5131F86108900901EE4 /* MainViewController.swift */; }; + C042ED5D1FC6A65A001D82F4 /* Version.swift in Sources */ = {isa = PBXBuildFile; fileRef = C042ED5C1FC6A65A001D82F4 /* Version.swift */; }; C04514881F85D7F500D88416 /* KeyboardViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04514871F85D7F500D88416 /* KeyboardViewController.swift */; }; C045148A1F85DF9100D88416 /* InputViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04514891F85DF9000D88416 /* InputViewController.swift */; }; C0452BAB1F9F1FE10064431A /* Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0452BAA1F9F1FE10064431A /* Language.swift */; }; @@ -196,6 +197,7 @@ C040E50F1F8606E300901EE4 /* TextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextField.swift; sourceTree = ""; }; C040E5111F86107E00901EE4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C040E5131F86108900901EE4 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; }; + C042ED5C1FC6A65A001D82F4 /* Version.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Version.swift; sourceTree = ""; }; C04514861F85D7F500D88416 /* SystemKeyboard-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SystemKeyboard-Bridging-Header.h"; sourceTree = ""; }; C04514871F85D7F500D88416 /* KeyboardViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardViewController.swift; sourceTree = ""; }; C04514891F85DF9000D88416 /* InputViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InputViewController.swift; sourceTree = ""; }; @@ -442,6 +444,7 @@ C055E6EA1F99ED090035C2DD /* RegisteredFont.swift */, C007C4641F9F52D8006461B9 /* LanguagesAPICall.swift */, C05F432C1FBD5A4C0058CBD4 /* KeyboardAPICall.swift */, + C042ED5C1FC6A65A001D82F4 /* Version.swift */, ); path = Model; sourceTree = ""; @@ -833,6 +836,7 @@ C06D37341F81F5C300F61AE0 /* HTTPDownloader.swift in Sources */, C0452BAF1F9F22A80064431A /* Font.swift in Sources */, C0D3F3601F9F3AD80055C7CF /* InstallableKeyboard.swift in Sources */, + C042ED5D1FC6A65A001D82F4 /* Version.swift in Sources */, C0D3F35E1F9F33490055C7CF /* Options.swift in Sources */, C06D37351F81F5C300F61AE0 /* HTTPDownloadRequest.swift in Sources */, C0324B8F1F8750B700AF3785 /* TextView.swift in Sources */, diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift index 5bd71d477a..e79ce8f83d 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift @@ -555,8 +555,9 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat } // Check version - if let latestRepositoryVersion = apiKeyboardRepository.keyboards?[keyboardID]?.version, - compareVersions(latestDownloadedVersion, latestRepositoryVersion) == .orderedAscending { + if let latestRepositoryVersionString = apiKeyboardRepository.keyboards?[keyboardID]?.version, + let latestRepositoryVersion = Version(latestRepositoryVersionString), + latestDownloadedVersion < latestRepositoryVersion { return .needsUpdate } return .upToDate @@ -710,58 +711,30 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat } // MARK: - File system and UserData management - func latestKeyboardFileVersion(withID keyboardID: String) -> String? { + func latestKeyboardFileVersion(withID keyboardID: String) -> Version? { guard let dirContents = try? FileManager.default.contentsOfDirectory(atPath: Storage.active.languageDir.path) else { return nil } - var latestVersion: String? + var latestVersion: Version? for filename in dirContents where filename.hasPrefix("\(keyboardID)-") && filename.hasJavaScriptExtension { let dashRange = filename.range(of: "-", options: .backwards)! let extensionRange = filename.range(of: ".js", options: .backwards)! - let version = String(filename[dashRange.upperBound.. previousMax { latestVersion = version } - } else if compareVersions(version, version) != nil { // Ensure that the version number is valid + } else { latestVersion = version } } return latestVersion } - /// Compares version numbers in dotted numberic format. - /// - Returns: ComparisonResult if both version numbers are valid. - func compareVersions(_ v1: String, _ v2: String) -> ComparisonResult? { - if v1.isEmpty || v2.isEmpty { - return nil - } - let components1 = v1.components(separatedBy: ".") - let components2 = v2.components(separatedBy: ".") - - let len = max(components1.count, components2.count) - for i in 0..= 0 else { - return nil - } - guard let val2 = Int(component2), val2 >= 0 else { - return nil - } - if val1 < val2 { - return .orderedAscending - } - if val1 > val2 { - return .orderedDescending - } - } - return .orderedSame - } - /// Updates the user's installed keyboards and current keyboard with information in newKeyboard. /// - Parameter newKeyboard: Info for updated keyboard. func updateUserKeyboards(with newKeyboard: InstallableKeyboard) { diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Model/Version.swift b/ios/engine/KMEI/KeymanEngine/Classes/Model/Version.swift new file mode 100644 index 0000000000..b5cff44d35 --- /dev/null +++ b/ios/engine/KMEI/KeymanEngine/Classes/Model/Version.swift @@ -0,0 +1,48 @@ +// +// Version.swift +// KeymanEngine +// +// Created by Gabriel Wong on 2017-11-23. +// Copyright © 2017 SIL International. All rights reserved. +// + +import Foundation + +/// Dotted-decimal version. +public struct Version: Comparable { + private let components: [Int] + public let string: String + + public init?(_ string: String) { + let stringComponents = string.components(separatedBy: ".") + var components: [Int] = [] + for s in stringComponents { + guard let i = Int(s), i >= 0 else { + return nil + } + components.append(i) + } + + self.string = string + self.components = components + } + + public static func <(lhs: Version, rhs: Version) -> Bool { + let len = max(lhs.components.count, rhs.components.count) + for i in 0.. rightComponent { + return false + } + } + return false + } + + public static func ==(lhs: Version, rhs: Version) -> Bool { + return lhs.components == rhs.components + } +} From b52b672005dfa038ed7be95098423d7879daface Mon Sep 17 00:00:00 2001 From: Gabriel Wong Date: Fri, 8 Dec 2017 12:31:22 +0700 Subject: [PATCH 02/22] Create Migration to KMP directory structure --- .../KeymanEngine.xcodeproj/project.pbxproj | 4 + .../Classes/KeyboardMenuView.swift | 6 +- .../KMEI/KeymanEngine/Classes/Manager.swift | 38 ++----- .../KeymanEngine/Classes/Migrations.swift | 105 ++++++++++++++++++ .../KeymanEngine/Classes/Model/Version.swift | 2 + .../KMEI/KeymanEngine/Classes/Storage.swift | 16 ++- 6 files changed, 132 insertions(+), 39 deletions(-) create mode 100644 ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift diff --git a/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj b/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj index df242a2b1b..dbc8e32d26 100644 --- a/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj +++ b/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj @@ -88,6 +88,7 @@ C07A9D8C1FD176AB00828ADD /* KeyboardRepositoryDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A9D8B1FD176AB00828ADD /* KeyboardRepositoryDelegate.swift */; }; C07A9D8E1FD1798900828ADD /* APIKeyboardRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A9D8D1FD1798900828ADD /* APIKeyboardRepository.swift */; }; C082CE151F90AFD400860F02 /* Collection+SafeAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = C082CE141F90AFD400860F02 /* Collection+SafeAccess.swift */; }; + C08E698D1FDA392D0026056B /* Migrations.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08E698C1FDA392D0026056B /* Migrations.swift */; }; C0959CD41F99C44E00B616BC /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0959CD31F99C44E00B616BC /* Constants.swift */; }; C0A93A541F8B21240079948B /* Manager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A93A531F8B21240079948B /* Manager.swift */; }; C0B09EAE1FCFD10F002F39AF /* FontManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0B09EAD1FCFD10F002F39AF /* FontManager.swift */; }; @@ -224,6 +225,7 @@ C08C62121F67C31100268D03 /* KeyboardNameTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardNameTableViewCell.swift; sourceTree = ""; }; C08C62141F67C8D500268D03 /* KeyboardInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardInfoViewController.swift; sourceTree = ""; }; C08C62161F67CFB800268D03 /* KeyboardPickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardPickerViewController.swift; sourceTree = ""; }; + C08E698C1FDA392D0026056B /* Migrations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Migrations.swift; sourceTree = ""; }; C092D8381F6A70C8005C5485 /* LanguageDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LanguageDetailViewController.swift; sourceTree = ""; }; C0959CD31F99C44E00B616BC /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; C0A5FF361F6682EB00BE740C /* PopoverView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PopoverView.swift; sourceTree = ""; }; @@ -591,6 +593,7 @@ C0E30C8B1FC40D0400C80416 /* Storage.swift */, C0EF3E7A1F95B65300CE9BD4 /* KeymanWebDelegate.swift */, C05B14321FD914870082A316 /* Log.swift */, + C08E698C1FDA392D0026056B /* Migrations.swift */, ); path = Classes; sourceTree = ""; @@ -840,6 +843,7 @@ C0D3F35E1F9F33490055C7CF /* Options.swift in Sources */, C06D37351F81F5C300F61AE0 /* HTTPDownloadRequest.swift in Sources */, C0324B8F1F8750B700AF3785 /* TextView.swift in Sources */, + C08E698D1FDA392D0026056B /* Migrations.swift in Sources */, C07A9D8C1FD176AB00828ADD /* KeyboardRepositoryDelegate.swift in Sources */, C024C9961FA6EC650060583B /* NotificationCenter+Typed.swift in Sources */, C06D37361F81F5C300F61AE0 /* KeyboardMenuView.swift in Sources */, diff --git a/ios/engine/KMEI/KeymanEngine/Classes/KeyboardMenuView.swift b/ios/engine/KMEI/KeymanEngine/Classes/KeyboardMenuView.swift index caa0def4f0..8b4a15b065 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/KeyboardMenuView.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/KeyboardMenuView.swift @@ -49,11 +49,7 @@ class KeyboardMenuView: UIView, UITableViewDelegate, UITableViewDataSource, UIGe _tableList = keyboardList _tableList!.append(titleCloseButton) } else { - var keyboard = Defaults.keyboard - if let version = Manager.shared.latestKeyboardFileVersion(withID: Defaults.keyboard.id) { - keyboard.version = version - } - _tableList = [keyboard] + _tableList = [Defaults.keyboard] _tableList!.append(titleCloseButton) } return _tableList! diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift index e79ce8f83d..993df4078f 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift @@ -522,7 +522,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat return } - let isUpdate = latestKeyboardFileVersion(withID: keyboard.id) != nil + let isUpdate = Storage.active.userDefaults.userKeyboards?.contains { $0.id == keyboard.id } ?? false downloadQueue = HTTPDownloader.init(self) let commonUserData: [String: Any] = [ @@ -550,15 +550,18 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat if keyboardIdForCurrentRequest() == keyboardID { return .downloading } - guard let latestDownloadedVersion = latestKeyboardFileVersion(withID: keyboardID) else { + let userKeyboards = Storage.active.userDefaults.userKeyboards + guard let userKeyboard = userKeyboards?.first(where: { $0.id == keyboardID }) else { return .needsDownload } // Check version - if let latestRepositoryVersionString = apiKeyboardRepository.keyboards?[keyboardID]?.version, - let latestRepositoryVersion = Version(latestRepositoryVersionString), - latestDownloadedVersion < latestRepositoryVersion { - return .needsUpdate + if let repositoryVersionString = apiKeyboardRepository.keyboards?[keyboardID]?.version { + let downloadedVersion = Version(userKeyboard.version) ?? Version.fallback + let repositoryVersion = Version(repositoryVersionString) ?? Version.fallback + if downloadedVersion < repositoryVersion { + return .needsUpdate + } } return .upToDate } @@ -711,29 +714,6 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat } // MARK: - File system and UserData management - func latestKeyboardFileVersion(withID keyboardID: String) -> Version? { - guard let dirContents = try? FileManager.default.contentsOfDirectory(atPath: Storage.active.languageDir.path) else { - return nil - } - - var latestVersion: Version? - for filename in dirContents where filename.hasPrefix("\(keyboardID)-") && filename.hasJavaScriptExtension { - let dashRange = filename.range(of: "-", options: .backwards)! - let extensionRange = filename.range(of: ".js", options: .backwards)! - guard let version = Version(String(filename[dashRange.upperBound.. previousMax { - latestVersion = version - } - } else { - latestVersion = version - } - } - return latestVersion - } /// Updates the user's installed keyboards and current keyboard with information in newKeyboard. /// - Parameter newKeyboard: Info for updated keyboard. diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift b/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift new file mode 100644 index 0000000000..0b7a362f78 --- /dev/null +++ b/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift @@ -0,0 +1,105 @@ +// +// Migrations.swift +// KeymanEngine +// +// Created by Gabriel Wong on 2017-12-08. +// Copyright © 2017 SIL International. All rights reserved. +// + +import Foundation + +enum Migrations { + static func migrateForKMP(storage: Storage) { + log.verbose("Migrating from base directory: \(storage.baseDir)") + + guard var userKeyboards = storage.userDefaults.userKeyboards else { + log.info("No user keyboards to migrate") + return + } + + var urlsForKeyboard: [String: Set] = [:] + for i in userKeyboards.indices { + let keyboard = userKeyboards[i] + guard let version = latestKeyboardFileVersion(withID: keyboard.id, dirPath: storage.languageDir.path) else { + log.warning("Could not find JS file for keyboard \(keyboard.id) in \(storage.languageDir.path)") + continue + } + var urls = urlsForKeyboard[keyboard.id] ?? Set() + urls.insert(storage.languageDir.appendingPathComponent("\(keyboard.id)-\(version.string).js")) + + let fontFiles = (keyboard.font?.source ?? []) + (keyboard.oskFont?.source ?? []) + for file in fontFiles { + guard file.hasFontExtension else { + log.info("Skipping copy of \(file) for keyboard \(keyboard.id) since it is not a font file.") + continue + } + let url = storage.fontDir.appendingPathComponent(file) + guard FileManager.default.fileExists(atPath: url.path) else { + log.warning("Font file \(url) for keyboard \(keyboard.id) does not exist") + continue + } + urls.insert(url) + } + urlsForKeyboard[keyboard.id] = urls + userKeyboards[i].version = version.string + } + + var successfulKeyboards: [String] = [] + + // Copy files + for (keyboardID, urls) in urlsForKeyboard { + let keyboardDir = storage.keyboardDir(forID: keyboardID) + do { + try FileManager.default.createDirectory(at: keyboardDir, + withIntermediateDirectories: true, + attributes: nil) + } catch { + log.error("Failed to create keyboard directory at \(keyboardDir)") + continue + } + + var successful = true + for srcURL in urls { + let dstURL = keyboardDir.appendingPathComponent(srcURL.lastPathComponent) + do { + try FileManager.default.copyItem(at: srcURL, to: dstURL) + } catch { + log.error("Failed to copy from \(srcURL) to \(dstURL) for keyboard \(keyboardID)") + successful = false + } + } + if successful { + successfulKeyboards.append(keyboardID) + log.info("Succesfully copied keyboard files for keyboard \(keyboardID)") + } + } + + // Remove keyboards that were not copied successfully + let filteredUserKeyboards = userKeyboards.filter { successfulKeyboards.contains($0.id) } + storage.userDefaults.userKeyboards = filteredUserKeyboards + + // TODO: Remove old directory + } + + private static func latestKeyboardFileVersion(withID keyboardID: String, dirPath: String) -> Version? { + guard let dirContents = try? FileManager.default.contentsOfDirectory(atPath: dirPath) else { + return nil + } + + var latestVersion: Version? + for filename in dirContents where filename.hasPrefix("\(keyboardID)-") && filename.hasJavaScriptExtension { + let dashRange = filename.range(of: "-", options: .backwards)! + let extensionRange = filename.range(of: ".js", options: .backwards)! + guard let version = Version(String(filename[dashRange.upperBound.. URL? { - guard let baseDir = baseDir else { - return nil - } + private static func createSubdirectory(baseDir: URL, name: String) -> URL? { let newDir = baseDir.appendingPathComponent(name) do { try FileManager.default.createDirectory(at: newDir, @@ -79,6 +80,7 @@ class Storage { attributes: nil) return newDir } catch { + log.error("Failed to create subdirectory at \(newDir)") return nil } } @@ -94,6 +96,10 @@ class Storage { func fontURL(forFilename filename: String) -> URL { return fontDir.appendingPathComponent(filename) } + + func keyboardDir(forID keyboardID: String) -> URL { + return keyboardDir.appendingPathComponent(keyboardID) + } } // MARK: - Copying From 8ba393e034e4029600471a6622dac6d67d90d28f Mon Sep 17 00:00:00 2001 From: Gabriel Wong Date: Fri, 8 Dec 2017 13:34:50 +0700 Subject: [PATCH 03/22] Use migrated directory structure --- .../KeymanEngine/Classes/FontManager.swift | 12 ++- .../Classes/KeymanWebViewController.swift | 35 +++++---- .../KMEI/KeymanEngine/Classes/Manager.swift | 76 +++++-------------- .../KeymanEngine/Classes/Migrations.swift | 13 ++-- .../KMEI/KeymanEngine/Classes/Storage.swift | 66 ++++++++++------ 5 files changed, 98 insertions(+), 104 deletions(-) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/FontManager.swift b/ios/engine/KMEI/KeymanEngine/Classes/FontManager.swift index 9871be02bc..75c7f87afc 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/FontManager.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/FontManager.swift @@ -49,15 +49,19 @@ public class FontManager { } private func customFonts() -> [URL]? { - let urls: [URL] + guard let keyboardDirs = Storage.active.keyboardDirs else { + return nil + } + do { - urls = try FileManager.default.contentsOfDirectory(at: Storage.active.fontDir, - includingPropertiesForKeys: nil) + let urls = try keyboardDirs.flatMap { + return try FileManager.default.contentsOfDirectory(at: $0, includingPropertiesForKeys: nil) + } + return urls.filter { $0.lastPathComponent.hasFontExtension } } catch { log.error("Failed to list font dir contents: \(error)") return nil } - return urls.filter { $0.lastPathComponent.hasFontExtension } } private func readFontName(at url: URL) -> String? { diff --git a/ios/engine/KMEI/KeymanEngine/Classes/KeymanWebViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/KeymanWebViewController.swift index 549c9870cf..612fc756b3 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/KeymanWebViewController.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/KeymanWebViewController.swift @@ -11,6 +11,7 @@ import WebKit // MARK: - UIViewController class KeymanWebViewController: UIViewController { + let storage: Storage weak var delegate: KeymanWebDelegate? var webView: WKWebView! @@ -22,6 +23,15 @@ class KeymanWebViewController: UIViewController { } } + init(storage: Storage) { + self.storage = storage + super.init(nibName: nil, bundle: nil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func loadView() { let config = WKWebViewConfiguration() let prefs = WKPreferences() @@ -121,26 +131,20 @@ extension KeymanWebViewController { // family does not have to match the name in the font file. It only has to be unique. return [ "family": "\(keyboardID)__\(isOsk ? "osk" : "display")", - "files": font.source + "files": font.source.map { storage.fontURL(forKeyboardID: keyboardID, filename: $0).absoluteString } ] } - func setKeyboard(id: String, - name: String, - languageID: String, - languageName: String, - fileURL: URL, - font: Font?, - oskFont: Font?) { + func setKeyboard(_ keyboard: InstallableKeyboard) { var stub: [String: Any] = [ - "KI": "Keyboard_\(id)", - "KN": name, - "KLC": languageID, - "KL": languageName, - "KF": fileURL.absoluteString + "KI": "Keyboard_\(keyboard.id)", + "KN": keyboard.name, + "KLC": keyboard.languageID, + "KL": keyboard.languageName, + "KF": storage.keyboardURL(for: keyboard).absoluteString ] - let displayFont = fontObject(from: font, keyboardID: id, isOsk: false) - let oskFont = fontObject(from: oskFont, keyboardID: id, isOsk: true) ?? displayFont + let displayFont = fontObject(from: keyboard.font, keyboardID: keyboard.id, isOsk: false) + let oskFont = fontObject(from: keyboard.oskFont, keyboardID: keyboard.id, isOsk: true) ?? displayFont if let displayFont = displayFont { stub["KFont"] = displayFont } @@ -160,6 +164,7 @@ extension KeymanWebViewController { return } + log.debug("Keyboard stub: \(stubString)") webView.evaluateJavaScript("setKeymanLanguage(\(stubString));", completionHandler: nil) } } diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift index 993df4078f..3ec1de5a74 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift @@ -139,24 +139,9 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat URLProtocol.registerClass(KeymanURLProtocol.self) - if !Util.isSystemKeyboard { - if let shared = Storage.shared, - let nonShared = Storage.nonShared { - let keysToCopy = [Key.userKeyboardsList, Key.userCurrentKeyboard, - Key.engineVersion, Key.keyboardPickerDisplayed] - nonShared.copyUserDefaults(to: shared, withKeys: keysToCopy, shouldOverwrite: false) - do { - try nonShared.copyFiles(to: shared) - } catch { - log.error("Failed to copy files to shared container: \(error)") - } - } - let userData = Storage.active.userDefaults - let isKPDisplayed = userData.bool(forKey: Key.keyboardPickerDisplayed) - if isKPDisplayed { - isKeymanHelpOn = false - } - } else { + Migrations.migrateForKMP(storage: Storage.active) + + if Util.isSystemKeyboard || Storage.active.userDefaults.bool(forKey: Key.keyboardPickerDisplayed) { isKeymanHelpOn = false } @@ -173,7 +158,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat updateUserKeyboards(with: Defaults.keyboard) - keymanWeb = KeymanWebViewController(nibName: nil, bundle: nil) + keymanWeb = KeymanWebViewController(storage: Storage.active) keymanWeb.frame = CGRect(origin: .zero, size: keyboardSize) keymanWeb.delegate = self reloadKeyboard(in: keymanWeb) @@ -228,14 +213,13 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat self.keyboardID = kb.id if let fontFilename = kb.font?.source.first(where: { $0.hasFontExtension }) { - _ = FontManager.shared.registerFont(at: Storage.active.fontURL(forFilename: fontFilename)) + _ = FontManager.shared.registerFont(at: Storage.active.fontURL(forKeyboardID: kb.id, filename: fontFilename)) } if let oskFontFilename = kb.oskFont?.source.first(where: { $0.hasFontExtension }) { - _ = FontManager.shared.registerFont(at: Storage.active.fontURL(forFilename: oskFontFilename)) + _ = FontManager.shared.registerFont(at: Storage.active.fontURL(forKeyboardID: kb.id, filename: oskFontFilename)) } - keymanWeb.setKeyboard(id: kb.id, name: kb.name, languageID: kb.languageID, languageName: kb.languageName, - fileURL: Storage.active.keyboardURL(for: kb), font: kb.font, oskFont: kb.oskFont) + keymanWeb.setKeyboard(kb) let userData = Util.isSystemKeyboard ? UserDefaults.standard : Storage.active.userDefaults @@ -354,7 +338,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat public func fontNameForKeyboard(withID keyboardID: String, languageID: String) -> String? { let kb = Storage.active.userDefaults.userKeyboard(withID: keyboardID, languageID: languageID) if let filename = kb?.font?.source.first(where: { $0.hasFontExtension }) { - let fontURL = Storage.active.fontURL(forFilename: filename) + let fontURL = Storage.active.fontURL(forKeyboardID: keyboardID, filename: filename) return FontManager.shared.fontName(at: fontURL) } return nil @@ -366,7 +350,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat func oskFontNameForKeyboard(withID keyboardID: String, languageID: String) -> String? { let kb = Storage.active.userDefaults.userKeyboard(withID: keyboardID, languageID: languageID) if let filename = kb?.oskFont?.source.first(where: { $0.hasFontExtension }) { - let fontURL = Storage.active.fontURL(forFilename: filename) + let fontURL = Storage.active.fontURL(forKeyboardID: keyboardID, filename: filename) return FontManager.shared.fontName(at: fontURL) } return nil @@ -451,7 +435,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat for (i, url) in fontURLs.enumerated() { request = HTTPDownloadRequest(url: url, userInfo: commonUserData) - request.destinationFile = Storage.active.fontURL(forFilename: url.lastPathComponent).path + request.destinationFile = Storage.active.fontURL(forKeyboardID: keyboardID, filename: url.lastPathComponent).path request.tag = i + 1 downloadQueue!.addRequest(request) } @@ -538,7 +522,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat downloadQueue!.addRequest(request) for (i, url) in fontURLs.enumerated() { request = HTTPDownloadRequest(url: url, userInfo: commonUserData) - request.destinationFile = Storage.active.fontURL(forFilename: url.lastPathComponent).path + request.destinationFile = Storage.active.fontURL(forKeyboardID: keyboard.id, filename: url.lastPathComponent).path request.tag = i + 1 downloadQueue!.addRequest(request) } @@ -598,16 +582,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat // MARK: - HTTPDownloadDelegate methods - func downloadQueueFinished(_ queue: HTTPDownloader) { - log.debug { - let fontContents = try? FileManager.default.contentsOfDirectory(atPath: Storage.active.fontDir.path) - return "Font Directory contents: \(fontContents ?? [])" - } - log.debug { - let langContents = try? FileManager.default.contentsOfDirectory(atPath: Storage.active.languageDir.path) - return "Language Directory contents: \(langContents ?? [])" - } - } + func downloadQueueFinished(_ queue: HTTPDownloader) { } func downloadRequestStarted(_ request: HTTPDownloadRequest) { // If we're downloading a new keyboard. @@ -692,25 +667,14 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat } // MARK: - Loading custom keyboards - - /// Preloads a .js file for a language so that the keyboard is available without downloading. - /// - Precondition: - /// - The .js filename must remain the same as when obtained from Keyman. - /// - The .js file must be bundled in your application. - public func preloadKeyboardFile(at url: URL, shouldOverwrite: Bool) throws { - try Storage.copyAndExcludeFromBackup(at: url, - to: Storage.active.languageDir.appendingPathComponent(url.lastPathComponent), - shouldOverwrite: shouldOverwrite) - } - - /// Preloads a .ttf or .otf file to be available without downloading. - /// - Precondition: - /// - The font file must be bundled in your application. - /// - SeeAlso: `registerCustomFonts()` - public func preloadFontFile(at url: URL, shouldOverwrite: Bool) throws { - try Storage.copyAndExcludeFromBackup(at: url, - to: Storage.active.fontDir.appendingPathComponent(url.lastPathComponent), - shouldOverwrite: shouldOverwrite) + /// Preloads the JS and font files required for a keyboard. + public func preloadFiles(forKeyboardID keyboardID: String, at urls: [URL], shouldOverwrite: Bool) throws { + let keyboardDir = Storage.active.keyboardDir(forID: keyboardID) + for url in urls { + try Storage.copyAndExcludeFromBackup(at: url, + to: keyboardDir.appendingPathComponent(url.lastPathComponent), + shouldOverwrite: shouldOverwrite) + } } // MARK: - File system and UserData management diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift b/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift index 0b7a362f78..fdb4e3a893 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift @@ -10,7 +10,10 @@ import Foundation enum Migrations { static func migrateForKMP(storage: Storage) { - log.verbose("Migrating from base directory: \(storage.baseDir)") + let languageDir = storage.baseDir.appendingPathComponent("languages") + let fontDir = storage.baseDir.appendingPathComponent("fonts") + + log.info("Migrating from base directory: \(storage.baseDir)") guard var userKeyboards = storage.userDefaults.userKeyboards else { log.info("No user keyboards to migrate") @@ -20,12 +23,12 @@ enum Migrations { var urlsForKeyboard: [String: Set] = [:] for i in userKeyboards.indices { let keyboard = userKeyboards[i] - guard let version = latestKeyboardFileVersion(withID: keyboard.id, dirPath: storage.languageDir.path) else { - log.warning("Could not find JS file for keyboard \(keyboard.id) in \(storage.languageDir.path)") + guard let version = latestKeyboardFileVersion(withID: keyboard.id, dirPath: languageDir.path) else { + log.warning("Could not find JS file for keyboard \(keyboard.id) in \(languageDir.path)") continue } var urls = urlsForKeyboard[keyboard.id] ?? Set() - urls.insert(storage.languageDir.appendingPathComponent("\(keyboard.id)-\(version.string).js")) + urls.insert(languageDir.appendingPathComponent("\(keyboard.id)-\(version.string).js")) let fontFiles = (keyboard.font?.source ?? []) + (keyboard.oskFont?.source ?? []) for file in fontFiles { @@ -33,7 +36,7 @@ enum Migrations { log.info("Skipping copy of \(file) for keyboard \(keyboard.id) since it is not a font file.") continue } - let url = storage.fontDir.appendingPathComponent(file) + let url = fontDir.appendingPathComponent(file) guard FileManager.default.fileExists(atPath: url.path) else { log.warning("Font file \(url) for keyboard \(keyboard.id) does not exist") continue diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Storage.swift b/ios/engine/KMEI/KeymanEngine/Classes/Storage.swift index c8717c4a3a..f920d1a3e7 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Storage.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Storage.swift @@ -47,8 +47,6 @@ extension Storage { class Storage { let baseDir: URL - let languageDir: URL - let fontDir: URL let keyboardDir: URL let userDefaults: UserDefaults @@ -57,16 +55,12 @@ class Storage { private init?(baseURL: URL, userDefaults: UserDefaults) { guard let baseDir = Storage.createSubdirectory(baseDir: baseURL, name: "keyman"), - let languageDir = Storage.createSubdirectory(baseDir: baseDir, name: "languages"), - let fontDir = Storage.createSubdirectory(baseDir: baseDir, name: "fonts"), let keyboardDir = Storage.createSubdirectory(baseDir: baseDir, name: "keyboards") else { return nil } self.baseDir = baseDir - self.languageDir = languageDir - self.fontDir = fontDir self.keyboardDir = keyboardDir self.userDefaults = userDefaults kmwURL = baseDir.appendingPathComponent(Resources.kmwFileName) @@ -85,20 +79,40 @@ class Storage { } } + func keyboardDir(forID keyboardID: String) -> URL { + return keyboardDir.appendingPathComponent(keyboardID) + } + func keyboardURL(for keyboard: InstallableKeyboard) -> URL { return keyboardURL(forID: keyboard.id, version: keyboard.version) } func keyboardURL(forID keyboardID: String, version: String) -> URL { - return languageDir.appendingPathComponent("\(keyboardID)-\(version).js") + return keyboardDir(forID: keyboardID).appendingPathComponent("\(keyboardID)-\(version).js") } - func fontURL(forFilename filename: String) -> URL { - return fontDir.appendingPathComponent(filename) + func fontURL(forKeyboardID keyboardID: String, filename: String) -> URL { + return keyboardDir(forID: keyboardID).appendingPathComponent(filename) } - func keyboardDir(forID keyboardID: String) -> URL { - return keyboardDir.appendingPathComponent(keyboardID) + var keyboardDirs: [URL]? { + let contents: [URL] + do { + contents = try FileManager.default.contentsOfDirectory(at: keyboardDir, + includingPropertiesForKeys: [.isDirectoryKey]) + } catch { + log.error("Failed to list contents at \(keyboardDir) with error \(error)") + return nil + } + return contents.filter { url in + do { + let values = try url.resourceValues(forKeys: [.isDirectoryKey]) + return values.isDirectory ?? false + } catch { + log.error(error) + return false + } + } } } @@ -107,28 +121,32 @@ extension Storage { func copyKMWFiles(from bundle: Bundle) throws { try Storage.copy(from: bundle, resourceName: Resources.kmwFileName, - dstDir: Storage.active.baseDir) + dstDir: baseDir) try Storage.copy(from: bundle, resourceName: "keymanios.js", - dstDir: Storage.active.baseDir) - try Storage.copy(from: bundle, - resourceName: "\(Defaults.keyboard.id)-\(Defaults.keyboard.version).js", - dstDir: Storage.active.languageDir) - try Storage.copy(from: bundle, - resourceName: "DejaVuSans.ttf", - dstDir: Storage.active.fontDir) + dstDir: baseDir) try Storage.copy(from: bundle, resourceName: "kmwosk.css", - dstDir: Storage.active.baseDir) + dstDir: baseDir) try Storage.copy(from: bundle, resourceName: "keymanweb-osk.ttf", - dstDir: Storage.active.baseDir) + dstDir: baseDir) + let defaultKeyboardDir = self.keyboardDir(forID: Defaults.keyboard.id) + try FileManager.default.createDirectory(at: defaultKeyboardDir, withIntermediateDirectories: true) + try Storage.copy(from: bundle, + resourceName: "\(Defaults.keyboard.id)-\(Defaults.keyboard.version).js", + dstDir: defaultKeyboardDir) + try Storage.copy(from: bundle, + resourceName: "DejaVuSans.ttf", + dstDir: defaultKeyboardDir) } func copyFiles(to dst: Storage) throws { - try Storage.copyDirectoryContents(at: baseDir, to: dst.baseDir) - try Storage.copyDirectoryContents(at: languageDir, to: dst.languageDir) - try Storage.copyDirectoryContents(at: fontDir, to: dst.fontDir) + if FileManager.default.fileExists(atPath: dst.baseDir.path) { + log.info("Deleting \(dst.baseDir) for copy from \(baseDir) to \(dst.baseDir)") + try FileManager.default.removeItem(at: dst.baseDir) + } + try FileManager.default.copyItem(at: baseDir, to: dst.baseDir) } func copyUserDefaults(to dst: Storage, withKeys keys: [String], shouldOverwrite: Bool) { From 658e2c460dc4ee800258943d3cb85707b3505d01 Mon Sep 17 00:00:00 2001 From: Gabriel Wong Date: Fri, 8 Dec 2017 14:42:21 +0700 Subject: [PATCH 04/22] Create FullKeyboardID to identify user keyboards --- .../KeymanEngine.xcodeproj/project.pbxproj | 4 + .../Extension/UserDefaults+Types.swift | 30 +++--- .../Classes/KeyboardMenuView.swift | 2 +- .../KeyboardPickerViewController.swift | 9 +- .../KMEI/KeymanEngine/Classes/Manager.swift | 93 +++++++------------ .../Classes/Model/FullKeyboardID.swift | 29 ++++++ .../Classes/Model/InstallableKeyboard.swift | 4 + .../KMEI/KeymanEngine/Classes/TextField.swift | 14 +-- .../KMEI/KeymanEngine/Classes/TextView.swift | 14 +-- .../InfoViewController.swift | 2 +- .../WebBrowserViewController.swift | 2 +- .../Keyman/Keyman/MainViewController.swift | 14 +-- .../SetUpViewController.swift | 2 +- 13 files changed, 108 insertions(+), 111 deletions(-) create mode 100644 ios/engine/KMEI/KeymanEngine/Classes/Model/FullKeyboardID.swift diff --git a/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj b/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj index 9bccbdf568..74bd9e2d64 100644 --- a/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj +++ b/ios/engine/KMEI/KeymanEngine.xcodeproj/project.pbxproj @@ -87,6 +87,7 @@ C07A9D8C1FD176AB00828ADD /* KeyboardRepositoryDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A9D8B1FD176AB00828ADD /* KeyboardRepositoryDelegate.swift */; }; C07A9D8E1FD1798900828ADD /* APIKeyboardRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = C07A9D8D1FD1798900828ADD /* APIKeyboardRepository.swift */; }; C082CE151F90AFD400860F02 /* Collection+SafeAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = C082CE141F90AFD400860F02 /* Collection+SafeAccess.swift */; }; + C08E69911FDA6F6F0026056B /* FullKeyboardID.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08E69901FDA6F6F0026056B /* FullKeyboardID.swift */; }; C0959CD41F99C44E00B616BC /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0959CD31F99C44E00B616BC /* Constants.swift */; }; C0A93A541F8B21240079948B /* Manager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0A93A531F8B21240079948B /* Manager.swift */; }; C0B09EAE1FCFD10F002F39AF /* FontManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0B09EAD1FCFD10F002F39AF /* FontManager.swift */; }; @@ -222,6 +223,7 @@ C08C62121F67C31100268D03 /* KeyboardNameTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardNameTableViewCell.swift; sourceTree = ""; }; C08C62141F67C8D500268D03 /* KeyboardInfoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardInfoViewController.swift; sourceTree = ""; }; C08C62161F67CFB800268D03 /* KeyboardPickerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardPickerViewController.swift; sourceTree = ""; }; + C08E69901FDA6F6F0026056B /* FullKeyboardID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullKeyboardID.swift; sourceTree = ""; }; C092D8381F6A70C8005C5485 /* LanguageDetailViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LanguageDetailViewController.swift; sourceTree = ""; }; C0959CD31F99C44E00B616BC /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; C0A5FF361F6682EB00BE740C /* PopoverView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PopoverView.swift; sourceTree = ""; }; @@ -442,6 +444,7 @@ C055E6EA1F99ED090035C2DD /* RegisteredFont.swift */, C007C4641F9F52D8006461B9 /* LanguagesAPICall.swift */, C05F432C1FBD5A4C0058CBD4 /* KeyboardAPICall.swift */, + C08E69901FDA6F6F0026056B /* FullKeyboardID.swift */, ); path = Model; sourceTree = ""; @@ -862,6 +865,7 @@ C082CE151F90AFD400860F02 /* Collection+SafeAccess.swift in Sources */, C06D37441F81F5C400F61AE0 /* KeyboardNameTableViewCell.swift in Sources */, C07A9D8A1FD1762700828ADD /* KeyboardRepository.swift in Sources */, + C08E69911FDA6F6F0026056B /* FullKeyboardID.swift in Sources */, C040E5101F8606E300901EE4 /* TextField.swift in Sources */, C0324B8D1F87480700AF3785 /* TextFieldDelegateProxy.swift in Sources */, C075EB061F8EFF870041F4BD /* String+Helpers.swift in Sources */, diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Extension/UserDefaults+Types.swift b/ios/engine/KMEI/KeymanEngine/Classes/Extension/UserDefaults+Types.swift index 21682a9de2..a9e992be15 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Extension/UserDefaults+Types.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Extension/UserDefaults+Types.swift @@ -17,7 +17,7 @@ public extension UserDefaults { do { return try array.map { try decoder.decode(InstallableKeyboard.self, from: $0) } } catch { - log.error("UserDefaults: Error decoding keyboards: \(error)") + log.error("Error decoding keyboards: \(error)") return nil } } @@ -32,32 +32,32 @@ public extension UserDefaults { let array = try keyboards.map { try encoder.encode($0) } set(array, forKey: key) } catch { - log.error("UserDefaults: Error encoding keyboards: \(error)") + log.error("Error encoding keyboards: \(error)") } } - public func installableKeyboard(forKey key: String) -> InstallableKeyboard? { + public func fullKeyboardID(forKey key: String) -> FullKeyboardID? { guard let data = data(forKey: key) else { return nil } do { - return try PropertyListDecoder().decode(InstallableKeyboard.self, from: data) + return try PropertyListDecoder().decode(FullKeyboardID.self, from: data) } catch { - log.error("UserDefaults: Error decoding keyboard: \(error)") + log.error("Error decoding FullKeyboardID: \(error)") return nil } } - public func set(_ keyboard: InstallableKeyboard?, forKey key: String) { - guard let keyboard = keyboard else { + public func set(_ fullKeyboardID: FullKeyboardID?, forKey key: String) { + guard let id = fullKeyboardID else { removeObject(forKey: key) return } do { - let data = try PropertyListEncoder().encode(keyboard) + let data = try PropertyListEncoder().encode(id) set(data, forKey: key) } catch { - log.error("UserDefaults: Error encoding keyboard: \(error)") + log.error("Error encoding FullKeyboardID: \(error)") } } @@ -71,17 +71,17 @@ public extension UserDefaults { } } - public var currentKeyboard: InstallableKeyboard? { + public var currentKeyboardID: FullKeyboardID? { get { - return installableKeyboard(forKey: Key.userCurrentKeyboard) + return fullKeyboardID(forKey: Key.userCurrentKeyboard) } - set(keyboard) { - set(keyboard, forKey: Key.userCurrentKeyboard) + set(fullKeyboardID) { + set(fullKeyboardID, forKey: Key.userCurrentKeyboard) } } - public func userKeyboard(withID keyboardID: String, languageID: String) -> InstallableKeyboard? { - return userKeyboards?.first { $0.id == keyboardID && $0.languageID == languageID } + public func userKeyboard(withFullID fullID: FullKeyboardID) -> InstallableKeyboard? { + return userKeyboards?.first { $0.fullID == fullID } } } diff --git a/ios/engine/KMEI/KeymanEngine/Classes/KeyboardMenuView.swift b/ios/engine/KMEI/KeymanEngine/Classes/KeyboardMenuView.swift index caa0def4f0..123cf20c5e 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/KeyboardMenuView.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/KeyboardMenuView.swift @@ -284,7 +284,7 @@ class KeyboardMenuView: UIView, UITableViewDelegate, UITableViewDataSource, UIGe let keyboard = tableList[indexPath.row] as! InstallableKeyboard cell.textLabel?.text = keyboard.name cell.tag = indexPath.row - if (Manager.shared.languageID == keyboard.languageID) && (Manager.shared.keyboardID == keyboard.id) { + if Manager.shared.currentKeyboardID == keyboard.fullID { cell.selectionStyle = .none cell.isSelected = true cell.accessoryType = .checkmark diff --git a/ios/engine/KMEI/KeymanEngine/Classes/LanguagePicker/KeyboardPickerViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/LanguagePicker/KeyboardPickerViewController.swift index 3ff1f82b3b..703b6ed7ac 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/LanguagePicker/KeyboardPickerViewController.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/LanguagePicker/KeyboardPickerViewController.swift @@ -164,7 +164,7 @@ class KeyboardPickerViewController: UITableViewController, UIAlertViewDelegate { cell.detailTextLabel?.text = kb.name cell.tag = indexPath.row - if isCurrentKeyboard(languageID: kb.languageID, keyboardID: kb.id) { + if Manager.shared.currentKeyboardID == kb.fullID { cell.selectionStyle = .blue cell.isSelected = true cell.accessoryType = .detailDisclosureButton @@ -365,7 +365,7 @@ class KeyboardPickerViewController: UITableViewController, UIAlertViewDelegate { private func scroll(toSelectedKeyboard animated: Bool) { let index = userKeyboards.index { kb in - return isCurrentKeyboard(languageID: kb.languageID, keyboardID: kb.id) + return Manager.shared.currentKeyboardID == kb.fullID } if let index = index { @@ -388,11 +388,6 @@ class KeyboardPickerViewController: UITableViewController, UIAlertViewDelegate { } } - private func isCurrentKeyboard(languageID: String?, keyboardID: String?) -> Bool { - return Manager.shared.keyboardID == keyboardID && - Manager.shared.languageID == languageID - } - @objc func hideToolbarDelayed(_ timer: Timer) { navigationController?.setToolbarHidden(true, animated: true) } diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift index 5bd71d477a..ce44ccf15d 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift @@ -91,8 +91,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat /// Set this to `UIApplication.shared.openURL` in your application. public var openURL: ((URL) -> Bool)? - var keyboardID: String? - var languageID: String? + var currentKeyboardID: FullKeyboardID? weak var keymanWebDelegate: KeymanWebDelegate? var currentRequest: HTTPDownloadRequest? var shouldReloadKeyboard = false @@ -206,8 +205,8 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat /// - SeeAlso: /// - addKeyboard() /// - Returns: Whether the keyboard was set successfully - public func setKeyboard(withID keyboardID: String, languageID: String) -> Bool { - if let keyboard = Storage.active.userDefaults.userKeyboard(withID: keyboardID, languageID: languageID) { + public func setKeyboard(withFullID fullID: FullKeyboardID) -> Bool { + if let keyboard = Storage.active.userDefaults.userKeyboard(withFullID: fullID) { return setKeyboard(keyboard) } return false @@ -217,15 +216,14 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat /// /// - Returns: Whether the keyboard was set successfully public func setKeyboard(_ kb: InstallableKeyboard) -> Bool { - if kb.languageID == self.languageID && kb.id == self.keyboardID { - log.info("Keyboard unchanged: \(kb.languageID)_\(kb.id)") + if kb.fullID == currentKeyboardID { + log.info("Keyboard unchanged: \(kb.fullID)") return false } - log.info("Setting language: \(kb.languageID)_\(kb.id)") + log.info("Setting language: \(kb.fullID)") - self.languageID = kb.languageID - self.keyboardID = kb.id + currentKeyboardID = kb.fullID if let fontFilename = kb.font?.source.first(where: { $0.hasFontExtension }) { _ = FontManager.shared.registerFont(at: Storage.active.fontURL(forFilename: fontFilename)) @@ -238,8 +236,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat fileURL: Storage.active.keyboardURL(for: kb), font: kb.font, oskFont: kb.oskFont) let userData = Util.isSystemKeyboard ? UserDefaults.standard : Storage.active.userDefaults - - userData.currentKeyboard = kb + userData.currentKeyboardID = kb.fullID userData.synchronize() if isKeymanHelpOn { @@ -269,7 +266,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat var userKeyboards = userDefaults.userKeyboards ?? [] // Update keyboard if it exists - if let index = userKeyboards.index(where: { $0.id == keyboard.id && $0.languageID == keyboard.languageID }) { + if let index = userKeyboards.index(where: { $0.fullID == keyboard.fullID }) { userKeyboards[index] = keyboard } else { userKeyboards.append(keyboard) @@ -282,9 +279,9 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat /// Removes a keyboard from the list in the keyboard picker if it exists. /// - Returns: The keyboard exists and was removed - public func removeKeyboard(withID keyboardID: String, languageID: String) -> Bool { + public func removeKeyboard(withFullID fullID: FullKeyboardID) -> Bool { // Remove keyboard from the list if it exists - let index = Storage.active.userDefaults.userKeyboards?.index { $0.id == keyboardID && $0.languageID == languageID } + let index = Storage.active.userDefaults.userKeyboards?.index { $0.fullID == fullID } if let index = index { return removeKeyboard(at: index) } @@ -312,7 +309,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat userData.synchronize() // Set a new keyboard if deleting the current one - if kb.id == keyboardID && kb.languageID == languageID { + if kb.fullID == currentKeyboardID { setKeyboard(userKeyboards[0]) } @@ -321,18 +318,19 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat } /// - Returns: Info for the current keyboard, if a keyboard is set - public var currentKeyboardInfo: InstallableKeyboard? { - guard let keyboardID = keyboardID, let languageID = languageID else { + public var currentKeyboard: InstallableKeyboard? { + guard let fullID = currentKeyboardID else { return nil } - return Storage.active.userDefaults.userKeyboard(withID: keyboardID, languageID: languageID) + return Storage.active.userDefaults.userKeyboard(withFullID: fullID) } /// Switch to the next keyboard. /// - Returns: Index of the newly selected keyboard. public func switchToNextKeyboard() -> Int? { guard let userKeyboards = Storage.active.userDefaults.userKeyboards, - let index = userKeyboards.index(where: { isCurrentKeyboard($0) }) else { + let index = userKeyboards.index(where: { self.currentKeyboardID == $0.fullID }) + else { return nil } let newIndex = (index + 1) % userKeyboards.count @@ -340,19 +338,11 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat return newIndex } - func isCurrentKeyboard(withID keyboardID: String?, languageID: String?) -> Bool { - return self.keyboardID == keyboardID && self.languageID == languageID - } - - func isCurrentKeyboard(_ keyboard: InstallableKeyboard) -> Bool { - return keyboard.id == self.keyboardID && keyboard.languageID == self.languageID - } - /// - Returns: The font name for the given keyboard ID and languageID, or returns nil if /// - The keyboard doesn't have a font /// - The keyboard info is not available in the user keyboards list - public func fontNameForKeyboard(withID keyboardID: String, languageID: String) -> String? { - let kb = Storage.active.userDefaults.userKeyboard(withID: keyboardID, languageID: languageID) + public func fontNameForKeyboard(withFullID fullID: FullKeyboardID) -> String? { + let kb = Storage.active.userDefaults.userKeyboard(withFullID: fullID) if let filename = kb?.font?.source.first(where: { $0.hasFontExtension }) { let fontURL = Storage.active.fontURL(forFilename: filename) return FontManager.shared.fontName(at: fontURL) @@ -363,8 +353,8 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat /// - Returns: the OSK font name for the given keyboard ID and languageID, or returns nil if /// - The keyboard doesn't have an OSK font /// - The keyboard info is not available in the user keyboards list - func oskFontNameForKeyboard(withID keyboardID: String, languageID: String) -> String? { - let kb = Storage.active.userDefaults.userKeyboard(withID: keyboardID, languageID: languageID) + func oskFontNameForKeyboard(withFullID fullID: FullKeyboardID) -> String? { + let kb = Storage.active.userDefaults.userKeyboard(withFullID: fullID) if let filename = kb?.oskFont?.source.first(where: { $0.hasFontExtension }) { let fontURL = Storage.active.fontURL(forFilename: filename) return FontManager.shared.fontName(at: fontURL) @@ -777,27 +767,13 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat if kb.languageID == newKeyboard.languageID { kb = newKeyboard } else { - kb.version = newKeyboard.id + kb.version = newKeyboard.version } userKeyboards[i] = kb } } userData.userKeyboards = userKeyboards userData.synchronize() - - // Set version for current keyboard - let currentUserData = Util.isSystemKeyboard ? UserDefaults.standard : Storage.active.userDefaults - if var kb = currentUserData.currentKeyboard { - if kb.id == newKeyboard.id { - if kb.languageID == newKeyboard.languageID { - kb = newKeyboard - } else { - kb.version = newKeyboard.id - } - currentUserData.currentKeyboard = kb - currentUserData.synchronize() - } - } } func synchronizeSWKeyboard() { @@ -964,9 +940,8 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat } @objc func resetKeyboard() { - let keyboard = currentKeyboardInfo - keyboardID = nil - languageID = nil + let keyboard = currentKeyboard + currentKeyboardID = nil if let keyboard = keyboard { setKeyboard(keyboard) @@ -1134,13 +1109,11 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat keymanWeb.setDeviceType(UIDevice.current.userInterfaceIdiom) var newKb = Defaults.keyboard - if (keyboardID == nil || languageID == nil) && !shouldReloadKeyboard { + if currentKeyboardID == nil && !shouldReloadKeyboard { let userData = Util.isSystemKeyboard ? UserDefaults.standard : Storage.active.userDefaults - if let currentKb = userData.currentKeyboard { - let kbID = currentKb.id - let langID = currentKb.languageID - if Storage.active.userDefaults.userKeyboard(withID: kbID, languageID: langID) != nil { - newKb = currentKb + if let id = userData.currentKeyboardID { + if let kb = Storage.active.userDefaults.userKeyboard(withFullID: id) { + newKb = kb } } else if let userKbs = Storage.active.userDefaults.userKeyboards, !userKbs.isEmpty { newKb = userKbs[0] @@ -1178,8 +1151,8 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat keyPreviewView = KeyPreviewView(frame: keyFrame) keyPreviewView!.setLabelText(preview) - var oskFontName = oskFontNameForKeyboard(withID: keyboardID!, languageID: languageID!) - oskFontName = oskFontName ?? fontNameForKeyboard(withID: keyboardID!, languageID: languageID!) + var oskFontName = oskFontNameForKeyboard(withFullID: currentKeyboardID!) + oskFontName = oskFontName ?? fontNameForKeyboard(withFullID: currentKeyboardID!) keyPreviewView!.setLabelFont(oskFontName) keymanWeb.view.addSubview(keyPreviewView!) } @@ -1307,9 +1280,9 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat let isPad = UIDevice.current.userInterfaceIdiom == .pad let fontSize = isPad ? UIFont.buttonFontSize * 2 : UIFont.buttonFontSize - var oskFontName = oskFontNameForKeyboard(withID: keyboardID!, languageID: languageID!) + var oskFontName = oskFontNameForKeyboard(withFullID: currentKeyboardID!) if oskFontName == nil { - oskFontName = fontNameForKeyboard(withID: keyboardID!, languageID: languageID!) + oskFontName = fontNameForKeyboard(withFullID: currentKeyboardID!) } if subKeyIDs.isEmpty { @@ -1393,7 +1366,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat if (!didSynchronize || shouldSynchronize) && Storage.shared != nil { synchronizeSWKeyboard() - if keyboardID != nil && languageID != nil { + if currentKeyboardID != nil { shouldReloadKeyboard = true reloadKeyboard(in: keymanWeb) } diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Model/FullKeyboardID.swift b/ios/engine/KMEI/KeymanEngine/Classes/Model/FullKeyboardID.swift new file mode 100644 index 0000000000..a243a1af11 --- /dev/null +++ b/ios/engine/KMEI/KeymanEngine/Classes/Model/FullKeyboardID.swift @@ -0,0 +1,29 @@ +// +// FullKeyboardID.swift +// KeymanEngine +// +// Created by Gabriel Wong on 2017-12-08. +// Copyright © 2017 SIL International. All rights reserved. +// + +import Foundation + +/// A complete identifier for an `InstallableKeyboard`. Keyboards must have unique `FullKeyboardID`s. +public struct FullKeyboardID: Codable { + public var keyboardID: String + public var languageID: String +} + +// MARK: - Equatable +extension FullKeyboardID: Equatable { + public static func ==(lhs: FullKeyboardID, rhs: FullKeyboardID) -> Bool { + return lhs.keyboardID == rhs.keyboardID && lhs.languageID == rhs.languageID + } +} + +// MARK: - CustomStringConvertible +extension FullKeyboardID: CustomStringConvertible { + public var description: String { + return "" + } +} diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Model/InstallableKeyboard.swift b/ios/engine/KMEI/KeymanEngine/Classes/Model/InstallableKeyboard.swift index d98681108e..265c6f32f4 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Model/InstallableKeyboard.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Model/InstallableKeyboard.swift @@ -20,6 +20,10 @@ public struct InstallableKeyboard: Codable { public var oskFont: Font? public var isCustom: Bool + public var fullID: FullKeyboardID { + return FullKeyboardID(keyboardID: id, languageID: languageID) + } + public init(id: String, name: String, languageID: String, diff --git a/ios/engine/KMEI/KeymanEngine/Classes/TextField.swift b/ios/engine/KMEI/KeymanEngine/Classes/TextField.swift index 38b245fea4..0e656d55f4 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/TextField.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/TextField.swift @@ -144,8 +144,7 @@ public class TextField: UITextField { return } - // TODO: Get font name directly from keyboard object - let fontName = Manager.shared.fontNameForKeyboard(withID: kb.id, languageID: kb.languageID) + let fontName = Manager.shared.fontNameForKeyboard(withFullID: kb.fullID) let fontSize = font?.pointSize ?? UIFont.systemFontSize if let fontName = fontName { font = UIFont(name: fontName, size: fontSize) @@ -240,10 +239,8 @@ extension TextField: UITextFieldDelegate { let textWD = baseWritingDirection(for: beginningOfDocument, in: .forward) let isRTL: Bool - if let keyboardID = Manager.shared.keyboardID, - let languageID = Manager.shared.languageID { - let keyboard = Storage.active.userDefaults.userKeyboard(withID: keyboardID, languageID: languageID) - isRTL = keyboard?.isRTL ?? false + if let keyboard = Manager.shared.currentKeyboard { + isRTL = keyboard.isRTL } else { isRTL = false } @@ -278,9 +275,8 @@ extension TextField: UITextFieldDelegate { Manager.shared.keymanWebDelegate = self let fontName: String? - if let keyboardID = Manager.shared.keyboardID, - let languageID = Manager.shared.languageID { - fontName = Manager.shared.fontNameForKeyboard(withID: keyboardID, languageID: languageID) + if let id = Manager.shared.currentKeyboardID { + fontName = Manager.shared.fontNameForKeyboard(withFullID: id) } else { fontName = nil } diff --git a/ios/engine/KMEI/KeymanEngine/Classes/TextView.swift b/ios/engine/KMEI/KeymanEngine/Classes/TextView.swift index 800ace08d9..df52e8a896 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/TextView.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/TextView.swift @@ -133,8 +133,7 @@ public class TextView: UITextView { return } - // TODO: Get font name directly from keyboard - let fontName = Manager.shared.fontNameForKeyboard(withID: kb.id, languageID: kb.languageID) + let fontName = Manager.shared.fontNameForKeyboard(withFullID: kb.fullID) let fontSize = font?.pointSize ?? UIFont.systemFontSize if let fontName = fontName { font = UIFont(name: fontName, size: fontSize) @@ -239,10 +238,8 @@ extension TextView: UITextViewDelegate { let textWD = baseWritingDirection(for: beginningOfDocument, in: .forward) let isRTL: Bool - if let keyboardID = Manager.shared.keyboardID, - let languageID = Manager.shared.languageID { - let keyboard = Storage.active.userDefaults.userKeyboard(withID: keyboardID, languageID: languageID) - isRTL = keyboard?.isRTL ?? false + if let keyboard = Manager.shared.currentKeyboard { + isRTL = keyboard.isRTL } else { isRTL = false } @@ -279,9 +276,8 @@ extension TextView: UITextViewDelegate { Manager.shared.keymanWebDelegate = self let fontName: String? - if let keyboardID = Manager.shared.keyboardID, - let languageID = Manager.shared.languageID { - fontName = Manager.shared.fontNameForKeyboard(withID: keyboardID, languageID: languageID) + if let id = Manager.shared.currentKeyboardID { + fontName = Manager.shared.fontNameForKeyboard(withFullID: id) } else { fontName = nil } diff --git a/ios/keyman/Keyman/Keyman/InfoViewController/InfoViewController.swift b/ios/keyman/Keyman/Keyman/InfoViewController/InfoViewController.swift index a34fa82789..de3d37e88a 100644 --- a/ios/keyman/Keyman/Keyman/InfoViewController/InfoViewController.swift +++ b/ios/keyman/Keyman/Keyman/InfoViewController/InfoViewController.swift @@ -52,7 +52,7 @@ class InfoViewController: UIViewController, UIWebViewDelegate { } private func loadFromServer() { - let keyboardInfo = Manager.shared.currentKeyboardInfo + let keyboardInfo = Manager.shared.currentKeyboard let currentKeyboardId = keyboardInfo?.id ?? Defaults.keyboard.id let userData = AppDelegate.activeUserDefaults() let keyboards = userData.userKeyboards diff --git a/ios/keyman/Keyman/Keyman/KMWebBrowser/WebBrowserViewController.swift b/ios/keyman/Keyman/Keyman/KMWebBrowser/WebBrowserViewController.swift index 4fdab8df1c..2e14f2799e 100644 --- a/ios/keyman/Keyman/Keyman/KMWebBrowser/WebBrowserViewController.swift +++ b/ios/keyman/Keyman/Keyman/KMWebBrowser/WebBrowserViewController.swift @@ -273,7 +273,7 @@ class WebBrowserViewController: UIViewController, UIWebViewDelegate, UIAlertView } private func keyboardChanged(_ kb: InstallableKeyboard) { - if let fontName = Manager.shared.fontNameForKeyboard(withID: kb.id, languageID: kb.languageID) { + if let fontName = Manager.shared.fontNameForKeyboard(withFullID: kb.fullID) { newFontFamily = fontName } else { newFontFamily = UIFont.systemFont(ofSize: UIFont.systemFontSize).fontName diff --git a/ios/keyman/Keyman/Keyman/MainViewController.swift b/ios/keyman/Keyman/Keyman/MainViewController.swift index 2a0fe05355..185559c2bd 100644 --- a/ios/keyman/Keyman/Keyman/MainViewController.swift +++ b/ios/keyman/Keyman/Keyman/MainViewController.swift @@ -470,7 +470,7 @@ class MainViewController: UIViewController, TextViewDelegate, UIActionSheetDeleg didDownload = false } - checkProfile(forKeyboardID: kb.id, languageID: kb.languageID, doListCheck: listCheck) + checkProfile(forFullID: kb.fullID, doListCheck: listCheck) } private func keyboardDownloadStarted() { @@ -869,8 +869,8 @@ class MainViewController: UIViewController, TextViewDelegate, UIActionSheetDeleg } } - private func profileName(withKeyboardID kbID: String, languageID langID: String) -> String? { - guard let keyboard = AppDelegate.activeUserDefaults().userKeyboard(withID: kbID, languageID: langID), + private func profileName(withFullID fullID: FullKeyboardID) -> String? { + guard let keyboard = AppDelegate.activeUserDefaults().userKeyboard(withFullID: fullID), let font = keyboard.font else { return nil } @@ -882,15 +882,15 @@ class MainViewController: UIViewController, TextViewDelegate, UIActionSheetDeleg return font.source.first { $0.lowercased().hasSuffix(FileExtensions.configurationProfile) } } - private func checkProfile(forKeyboardID kbID: String, languageID langID: String, doListCheck: Bool) { - if kbID == Defaults.keyboard.id && langID == Defaults.keyboard.languageID { + private func checkProfile(forFullID fullID: FullKeyboardID, doListCheck: Bool) { + if fullID == Defaults.keyboard.fullID { return } if profileName != nil { return // already installing a profile } - guard let profile = profileName(withKeyboardID: kbID, languageID: langID) else { + guard let profile = profileName(withFullID: fullID) else { return } @@ -906,7 +906,7 @@ class MainViewController: UIViewController, TextViewDelegate, UIActionSheetDeleg if doInstall { profileName = profile - let keyboard = AppDelegate.activeUserDefaults().userKeyboard(withID: kbID, languageID: langID)! + let keyboard = AppDelegate.activeUserDefaults().userKeyboard(withFullID: fullID)! let languageName = keyboard.languageName let title = "\(languageName) Font" let msg = "Touch Install to make \(languageName) display correctly in all your apps" diff --git a/ios/keyman/Keyman/Keyman/SetUpViewController/SetUpViewController.swift b/ios/keyman/Keyman/Keyman/SetUpViewController/SetUpViewController.swift index a58ad75a11..464d21d91e 100644 --- a/ios/keyman/Keyman/Keyman/SetUpViewController/SetUpViewController.swift +++ b/ios/keyman/Keyman/Keyman/SetUpViewController/SetUpViewController.swift @@ -57,7 +57,7 @@ class SetUpViewController: UIViewController, UIWebViewDelegate { } private func loadFromServer() { - let keyboardInfo = Manager.shared.currentKeyboardInfo + let keyboardInfo = Manager.shared.currentKeyboard let currentKeyboardId = keyboardInfo?.id ?? Defaults.keyboard.id let userData = AppDelegate.activeUserDefaults() let keyboards = userData.userKeyboards From 3b2e1153ca55c00c8a02ddbc58167be71ca1cfbc Mon Sep 17 00:00:00 2001 From: Gabriel Wong Date: Fri, 8 Dec 2017 15:10:54 +0700 Subject: [PATCH 05/22] Keep track of migrations that are performed --- ios/engine/KMEI/KeymanEngine/Classes/Constants.swift | 1 + .../Classes/Extension/UserDefaults+Types.swift | 10 ++++++++++ ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift | 11 +++++++++++ 3 files changed, 22 insertions(+) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Constants.swift b/ios/engine/KMEI/KeymanEngine/Classes/Constants.swift index 200d053541..fa1e0011fe 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Constants.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Constants.swift @@ -19,6 +19,7 @@ public enum Key { static let engineVersion = "KeymanEngineVersion" static let keyboardPickerDisplayed = "KeyboardPickerDisplayed" static let synchronizeSWKeyboard = "KeymanSynchronizeSWKeyboard" + static let migrationLevel = "KeymanEngineMigrationLevel" // JSON keys for language REST calls static let options = "options" diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Extension/UserDefaults+Types.swift b/ios/engine/KMEI/KeymanEngine/Classes/Extension/UserDefaults+Types.swift index 21682a9de2..d5f7e5554d 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Extension/UserDefaults+Types.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Extension/UserDefaults+Types.swift @@ -84,4 +84,14 @@ public extension UserDefaults { public func userKeyboard(withID keyboardID: String, languageID: String) -> InstallableKeyboard? { return userKeyboards?.first { $0.id == keyboardID && $0.languageID == languageID } } + + var migrationLevel: Int { + get { + return integer(forKey: Key.migrationLevel) + } + + set(level) { + set(level, forKey: Key.migrationLevel) + } + } } diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift b/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift index fdb4e3a893..2417da3cdd 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift @@ -8,8 +8,17 @@ import Foundation +fileprivate enum MigrationLevel { + static let initial = 0 + static let migratedForKMP = 10 +} + enum Migrations { static func migrateForKMP(storage: Storage) { + guard storage.userDefaults.migrationLevel < MigrationLevel.migratedForKMP else { + return + } + let languageDir = storage.baseDir.appendingPathComponent("languages") let fontDir = storage.baseDir.appendingPathComponent("fonts") @@ -80,6 +89,8 @@ enum Migrations { // Remove keyboards that were not copied successfully let filteredUserKeyboards = userKeyboards.filter { successfulKeyboards.contains($0.id) } storage.userDefaults.userKeyboards = filteredUserKeyboards + storage.userDefaults.migrationLevel = MigrationLevel.migratedForKMP + storage.userDefaults.synchronize() // TODO: Remove old directory } From 59543badedf12ec637bb0e2e514125f1797c10aa Mon Sep 17 00:00:00 2001 From: Gabriel Wong Date: Fri, 8 Dec 2017 16:45:38 +0700 Subject: [PATCH 06/22] Ensure keyboard directory created when downloading --- .../KMEI/KeymanEngine/Classes/Manager.swift | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift index 3ec1de5a74..d87f04a470 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift @@ -415,8 +415,15 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat return } - let keyboardURL = options.keyboardBaseURL.appendingPathComponent(filename) + do { + try FileManager.default.createDirectory(at: Storage.active.keyboardDir(forID: keyboardID), + withIntermediateDirectories: true) + } catch { + log.error("Could not create dir for download: \(error)") + return + } + let keyboardURL = options.keyboardBaseURL.appendingPathComponent(filename) let fontURLs = Array(Set(keyboardFontURLs(forFont: keyboard.font, options: options) + keyboardFontURLs(forFont: keyboard.oskFont, options: options))) @@ -506,9 +513,17 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat return } + do { + try FileManager.default.createDirectory(at: Storage.active.keyboardDir(forID: keyboard.id), + withIntermediateDirectories: true) + } catch { + log.error("Could not create dir for download: \(error)") + return + } + let isUpdate = Storage.active.userDefaults.userKeyboards?.contains { $0.id == keyboard.id } ?? false - downloadQueue = HTTPDownloader.init(self) + downloadQueue = HTTPDownloader(self) let commonUserData: [String: Any] = [ Key.keyboardInfo: installableKeyboards, Key.update: isUpdate From 63ab59c0110e176fd3fcbb77a2e485164a395195 Mon Sep 17 00:00:00 2001 From: Gabriel Wong Date: Fri, 8 Dec 2017 17:13:25 +0700 Subject: [PATCH 07/22] Delete keyboard files --- .../KeymanEngine/Classes/FontManager.swift | 50 ++++++++++--------- .../KMEI/KeymanEngine/Classes/Manager.swift | 13 +++++ .../KeymanEngine/Classes/Migrations.swift | 1 + 3 files changed, 40 insertions(+), 24 deletions(-) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/FontManager.swift b/ios/engine/KMEI/KeymanEngine/Classes/FontManager.swift index 75c7f87afc..b0379ca6a7 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/FontManager.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/FontManager.swift @@ -28,39 +28,21 @@ public class FontManager { /// Registers all new fonts found in the font path. Call this after you have preloaded all your font files /// with `preloadFontFile(atPath:shouldOverwrite:)` public func registerCustomFonts() { - guard let customFonts = customFonts() else { + guard let keyboardDirs = Storage.active.keyboardDirs else { return } - - for fontURL in customFonts { - _ = registerFont(at: fontURL) + for dir in keyboardDirs { + registerFonts(in: dir) } } /// Unregisters all registered fonts in the font path. public func unregisterCustomFonts() { - guard let customFonts = customFonts() else { + guard let keyboardDirs = Storage.active.keyboardDirs else { return } - - for fontURL in customFonts { - _ = unregisterFont(at: fontURL) - } - } - - private func customFonts() -> [URL]? { - guard let keyboardDirs = Storage.active.keyboardDirs else { - return nil - } - - do { - let urls = try keyboardDirs.flatMap { - return try FileManager.default.contentsOfDirectory(at: $0, includingPropertiesForKeys: nil) - } - return urls.filter { $0.lastPathComponent.hasFontExtension } - } catch { - log.error("Failed to list font dir contents: \(error)") - return nil + for dir in keyboardDirs { + unregisterFonts(in: dir) } } @@ -143,6 +125,26 @@ public class FontManager { return font.isRegistered } + public func registerFonts(in directory: URL) { + guard let urls = try? FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil) else { + log.error("Could not list contents of directory \(directory)") + return + } + for url in urls where url.lastPathComponent.hasFontExtension { + _ = registerFont(at: url) + } + } + + public func unregisterFonts(in directory: URL, fromSystemOnly: Bool = true) { + guard let urls = try? FileManager.default.contentsOfDirectory(at: directory, includingPropertiesForKeys: nil) else { + log.error("Could not list contents of directory \(directory)") + return + } + for url in urls where url.lastPathComponent.hasFontExtension { + _ = unregisterFont(at: url, fromSystemOnly: fromSystemOnly) + } + } + private func fontExists(_ fontName: String) -> Bool { return UIFont.familyNames.contains { familyName in UIFont.fontNames(forFamilyName: familyName).contains(fontName) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift index d87f04a470..1cc4b96ec0 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift @@ -295,11 +295,24 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat userData.set([Date()], forKey: Key.synchronizeSWKeyboard) userData.synchronize() + log.info("Removing keyboard with ID \(kb.id) and languageID \(kb.languageID)") + // Set a new keyboard if deleting the current one if kb.id == keyboardID && kb.languageID == languageID { setKeyboard(userKeyboards[0]) } + if !userKeyboards.contains(where: { $0.id == kb.id }) { + let keyboardDir = Storage.active.keyboardDir(forID: kb.id) + FontManager.shared.unregisterFonts(in: keyboardDir, fromSystemOnly: false) + log.info("Deleting directory \(keyboardDir)") + if (try? FileManager.default.removeItem(at: keyboardDir)) == nil { + log.error("Failed to delete \(keyboardDir)") + } + } else { + log.info("User has another language installed. Skipping delete of keyboard files.") + } + NotificationCenter.default.post(name: Notifications.keyboardRemoved, object: self, value: kb) return true } diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift b/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift index 2417da3cdd..aaf1ed2d17 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift @@ -16,6 +16,7 @@ fileprivate enum MigrationLevel { enum Migrations { static func migrateForKMP(storage: Storage) { guard storage.userDefaults.migrationLevel < MigrationLevel.migratedForKMP else { + log.info("KMP directory migration already performed. Skipping.") return } From 161a1294077e2773dd552d3e926598b315e71ca5 Mon Sep 17 00:00:00 2001 From: Gabriel Wong Date: Mon, 11 Dec 2017 12:42:49 +0700 Subject: [PATCH 08/22] Fix samples --- .../KMEI/KeymanEngine/Classes/Manager.swift | 1 + .../KMSample1/KMSample1/ViewController.swift | 22 ++++++++++-------- .../KMSample2/KMSample2/ViewController.swift | 23 ++++++++++--------- 3 files changed, 25 insertions(+), 21 deletions(-) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift index 1cc4b96ec0..d157a4365a 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift @@ -698,6 +698,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat /// Preloads the JS and font files required for a keyboard. public func preloadFiles(forKeyboardID keyboardID: String, at urls: [URL], shouldOverwrite: Bool) throws { let keyboardDir = Storage.active.keyboardDir(forID: keyboardID) + try FileManager.default.createDirectory(at: keyboardDir, withIntermediateDirectories: true) for url in urls { try Storage.copyAndExcludeFromBackup(at: url, to: keyboardDir.appendingPathComponent(url.lastPathComponent), diff --git a/ios/samples/KMSample1/KMSample1/ViewController.swift b/ios/samples/KMSample1/KMSample1/ViewController.swift index 7172846fe2..b6b9b64667 100644 --- a/ios/samples/KMSample1/KMSample1/ViewController.swift +++ b/ios/samples/KMSample1/KMSample1/ViewController.swift @@ -15,17 +15,7 @@ class ViewController: UIViewController, TextViewDelegate { super.viewDidLoad() Manager.shared.openURL = UIApplication.shared.openURL Manager.shared.isKeymanHelpOn = false - do { - try Manager.shared.preloadKeyboardFile(at: Bundle.main.url(forResource: "tamil99m-1.1", withExtension: "js")!, - shouldOverwrite: true) - try Manager.shared.preloadFontFile(at: Bundle.main.url(forResource: "aava1", withExtension: "ttf")!, - shouldOverwrite: true) - } catch { - print("Error preloading: \(error)") - } - FontManager.shared.registerCustomFonts() - Manager.shared.addKeyboard(Defaults.keyboard) let kb = InstallableKeyboard(id: "tamil99m", name: "Tamil 99M", languageID: "tam", @@ -35,6 +25,18 @@ class ViewController: UIViewController, TextViewDelegate { font: Font(filename: "aava1.ttf"), oskFont: nil, isCustom: true) + let urls = [ + Bundle.main.url(forResource: "tamil99m-1.1", withExtension: "js")!, + Bundle.main.url(forResource: "aava1", withExtension: "ttf")! + ] + do { + try Manager.shared.preloadFiles(forKeyboardID: kb.id, at: urls, shouldOverwrite: true) + } catch { + print("Error preloading: \(error)") + } + FontManager.shared.registerCustomFonts() + + Manager.shared.addKeyboard(Defaults.keyboard) Manager.shared.addKeyboard(kb) textView.setKeymanDelegate(self) diff --git a/ios/samples/KMSample2/KMSample2/ViewController.swift b/ios/samples/KMSample2/KMSample2/ViewController.swift index c67e75cba9..5cdb44657f 100644 --- a/ios/samples/KMSample2/KMSample2/ViewController.swift +++ b/ios/samples/KMSample2/KMSample2/ViewController.swift @@ -16,19 +16,8 @@ class ViewController: UIViewController, TextViewDelegate { super.viewDidLoad() Manager.shared.openURL = UIApplication.shared.openURL - Manager.shared.isKeymanHelpOn = false - do { - try Manager.shared.preloadKeyboardFile(at: Bundle.main.url(forResource: "tamil99m-1.1", withExtension: "js")!, - shouldOverwrite: true) - try Manager.shared.preloadFontFile(at: Bundle.main.url(forResource: "aava1", withExtension: "ttf")!, - shouldOverwrite: true) - } catch { - print("Error preloading: \(error)") - } - FontManager.shared.registerCustomFonts() - Manager.shared.addKeyboard(Defaults.keyboard) let kb = InstallableKeyboard(id: "tamil99m", name: "Tamil 99M", languageID: "tam", @@ -38,6 +27,18 @@ class ViewController: UIViewController, TextViewDelegate { font: Font(filename: "aava1.ttf"), oskFont: nil, isCustom: true) + let urls = [ + Bundle.main.url(forResource: "tamil99m-1.1", withExtension: "js")!, + Bundle.main.url(forResource: "aava1", withExtension: "ttf")! + ] + do { + try Manager.shared.preloadFiles(forKeyboardID: kb.id, at: urls, shouldOverwrite: true) + } catch { + print("Error preloading: \(error)") + } + FontManager.shared.registerCustomFonts() + + Manager.shared.addKeyboard(Defaults.keyboard) Manager.shared.addKeyboard(kb) textView.setKeymanDelegate(self) From 1b06075594a901038d0ffc3e0dd6593fc3ee9224 Mon Sep 17 00:00:00 2001 From: Gabriel Wong Date: Mon, 11 Dec 2017 12:54:28 +0700 Subject: [PATCH 09/22] Update migration level even when no keyboards migrated --- .../KMEI/KeymanEngine/Classes/Manager.swift | 2 +- .../KMEI/KeymanEngine/Classes/Migrations.swift | 17 ++++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift index d157a4365a..aaa61659ec 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift @@ -139,7 +139,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat URLProtocol.registerClass(KeymanURLProtocol.self) - Migrations.migrateForKMP(storage: Storage.active) + Migrations.migrate(storage: Storage.active) if Util.isSystemKeyboard || Storage.active.userDefaults.bool(forKey: Key.keyboardPickerDisplayed) { isKeymanHelpOn = false diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift b/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift index aaf1ed2d17..e35e8177f0 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift @@ -8,18 +8,23 @@ import Foundation -fileprivate enum MigrationLevel { +private enum MigrationLevel { static let initial = 0 static let migratedForKMP = 10 } enum Migrations { - static func migrateForKMP(storage: Storage) { - guard storage.userDefaults.migrationLevel < MigrationLevel.migratedForKMP else { - log.info("KMP directory migration already performed. Skipping.") - return + static func migrate(storage: Storage) { + if storage.userDefaults.migrationLevel < MigrationLevel.migratedForKMP { + migrateForKMP(storage: storage) + storage.userDefaults.migrationLevel = MigrationLevel.migratedForKMP + storage.userDefaults.synchronize() + } else { + log.debug("KMP directory migration already performed. Skipping.") } + } + static func migrateForKMP(storage: Storage) { let languageDir = storage.baseDir.appendingPathComponent("languages") let fontDir = storage.baseDir.appendingPathComponent("fonts") @@ -90,8 +95,6 @@ enum Migrations { // Remove keyboards that were not copied successfully let filteredUserKeyboards = userKeyboards.filter { successfulKeyboards.contains($0.id) } storage.userDefaults.userKeyboards = filteredUserKeyboards - storage.userDefaults.migrationLevel = MigrationLevel.migratedForKMP - storage.userDefaults.synchronize() // TODO: Remove old directory } From 592b26b52d2a839f55d7f147827706ccc7a3de59 Mon Sep 17 00:00:00 2001 From: Gabriel Wong Date: Tue, 12 Dec 2017 14:20:18 +0700 Subject: [PATCH 10/22] Fix help popup positioning --- .../Classes/KeymanWebViewController.swift | 16 +++--- .../KMEI/KeymanEngine/Classes/Manager.swift | 50 +++++-------------- 2 files changed, 19 insertions(+), 47 deletions(-) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/KeymanWebViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/KeymanWebViewController.swift index 549c9870cf..10b4d28e7e 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/KeymanWebViewController.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/KeymanWebViewController.swift @@ -62,10 +62,7 @@ extension KeymanWebViewController { let y = CGFloat(Float(components[1])!) let w = CGFloat(Float(components[2])!) let h = CGFloat(Float(components[3])!) - let isPad = UIDevice.current.userInterfaceIdiom == .pad - let adjY: CGFloat = isPad ? -0.5 : -1.0 - let frame = CGRect(x: x - w / 2.0, y: y - adjY, width: w, height: h) - completion(frame) + completion(KeymanWebViewController.keyFrame(x: x, y: y, w: w, h: h)) } } @@ -198,7 +195,7 @@ extension KeymanWebViewController: WKScriptMessageHandler { let h = CGFloat(Float(fragment[hKey.upperBound.. CGRect { - let isPad = UIDevice.current.userInterfaceIdiom == .pad - let adjY: CGFloat = isPad ? -0.5 : -1.0 - return CGRect(x: x - w / 2.0, y: y - adjY, width: w, height: h) + private static func keyFrame(x: CGFloat, y: CGFloat, w: CGFloat, h: CGFloat) -> CGRect { + // kmw adds w/2 to x. + return CGRect(x: x - w / 2.0, y: y, width: w, height: h) } } diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift index 5bd71d477a..5ed97b1718 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift @@ -984,12 +984,12 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat } keymanWeb.languageMenuPosition { keyFrame in - self.showHelpBubble(at: keyFrame.origin) + self.showHelpBubble(for: keyFrame) } } // TODO: The bulk of this should be moved to PopoverView - func showHelpBubble(at point: CGPoint) { + func showHelpBubble(for keyFrame: CGRect) { self.helpBubbleView?.removeFromSuperview() let helpBubbleView = PopoverView(frame: CGRect.zero) self.helpBubbleView = helpBubbleView @@ -1001,48 +1001,24 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat let isPad = UIDevice.current.userInterfaceIdiom == .pad let sizeMultiplier = CGFloat(isPad ? 1.5 : 1.0) - let frameWidth = 90.0 * sizeMultiplier - let frameHeight = (40.0 + helpBubbleView.arrowHeight) * sizeMultiplier + let popupWidth = 90.0 * sizeMultiplier + let popupHeight = 40.0 * sizeMultiplier + helpBubbleView.arrowHeight let fontSize = 10.0 * sizeMultiplier let inputViewFrame = keymanWeb.view.frame let screenWidth = inputViewFrame.size.width - // TODO: Refactor this out - let isPortrait: Bool - if Util.isSystemKeyboard { - isPortrait = InputViewController.isPortrait - } else { - isPortrait = UIDevice.current.orientation.isPortrait - } + let x = CGFloat.maximum(0, CGFloat.minimum(screenWidth - popupWidth, keyFrame.midX - popupWidth / 2)) + let adjY = CGFloat(3.0) // Tweak the positioning of the popup + let y = keyFrame.minY - popupHeight + adjY - let adjY: CGFloat - if isPortrait { - adjY = Util.isSystemKeyboard ? 9.0 : 4.0 - } else { - adjY = Util.isSystemKeyboard ? 3.0 : 4.0 - } - let px = point.x - let py = point.y + adjY + (isPad ? 2.0 : 1.0) - var x = px - frameWidth / 2 - let y = py - frameHeight - if x < 0 { - x = 0 - } else if x + frameWidth > screenWidth { - x = screenWidth - frameWidth - } + helpBubbleView.frame = CGRect(x: x, y: y, width: popupWidth, height: popupHeight) + helpBubbleView.arrowPosX = keyFrame.midX - x - helpBubbleView.frame = CGRect(x: x, y: y, width: frameWidth, height: frameHeight) - if x == 0 { - helpBubbleView.arrowPosX = px - } else if x == screenWidth - frameWidth { - helpBubbleView.arrowPosX = (px - x) - } else { - helpBubbleView.arrowPosX = frameWidth / 2 - } - - let helpText = UILabel(frame: CGRect(x: 5, y: 0, - width: frameWidth - 10, height: frameHeight - helpBubbleView.arrowHeight)) + let helpText = UILabel(frame: CGRect(x: 5, + y: 0, + width: popupWidth - 10, + height: popupHeight - helpBubbleView.arrowHeight)) helpText.backgroundColor = UIColor.clear helpText.font = helpText.font.withSize(fontSize) helpText.textAlignment = .center From ba1a84dde6d70ef1f4f29b3fa56a43fe48ab160e Mon Sep 17 00:00:00 2001 From: darcywong00 Date: Wed, 13 Dec 2017 05:04:33 +0700 Subject: [PATCH 11/22] Refactor keyboard download code Currently in `KMManager`, there's 3 methods to download a `KMKeyboard` (2 in `KMKeyboardDownloader`, 1 in `KMCustomKeyboardDownloader`) for the 3 use-cases respectively: 1. Add new keyboard (from the cloud) 2. Update a keyboard 3. Install a custom keyboard This PR refactors the following: 1. Code from `MainActivity.checkUrl()`, `KMManager.KMKeyboardDownloader()`, and `KMManager.CustomKeyboardDownloader()` into a new activity `KMKeyboardDownloadActivity`. Code for doing actual file downloads are kept in an `AsyncTask`. 2. `FileDownloader` moved to a separate util class. 3. Confirmation dialogs for downloading keyboards are also refactored into `ConfirmDialogFragment`. --- .../com/tavultesoft/kmapro/MainActivity.java | 141 +-- android/KMEA/app/src/main/AndroidManifest.xml | 6 + .../kmea/ConfirmDialogFragment.java | 45 + .../kmea/KMKeyboardDownloaderActivity.java | 386 ++++++++ .../java/com/tavultesoft/kmea/KMManager.java | 885 +----------------- .../kmea/KeyboardListActivity.java | 55 +- .../kmea/KeyboardPickerActivity.java | 25 +- .../kmea/LanguageListActivity.java | 62 +- .../tavultesoft/kmea/util/FileDownloader.java | 113 +++ 9 files changed, 680 insertions(+), 1038 deletions(-) create mode 100644 android/KMEA/app/src/main/java/com/tavultesoft/kmea/ConfirmDialogFragment.java create mode 100644 android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java create mode 100644 android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/FileDownloader.java diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index a4710604dd..a7c43fe385 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -14,13 +14,14 @@ import java.util.Arrays; import java.util.HashMap; import java.util.List; +import com.tavultesoft.kmea.KMKeyboardDownloaderActivity; import com.tavultesoft.kmea.KMManager; - import com.tavultesoft.kmea.KMManager.KeyboardType; import com.tavultesoft.kmea.KMTextView; import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardDownloadEventListener; import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardEventListener; +import android.app.FragmentManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -179,10 +180,44 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O } KMManager.addKeyboardEventListener(this); - KMManager.addKeyboardDownloadEventListener(this); + KMKeyboardDownloaderActivity.addKeyboardDownloadEventListener(this); - checkUrl(); - getIntent().setData(null); + // If URL provided, start KMKEeyboardDownloaderActivity + Intent intent = getIntent(); + Uri data = intent.getData(); + if (data != null) { + String directStr = data.getQueryParameter(KMKeyboardDownloaderActivity.KMKey_Direct); + boolean isDirect = false; + if (directStr != null && directStr.equals("true")) { + isDirect = true; + } + + String url = data.getQueryParameter(KMKeyboardDownloaderActivity.ARG_URL); + if (url != null) { + boolean isCustom = KMKeyboardDownloaderActivity.isCustom(url); + + int index = url.lastIndexOf("/") + 1; + String jsonFilename = "unknown:"; + if (index >= 0 && index <= url.length()) { + jsonFilename = url.substring(index); + } + + Bundle bundle = new Bundle(); + bundle.putString(KMKeyboardDownloaderActivity.ARG_KEYBOARD, + data.getQueryParameter(KMKeyboardDownloaderActivity.ARG_KEYBOARD)); + bundle.putString(KMKeyboardDownloaderActivity.ARG_LANGUAGE, + data.getQueryParameter(KMKeyboardDownloaderActivity.ARG_LANGUAGE)); + bundle.putBoolean(KMKeyboardDownloaderActivity.ARG_IS_CUSTOM, isCustom); + bundle.putBoolean(KMKeyboardDownloaderActivity.ARG_IS_DIRECT, isDirect); + bundle.putString(KMKeyboardDownloaderActivity.ARG_URL, + data.getQueryParameter(KMKeyboardDownloaderActivity.ARG_URL)); + bundle.putString(KMKeyboardDownloaderActivity.ARG_JSON_URL, jsonFilename); + Intent i = new Intent(getApplicationContext(), KMKeyboardDownloaderActivity.class); + i.putExtras(bundle); + startActivity(i); + } + } + intent.setData(null); } @Override @@ -190,7 +225,7 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O super.onPause(); KMManager.onPause(); KMManager.removeKeyboardEventListener(this); - KMManager.removeKeyboardDownloadEventListener(this); + KMKeyboardDownloaderActivity.removeKeyboardDownloadEventListener(this); SharedPreferences prefs = getSharedPreferences(getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putString(userTextKey, textView.getText().toString()); @@ -470,102 +505,6 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O startActivity(getStartedIntent); } - private boolean checkUrl() { - // return true if requires user action (e.g. Download dialog) - Intent i = getIntent(); - Uri data = i.getData(); - if (data == null) - return false; - - final Context context = this; - final String keyboard = data.getQueryParameter("keyboard"); - final String language = data.getQueryParameter("language"); - final String url = data.getQueryParameter("url"); - - if (url != null) { - /* - int kbIndex = -1; - if (keyboard != null && language != null) - kbIndex = KMManager.getKeyboardIndex(this, keyboard, language); - - if (kbIndex >= 0) { - KMManager.setKeyboard(context, kbIndex); - return false; - }*/ - - String direct = data.getQueryParameter("direct"); - final boolean isDirect; - if (direct != null && direct.equals("true")) { - isDirect = true; - } else { - isDirect = false; - } - - int index = url.lastIndexOf("/") + 1; - String jsonFilename = "unknown"; - if (index >= 0 && index <= url.length()) { - jsonFilename = url.substring(index); - } - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context); - dialogBuilder.setTitle("Custom Keyboard: " + jsonFilename); - dialogBuilder.setMessage("Would you like to download this keyboard?"); - dialogBuilder.setPositiveButton("Download", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - // Download custom keyboard - if (KMManager.hasConnection(context)) { - KMManager.KMCustomKeyboardDownloader.download(context, url, isDirect, true); - } else { - Toast.makeText(context, "No internet connection", Toast.LENGTH_SHORT).show(); - } - } - }); - dialogBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - // Cancel - } - }); - - AlertDialog dialog = dialogBuilder.create(); - dialog.show(); - - return true; - } else if (keyboard != null && language != null && !keyboard.trim().isEmpty() && !language.trim().isEmpty()) { - int kbIndex = KMManager.getKeyboardIndex(this, keyboard, language); - - if (kbIndex >= 0) { - KMManager.setKeyboard(context, kbIndex); - return false; - } - - String kbKey = String.format("%s_%s", language, keyboard); - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context); - dialogBuilder.setTitle("Keyboard: " + kbKey); - dialogBuilder.setMessage("Would you like to download this keyboard?"); - dialogBuilder.setPositiveButton("Download", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - // Download keyboard - if (KMManager.hasConnection(context)) { - KMManager.KMKeyboardDownloader.download(context, KMManager.KMDefault_LegacyPackageID, keyboard, language, true); - } else { - Toast.makeText(context, "No internet connection", Toast.LENGTH_SHORT).show(); - } - } - }); - dialogBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - // Cancel - } - }); - - AlertDialog dialog = dialogBuilder.create(); - dialog.show(); - - return true; - } - - return false; - } - public static Drawable getActionBarDrawable(Context context) { Point size = new Point(); WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); diff --git a/android/KMEA/app/src/main/AndroidManifest.xml b/android/KMEA/app/src/main/AndroidManifest.xml index decccf06cb..22b2fae11d 100644 --- a/android/KMEA/app/src/main/AndroidManifest.xml +++ b/android/KMEA/app/src/main/AndroidManifest.xml @@ -28,6 +28,12 @@ android:label="@string/app_name" android:theme="@android:style/Theme.Holo.Light.Dialog" > + + \ No newline at end of file diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/ConfirmDialogFragment.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/ConfirmDialogFragment.java new file mode 100644 index 0000000000..a740bccbec --- /dev/null +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/ConfirmDialogFragment.java @@ -0,0 +1,45 @@ +package com.tavultesoft.kmea; + +import android.app.AlertDialog; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.DialogInterface; +import android.os.Bundle; +import android.widget.Toast; + +/** + * Confirmation dialog for downloading a Keyman keyboard + */ +public class ConfirmDialogFragment extends DialogFragment { + public static String ARG_TITLE = "ConfirmDialogFragment.title"; + + public ConfirmDialogFragment() { + } + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + Bundle args = getArguments(); + final String title = args.getString(ARG_TITLE); + + return new AlertDialog.Builder(getActivity()) + .setTitle(title) + .setMessage("Would you like to download this keyboard?") + .setPositiveButton("Download", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Download keyboard + if (KMManager.hasConnection(getActivity())) { + KMKeyboardDownloaderActivity.download(getActivity(), true); + } else { + Toast.makeText(getActivity(), "No internet connection", Toast.LENGTH_SHORT).show(); + } + } + }) + .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // Cancel + } + }) + .create(); + } +} diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java new file mode 100644 index 0000000000..e49886ed04 --- /dev/null +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java @@ -0,0 +1,386 @@ +package com.tavultesoft.kmea; + +import android.app.Activity; +import android.app.DialogFragment; +import android.app.ProgressDialog; +import android.content.Context; +import android.os.AsyncTask; +import android.os.Bundle; +import android.util.Log; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.File; +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.HashMap; + +import com.tavultesoft.kmea.util.FileDownloader; + +import static com.tavultesoft.kmea.KMManager.KMDefault_AssetPackages; + +public class KMKeyboardDownloaderActivity extends Activity { + // Keys for cloud keyboard + public static final String ARG_PKG_ID = "KMKeyboardActivity.pkgID"; + public static final String ARG_KB_ID = "KMKeyboardActivity.kbID"; + public static final String ARG_LANG_ID = "KMKeyboardActivity.langID"; + public static final String ARG_KB_NAME = "KMKeyboardActivity.kbName"; + public static final String ARG_LANG_NAME = "KMKeyboardActivity.langName"; + public static final String ARG_IS_CUSTOM = "KMKeyboardActivity.isCustom"; + + // Keys for custom keyboard + public static final String ARG_KEYBOARD = "KMKeyboardActivity.keyboard"; + public static final String ARG_LANGUAGE = "KMKeyboardActivity.language"; + public static final String ARG_IS_DIRECT = "KMKeyboardActivity.isDirect"; + public static final String ARG_URL = "KMKeyboardActivity.url"; + public static final String ARG_JSON_URL = "KMKeyboardActivity.jsonUrl"; + + public static final String kKeymanApiBaseURL = "https://r.keymanweb.com/api/3.0/"; + public static final String kKeymanApiRemoteURL = "https://r.keymanweb.com/api/2.0/remote?url="; + public static final String KMKey_KeyboardBaseURI = "keyboardBaseUri"; + public static final String KMKey_FontBaseURI = "fontBaseUri"; + public static final String KMKey_Direct = "direct"; + + private static String pkgID; + private static String kbID; + private static String langID; + private static String kbName; + private static String langName; + private static Boolean isCustom; + + private static String customKeyboard; + private static String customLanguage; + private static Boolean isDirect; + private static String url; + private static String jsonUrl; + + private static ArrayList kbDownloadEventListeners = null; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle bundle = getIntent().getExtras(); + if (bundle != null) { + pkgID = bundle.getString(ARG_PKG_ID); + kbID = bundle.getString(ARG_KB_ID); + langID = bundle.getString(ARG_LANG_ID); + kbName = bundle.getString(ARG_KB_NAME); + langName = bundle.getString(ARG_LANG_NAME); + isCustom = bundle.getBoolean(ARG_IS_CUSTOM); + + // URL parameters for custom keyboard (if they exist) + customKeyboard = bundle.getString(ARG_KEYBOARD); + customLanguage = bundle.getString(ARG_LANGUAGE); + isDirect = bundle.getBoolean(ARG_IS_DIRECT); + url = bundle.getString(ARG_URL); + jsonUrl = bundle.getString(ARG_JSON_URL); + } else { + return; + } + + Bundle args = new Bundle(); + String title = ""; + if (url != null) { + int index = url.lastIndexOf("/") + 1; + String jsonFilename = "unknown"; + if (index >= 0 && index <= url.length()) { + jsonFilename = url.substring(index); + } + + title = "Custom Keyboard: " + jsonFilename; + } else if (customKeyboard != null && customLanguage != null && + !customKeyboard.trim().isEmpty() && !customLanguage.trim().isEmpty()) { + int kbIndex = KMManager.getKeyboardIndex(getApplicationContext(), customKeyboard, customLanguage); + + if (kbIndex >= 0) { + KMManager.setKeyboard(getApplicationContext(), kbIndex); + // No interaction needed + return; // false + } + + title = customLanguage + "_" + customKeyboard; + } else { + // Download keyboard from cloud server + title = langName + ": " + kbName; + } + + DialogFragment dialog = new ConfirmDialogFragment(); + args.putString(ConfirmDialogFragment.ARG_TITLE, title); + dialog.setArguments(args); + dialog.show( + + getFragmentManager(), "dialog"); +} + + /** + * Async task to download a Keyman keyboard from either Keyman cloud server or custom url + * @param context + * @param showProgressDialog + */ + public static void download(final Context context, final boolean showProgressDialog) { + + new AsyncTask() { + private ProgressDialog progressDialog; + private String languageName = ""; + private String keyboardName = ""; + private String kbVersion = "1.0"; + private String kbIsCustom = isCustom ? "Y" : "N"; + private String font = ""; + private String oskFont = ""; + + @Override + protected void onPreExecute() { + super.onPreExecute(); + if (showProgressDialog) { + progressDialog = new ProgressDialog(context); + progressDialog.setMessage("Downloading keyboard..."); + progressDialog.setCancelable(false); + if (!((Activity) context).isFinishing()) { + progressDialog.show(); + } else { + cancel(true); + progressDialog = null; + } + } + } + + @Override + protected Integer doInBackground(Void... voids) { + int ret = -1; + ArrayList urls = new ArrayList(); + + if (isCancelled()) + return ret; + + try { + String exceptionStr = "Invalid keyboard"; + if (pkgID == null || pkgID.trim().isEmpty() || + langID == null || langID.trim().isEmpty() || + kbID == null || kbID.trim().isEmpty()) + throw new Exception(exceptionStr); + + String deviceType = context.getResources().getString(R.string.device_type); + if (deviceType.equals("AndroidTablet")) { + deviceType = "androidtablet"; + } else { + deviceType = "androidphone"; + } + + // Formerly from KMManager.KMCustomKeyboardDownloader + JSONParser jsonParser = new JSONParser(); + JSONObject kbData = null; + String remoteUrl = ""; + if (isCustom) { + if (isDirect) { + remoteUrl = jsonUrl; + } else { + String encodedUrl = URLEncoder.encode(jsonUrl, "utf-8"); + remoteUrl = String.format("%s%s&device=%s", kKeymanApiRemoteURL, encodedUrl, deviceType); + } + } else { + remoteUrl = String.format("%slanguages/%s/%s?device=%s", kKeymanApiBaseURL, langID, kbID, deviceType); + } + kbData = jsonParser.getJSONObjectFromUrl(remoteUrl); + + exceptionStr = "Could not reach Keyman server"; + if (kbData == null) { + throw new Exception(exceptionStr); + } + + JSONObject options = kbData.optJSONObject(KMManager.KMKey_Options); + JSONObject language = kbData.optJSONObject(KMManager.KMKey_Language); + exceptionStr = "The keyboard could not be installed"; + if (options == null || language == null) { + throw new Exception(exceptionStr); + } + + JSONArray keyboards = language.getJSONArray(KMManager.KMKey_LanguageKeyboards); + String kbBaseUri = options.optString(KMKey_KeyboardBaseURI, ""); + String fontBaseUri = options.optString(KMKey_FontBaseURI, ""); + + if (keyboards == null || kbBaseUri.isEmpty()) + throw new Exception(exceptionStr); + + JSONObject keyboard = keyboards.getJSONObject(0); + if (keyboard == null) + throw new Exception(exceptionStr); + + languageName = language.optString(KMManager.KMKey_Name, ""); + keyboardName = keyboard.optString(KMManager.KMKey_Name, ""); + kbVersion = keyboard.optString(KMManager.KMKey_KeyboardVersion, "1.0"); + font = keyboard.optString(KMManager.KMKey_Font, ""); + oskFont = keyboard.optString(KMManager.KMKey_OskFont, null); + String kbFilename = keyboard.optString(KMManager.KMKey_Filename, ""); + + if (keyboardName.isEmpty() || languageName.isEmpty() || kbFilename.isEmpty()) + throw new Exception(exceptionStr); + + String kbUrl = kbBaseUri + kbFilename; + urls.add(kbUrl); + JSONObject jsonFont = keyboard.optJSONObject(KMManager.KMKey_Font); + JSONObject jsonOskFont = keyboard.optJSONObject(KMManager.KMKey_OskFont); + ArrayList fontUrls = fontUrls(jsonFont, fontBaseUri, true); + ArrayList oskFontUrls = fontUrls(jsonOskFont, fontBaseUri, true); + if (fontUrls != null) + urls.addAll(fontUrls); + if (oskFontUrls != null) { + for (String url : oskFontUrls) { + if (!urls.contains(url)) + urls.add(url); + } + } + + notifyListeners(KeyboardEventHandler.EventType.KEYBOARD_DOWNLOAD_STARTED, 0); + + ret = 1; + int result = 0; + for (String url : urls) { + String directory = KMDefault_AssetPackages + File.separator + pkgID; + File dir = new File(directory); + String filename = ""; + if (url.endsWith(".js")) { + + int start = kbFilename.lastIndexOf("/"); + if (start < 0) { + start = 0; + } else { + start++; + } + if (!kbFilename.contains("-")) { + filename = kbFilename.substring(start, kbFilename.length() - 3) + "-" + kbVersion + ".js"; + } else { + filename = kbFilename.substring(start); + } + } + + result = FileDownloader.download(context, url, directory, filename); + if (result < 0) { + ret = -1; + break; + } + } + } catch (Exception e) { + ret = -1; + Log.e("Keyboard download", "Error: " + e); + } + + return ret; + } + + @Override + protected void onProgressUpdate(Integer... progress) { + // Do nothing + } + + @Override + protected void onPostExecute(Integer result) { + if (showProgressDialog) { + if (progressDialog != null && progressDialog.isShowing()) { + try { + progressDialog.dismiss(); + progressDialog = null; + } catch (Exception e) { + progressDialog = null; + } + } + } + + notifyListeners(KeyboardEventHandler.EventType.KEYBOARD_DOWNLOAD_FINISHED, result); + super.onPostExecute(result); + } + + /** + * Notify listeners when an event happens + * @param eventType + * @param result + */ + protected void notifyListeners(KeyboardEventHandler.EventType eventType, int result) { + if (kbDownloadEventListeners != null) { + HashMap keyboardInfo = new HashMap(); + keyboardInfo.put(KMManager.KMKey_PackageID, pkgID); + keyboardInfo.put(KMManager.KMKey_KeyboardID, kbID); + keyboardInfo.put(KMManager.KMKey_LanguageID, langID); + keyboardInfo.put(KMManager.KMKey_KeyboardName, keyboardName); + keyboardInfo.put(KMManager.KMKey_LanguageName, languageName); + keyboardInfo.put(KMManager.KMKey_KeyboardVersion, kbVersion); + keyboardInfo.put(KMManager.KMKey_CustomKeyboard, kbIsCustom); + keyboardInfo.put(KMManager.KMKey_Font, font); + if (oskFont != null) + keyboardInfo.put(KMManager.KMKey_OskFont, oskFont); + KeyboardEventHandler.notifyListeners(kbDownloadEventListeners, eventType, keyboardInfo, result); + } + } + }.execute(); + } + + public static boolean isCustom(String u) { + boolean ret = false; + if (u != null && !u.contains(KMKeyboardDownloaderActivity.kKeymanApiBaseURL) && + !u.contains(KMKeyboardDownloaderActivity.kKeymanApiRemoteURL)) { + isCustom = true; + } + return ret; + } + + private static ArrayList fontUrls(JSONObject jsonFont, String baseUri, boolean isOskFont) { + if (jsonFont == null) + return null; + + ArrayList urls = new ArrayList(); + JSONArray fontSource = jsonFont.optJSONArray(KMManager.KMKey_FontSource); + if (fontSource != null) { + int fcCount = fontSource.length(); + for (int i = 0; i < fcCount; i++) { + String fontSourceString; + try { + fontSourceString = fontSource.getString(i); + if (fontSourceString.endsWith(".ttf") || fontSourceString.endsWith(".otf")) { + urls.add(baseUri + fontSourceString); + } else if (isOskFont && (fontSourceString.endsWith(".svg") || fontSourceString.endsWith(".woff"))) { + urls.add(baseUri + fontSourceString); + } else if (isOskFont && fontSourceString.contains(".svg#")) { + String fontFilename = fontSourceString.substring(0, fontSourceString.indexOf(".svg#") + 5); + urls.add(baseUri + fontFilename); + } + } catch (JSONException e) { + return null; + } + } + } else { + String fontSourceString; + try { + fontSourceString = jsonFont.getString(KMManager.KMKey_FontSource); + if (fontSourceString.endsWith(".ttf") || fontSourceString.endsWith(".otf")) { + urls.add(baseUri + fontSourceString); + } else if (isOskFont && (fontSourceString.endsWith(".svg") || fontSourceString.endsWith(".woff"))) { + urls.add(baseUri + fontSourceString); + } else if (isOskFont && fontSourceString.contains(".svg#")) { + String fontFilename = fontSourceString.substring(0, fontSourceString.indexOf(".svg#") + 5); + urls.add(baseUri + fontFilename); + } + } catch (JSONException e) { + return null; + } + } + + return urls; + } + + public static void addKeyboardDownloadEventListener(KeyboardEventHandler.OnKeyboardDownloadEventListener listener) { + if (kbDownloadEventListeners == null) { + kbDownloadEventListeners = new ArrayList(); + } + + if (listener != null && !kbDownloadEventListeners.contains(listener)) { + kbDownloadEventListeners.add(listener); + } + } + + public static void removeKeyboardDownloadEventListener(KeyboardEventHandler.OnKeyboardDownloadEventListener listener) { + if (kbDownloadEventListeners != null) { + kbDownloadEventListeners.remove(listener); + } + } +} diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java index 953d4dc488..0c990b2ac4 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java @@ -4,24 +4,15 @@ package com.tavultesoft.kmea; -import java.io.BufferedInputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.URL; -import java.net.URLEncoder; import java.util.ArrayList; import java.util.HashMap; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - import android.annotation.SuppressLint; -import android.app.Activity; -import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -33,7 +24,6 @@ import android.graphics.Typeface; import android.inputmethodservice.InputMethodService; import android.net.ConnectivityManager; import android.net.NetworkInfo; -import android.os.AsyncTask; import android.os.Handler; import android.os.Looper; import android.text.InputType; @@ -53,9 +43,7 @@ import android.widget.FrameLayout; import android.widget.RelativeLayout; import com.tavultesoft.kmea.KeyboardEventHandler.EventType; -import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardDownloadEventListener; import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardEventListener; -import com.tavultesoft.kmea.util.Connection; public final class KMManager { @@ -86,7 +74,6 @@ public final class KMManager { private static InputMethodService IMService; private static boolean debugMode = false; private static boolean shouldAllowSetKeyboard = true; - private static ArrayList kbDownloadEventListeners = null; private static boolean didCopyAssets = false; private static GlobeKeyAction inappKbGlobeKeyAction = GlobeKeyAction.GLOBE_KEY_ACTION_SHOW_MENU; private static GlobeKeyAction sysKbGlobeKeyAction = GlobeKeyAction.GLOBE_KEY_ACTION_SHOW_MENU; @@ -100,9 +87,6 @@ public final class KMManager { protected static KMKeyboard InAppKeyboard = null; protected static KMKeyboard SystemKeyboard = null; - protected static final String kKeymanApiBaseURL = "https://r.keymanweb.com/api/3.0/"; - private static final String kKeymanApiRemoteURL = "https://r.keymanweb.com/api/2.0/remote?url="; - // Keyman public keys public static final String KMKey_ID = "id"; public static final String KMKey_Name = "name"; @@ -131,8 +115,6 @@ public final class KMManager { public static final String KMKey_UserKeyboardIndex = "UserKeyboardIndex"; // Keyman internal keys - protected static final String KMKey_KeyboardBaseURI = "keyboardBaseUri"; - protected static final String KMKey_FontBaseURI = "fontBaseUri"; protected static final String KMKey_ShouldShowHelpBubble = "ShouldShowHelpBubble"; // Default Asset Paths @@ -465,47 +447,53 @@ public final class KMManager { } String[] files = legacyLanguagesDir.list(); - for (String filename : files) { - // Handle keyboards missing version number - if (filename.lastIndexOf("-") < 0) { - if (filename.equals("us.js")) { - File kbFile = new File(legacyLanguagesPath, filename); - kbFile.delete(); - } else if (filename.endsWith(".js")) { - // Append default version number to keyboard and move - String newFilename = filename.substring(0, filename.lastIndexOf(".js")) + "-1.0.js"; - File kbFile = new File(legacyLanguagesPath, filename); - kbFile.renameTo(new File(migratedDir, newFilename)); - } - // Handle keyboards with version number - } else { - // Remove legacy default keyboards - if (filename.startsWith(KMDefault_KeyboardID + "-")) { - File legacyKbFile = new File(legacyLanguagesDir, filename); - if (legacyKbFile.exists()) { - legacyKbFile.delete(); - continue; + if (files != null) { + for (String filename : files) { + // Handle keyboards missing version number + if (filename.lastIndexOf("-") < 0) { + if (filename.equals("us.js")) { + File kbFile = new File(legacyLanguagesPath, filename); + kbFile.delete(); + } else if (filename.endsWith(".js")) { + // Append default version number to keyboard and move + String newFilename = filename.substring(0, filename.lastIndexOf(".js")) + "-1.0.js"; + File kbFile = new File(legacyLanguagesPath, filename); + kbFile.renameTo(new File(migratedDir, newFilename)); + } + // Handle keyboards with version number + } else { + // Remove legacy default keyboards + if (filename.startsWith(KMDefault_KeyboardID + "-")) { + File legacyKbFile = new File(legacyLanguagesDir, filename); + if (legacyKbFile.exists()) { + legacyKbFile.delete(); + continue; + } } - } - // Migrate /languages/*.js keyboards - File kbFile = new File(legacyLanguagesPath, filename); - kbFile.renameTo(new File(migratedDir, filename)); + // Migrate /languages/*.js keyboards + File kbFile = new File(legacyLanguagesPath, filename); + kbFile.renameTo(new File(migratedDir, filename)); + } + } + + // Cleanup empty directory + if (legacyLanguagesDir.list().length == 0) { + legacyLanguagesDir.delete(); } } files = legacyFontsDir.list(); - for (String filename : files) { - File fontFile = new File(legacyFontsPath, filename); - fontFile.renameTo(new File(migratedDir, filename)); - } + if (files != null) { + for (String filename : files) { + File fontFile = new File(legacyFontsPath, filename); + fontFile.renameTo(new File(migratedDir, filename)); + } - // Cleanup empty directories - if (legacyLanguagesDir.list().length == 0) { - legacyLanguagesDir.delete(); - } - if (legacyFontsDir.list().length == 0) { - legacyFontsDir.delete(); + // Cleanup empty directory + if (legacyFontsDir.list().length == 0) { + legacyFontsDir.delete(); + } } } @@ -636,783 +624,6 @@ public final class KMManager { return KeyboardPickerActivity.getKeyboardsList(context); } - public static final class KMKeyboardDownloader { - - public static void download(final Context context, final int languageIndex, final int keyboardIndex, final boolean showProgressDialog) { - new AsyncTask() { - private ProgressDialog progressDialog; - private String packageID = ""; - private String languageID = ""; - private String keyboardID = ""; - private String languageName = ""; - private String keyboardName = ""; - private String kbVersion = "1.0"; - private String isCustom = "N"; - private String font = ""; - private String oskFont = null; - - @Override - protected void onPreExecute() { - super.onPreExecute(); - if (showProgressDialog) { - progressDialog = new ProgressDialog(context); - progressDialog.setMessage("Downloading keyboard..."); - progressDialog.setCancelable(false); - if (!((Activity) context).isFinishing()) { - progressDialog.show(); - } else { - cancel(true); - progressDialog = null; - } - } - } - - @Override - protected Integer doInBackground(Void... voids) { - int ret = -1; - ArrayList urls = new ArrayList(); - - if (isCancelled()) - return ret; - - try { - JSONArray languages = LanguageListActivity.languages(); - JSONObject options = LanguageListActivity.options(); - - if (languages == null || options == null) { - throw new Exception("Language list is empty"); - } - - JSONObject language = languages.getJSONObject(languageIndex); - JSONArray keyboards = language.getJSONArray(KMKey_LanguageKeyboards); - JSONObject keyboard = keyboards.getJSONObject(keyboardIndex); - - if (keyboard.has(KMKey_PackageID)) { - packageID = keyboard.getString(KMKey_PackageID); - } else { - packageID = KMDefault_LegacyPackageID; - } - keyboardID = keyboard.getString(KMKey_ID); - languageID = language.getString(KMKey_ID); - keyboardName = keyboard.getString(KMKey_Name); - languageName = language.getString(KMKey_Name); - kbVersion = keyboard.optString(KMKey_KeyboardVersion, "1.0"); - font = keyboard.optString(KMKey_Font, ""); - oskFont = keyboard.optString(KMKey_OskFont, null); - String kbFilename = keyboard.optString(KMKey_Filename, ""); - - String kbUrl = options.getString(KMKey_KeyboardBaseURI) + kbFilename; - urls.add(kbUrl); - JSONObject jsonFont = keyboard.optJSONObject(KMKey_Font); - JSONObject jsonOskFont = keyboard.optJSONObject(KMKey_OskFont); - String fontBaseUri = options.getString(KMKey_FontBaseURI); - ArrayList fontUrls = fontUrls(jsonFont, fontBaseUri, true); - ArrayList oskFontUrls = fontUrls(jsonOskFont, fontBaseUri, true); - if (fontUrls != null) - urls.addAll(fontUrls); - if (oskFontUrls != null) { - for (String url : oskFontUrls) { - if (!urls.contains(url)) - urls.add(url); - } - } - - // Notify listeners: onDownloadStarted - if (kbDownloadEventListeners != null) { - HashMap keyboardInfo = new HashMap(); - keyboardInfo.put(KMKey_PackageID, packageID); - keyboardInfo.put(KMKey_KeyboardID, keyboardID); - keyboardInfo.put(KMKey_LanguageID, languageID); - keyboardInfo.put(KMKey_KeyboardName, keyboardName); - keyboardInfo.put(KMKey_LanguageName, languageName); - keyboardInfo.put(KMKey_KeyboardVersion, kbVersion); - keyboardInfo.put(KMKey_CustomKeyboard, isCustom); - keyboardInfo.put(KMKey_Font, font); - if (oskFont != null) - keyboardInfo.put(KMKey_OskFont, oskFont); - KeyboardEventHandler.notifyListeners(kbDownloadEventListeners, EventType.KEYBOARD_DOWNLOAD_STARTED, keyboardInfo, 0); - } - - ret = 1; - int result = 0; - for (String url : urls) { - String directory = KMDefault_AssetPackages + File.separator + packageID; - File dir = new File(directory); - String filename = ""; - if (url.endsWith(".js")) { - int start = kbFilename.lastIndexOf("/"); - if (start < 0) { - start = 0; - } else { - start++; - } - if (!kbFilename.contains("-")) { - filename = kbFilename.substring(start, kbFilename.length() - 3) + "-" + kbVersion + ".js"; - } else { - filename = kbFilename.substring(start); - } - } - - result = FileDownloader.download(context, url, directory, filename); - if (result < 0) { - ret = -1; - break; - } - } - } catch (Exception e) { - ret = -1; - Log.e("Keyboard download", "Error: " + e); - } - - return ret; - } - - @Override - protected void onProgressUpdate(Integer... progress) { - // Do nothing - } - - @Override - protected void onPostExecute(Integer result) { - if (showProgressDialog) { - if (progressDialog != null && progressDialog.isShowing()) { - try { - progressDialog.dismiss(); - progressDialog = null; - } catch (Exception e) { - progressDialog = null; - } - } - } - - // Notify listeners: onDownloadFinished - if (kbDownloadEventListeners != null) { - HashMap keyboardInfo = new HashMap(); - keyboardInfo.put(KMKey_PackageID, packageID); - keyboardInfo.put(KMKey_KeyboardID, keyboardID); - keyboardInfo.put(KMKey_LanguageID, languageID); - keyboardInfo.put(KMKey_KeyboardName, keyboardName); - keyboardInfo.put(KMKey_LanguageName, languageName); - keyboardInfo.put(KMKey_KeyboardVersion, kbVersion); - keyboardInfo.put(KMKey_CustomKeyboard, isCustom); - keyboardInfo.put(KMKey_Font, font); - if (oskFont != null) - keyboardInfo.put(KMKey_OskFont, oskFont); - KeyboardEventHandler.notifyListeners(kbDownloadEventListeners, EventType.KEYBOARD_DOWNLOAD_FINISHED, keyboardInfo, result); - } - } - }.execute(); - } - - public static void download(final Context context, final String packageID, final String keyboardID, final String languageID, final boolean showProgressDialog) { - new AsyncTask() { - private ProgressDialog progressDialog; - private String languageName = ""; - private String keyboardName = ""; - private String kbVersion = "1.0"; - private String isCustom = "N"; - private String font = ""; - private String oskFont = ""; - - @Override - protected void onPreExecute() { - super.onPreExecute(); - if (showProgressDialog) { - progressDialog = new ProgressDialog(context); - progressDialog.setMessage("Downloading keyboard..."); - progressDialog.setCancelable(false); - if (!((Activity) context).isFinishing()) { - progressDialog.show(); - } else { - cancel(true); - progressDialog = null; - } - } - } - - @Override - protected Integer doInBackground(Void... voids) { - int ret = -1; - ArrayList urls = new ArrayList(); - - if (isCancelled()) - return ret; - - try { - String exceptionStr = "Invalid keyboard"; - if (packageID == null || packageID.trim().isEmpty() || - languageID == null || languageID.trim().isEmpty() || - keyboardID == null || keyboardID.trim().isEmpty()) - throw new Exception(exceptionStr); - - String deviceType = context.getResources().getString(R.string.device_type); - if (deviceType.equals("AndroidTablet")) { - deviceType = "androidtablet"; - } else { - deviceType = "androidphone"; - } - - String jsonUrl = String.format("%slanguages/%s/%s?device=%s", kKeymanApiBaseURL, languageID, keyboardID, deviceType); - JSONParser jsonParser = new JSONParser(); - JSONObject kbData = jsonParser.getJSONObjectFromUrl(jsonUrl); - - exceptionStr = "Could not reach Keyman server"; - if (kbData == null) { - throw new Exception(exceptionStr); - } - - JSONObject options = kbData.optJSONObject(KMKey_Options); - JSONObject language = kbData.optJSONObject(KMKey_Language); - exceptionStr = "The keyboard could not be installed"; - if (options == null || language == null) { - throw new Exception(exceptionStr); - } - - JSONArray keyboards = language.getJSONArray(KMKey_LanguageKeyboards); - String kbBaseUri = options.optString(KMKey_KeyboardBaseURI, ""); - String fontBaseUri = options.optString(KMKey_FontBaseURI, ""); - - if (keyboards == null || kbBaseUri.isEmpty()) - throw new Exception(exceptionStr); - - JSONObject keyboard = keyboards.getJSONObject(0); - if (keyboard == null) - throw new Exception(exceptionStr); - - languageName = language.optString(KMKey_Name, ""); - keyboardName = keyboard.optString(KMKey_Name, ""); - kbVersion = keyboard.optString(KMKey_KeyboardVersion, "1.0"); - font = keyboard.optString(KMKey_Font, ""); - oskFont = keyboard.optString(KMKey_OskFont, null); - String kbFilename = keyboard.optString(KMKey_Filename, ""); - - if (keyboardName.isEmpty() || languageName.isEmpty() || kbFilename.isEmpty()) - throw new Exception(exceptionStr); - - String kbUrl = kbBaseUri + kbFilename; - urls.add(kbUrl); - JSONObject jsonFont = keyboard.optJSONObject(KMKey_Font); - JSONObject jsonOskFont = keyboard.optJSONObject(KMKey_OskFont); - ArrayList fontUrls = fontUrls(jsonFont, fontBaseUri, true); - ArrayList oskFontUrls = fontUrls(jsonOskFont, fontBaseUri, true); - if (fontUrls != null) - urls.addAll(fontUrls); - if (oskFontUrls != null) { - for (String url : oskFontUrls) { - if (!urls.contains(url)) - urls.add(url); - } - } - - // Notify listeners: onDownloadStarted - if (kbDownloadEventListeners != null) { - HashMap keyboardInfo = new HashMap(); - keyboardInfo.put(KMKey_PackageID, packageID); - keyboardInfo.put(KMKey_KeyboardID, keyboardID); - keyboardInfo.put(KMKey_LanguageID, languageID); - keyboardInfo.put(KMKey_KeyboardName, keyboardName); - keyboardInfo.put(KMKey_LanguageName, languageName); - keyboardInfo.put(KMKey_KeyboardVersion, kbVersion); - keyboardInfo.put(KMKey_CustomKeyboard, isCustom); - keyboardInfo.put(KMKey_Font, font); - if (oskFont != null) - keyboardInfo.put(KMKey_OskFont, oskFont); - KeyboardEventHandler.notifyListeners(kbDownloadEventListeners, EventType.KEYBOARD_DOWNLOAD_STARTED, keyboardInfo, 0); - } - - ret = 1; - int result = 0; - for (String url : urls) { - String directory = KMDefault_AssetPackages + File.separator + packageID; - File dir = new File(directory); - String filename = ""; - if (url.endsWith(".js")) { - - int start = kbFilename.lastIndexOf("/"); - if (start < 0) { - start = 0; - } else { - start++; - } - if (!kbFilename.contains("-")) { - filename = kbFilename.substring(start, kbFilename.length() - 3) + "-" + kbVersion + ".js"; - } else { - filename = kbFilename.substring(start); - } - } - - result = FileDownloader.download(context, url, directory, filename); - if (result < 0) { - ret = -1; - break; - } - } - } catch (Exception e) { - ret = -1; - Log.e("Keyboard download", "Error: " + e); - } - - return ret; - } - - @Override - protected void onProgressUpdate(Integer... progress) { - // Do nothing - } - - @Override - protected void onPostExecute(Integer result) { - if (showProgressDialog) { - if (progressDialog != null && progressDialog.isShowing()) { - try { - progressDialog.dismiss(); - progressDialog = null; - } catch (Exception e) { - progressDialog = null; - } - } - } - - // Notify listeners: onDownloadFinished - if (kbDownloadEventListeners != null) { - HashMap keyboardInfo = new HashMap(); - keyboardInfo.put(KMKey_KeyboardID, keyboardID); - keyboardInfo.put(KMKey_LanguageID, languageID); - keyboardInfo.put(KMKey_KeyboardName, keyboardName); - keyboardInfo.put(KMKey_LanguageName, languageName); - keyboardInfo.put(KMKey_KeyboardVersion, kbVersion); - keyboardInfo.put(KMKey_CustomKeyboard, isCustom); - keyboardInfo.put(KMKey_Font, font); - if (oskFont != null) - keyboardInfo.put(KMKey_OskFont, oskFont); - KeyboardEventHandler.notifyListeners(kbDownloadEventListeners, EventType.KEYBOARD_DOWNLOAD_FINISHED, keyboardInfo, result); - } - } - }.execute(); - } - - private static ArrayList fontUrls(JSONObject jsonFont, String baseUri, boolean isOskFont) { - if (jsonFont == null) - return null; - - ArrayList urls = new ArrayList(); - JSONArray fontSource = jsonFont.optJSONArray(KMKey_FontSource); - if (fontSource != null) { - int fcCount = fontSource.length(); - for (int i = 0; i < fcCount; i++) { - String fontSourceString; - try { - fontSourceString = fontSource.getString(i); - if (fontSourceString.endsWith(".ttf") || fontSourceString.endsWith(".otf")) { - urls.add(baseUri + fontSourceString); - } else if (isOskFont && (fontSourceString.endsWith(".svg") || fontSourceString.endsWith(".woff"))) { - urls.add(baseUri + fontSourceString); - } else if (isOskFont && fontSourceString.contains(".svg#")) { - String fontFilename = fontSourceString.substring(0, fontSourceString.indexOf(".svg#") + 5); - urls.add(baseUri + fontFilename); - } - } catch (JSONException e) { - return null; - } - } - } else { - String fontSourceString; - try { - fontSourceString = jsonFont.getString(KMKey_FontSource); - if (fontSourceString.endsWith(".ttf") || fontSourceString.endsWith(".otf")) { - urls.add(baseUri + fontSourceString); - } else if (isOskFont && (fontSourceString.endsWith(".svg") || fontSourceString.endsWith(".woff"))) { - urls.add(baseUri + fontSourceString); - } else if (isOskFont && fontSourceString.contains(".svg#")) { - String fontFilename = fontSourceString.substring(0, fontSourceString.indexOf(".svg#") + 5); - urls.add(baseUri + fontFilename); - } - } catch (JSONException e) { - return null; - } - } - - return urls; - } - } - - public static final class KMCustomKeyboardDownloader { - - public static void download(final Context context, final String jsonUrl, final boolean isDirect, final boolean showProgressDialog) { - new AsyncTask() { - private ProgressDialog progressDialog; - private String packageID = ""; - private String keyboardID = ""; - private String languageID = ""; - private String keyboardName = ""; - private String languageName = ""; - private String kbVersion = "1.0"; - private String isCustom = "Y"; - private String font = ""; - private String oskFont = ""; - - @Override - protected void onPreExecute() { - super.onPreExecute(); - if (showProgressDialog) { - progressDialog = new ProgressDialog(context); - progressDialog.setMessage("Downloading keyboard..."); - progressDialog.setCancelable(false); - if (!((Activity) context).isFinishing()) { - progressDialog.show(); - } else { - cancel(true); - progressDialog = null; - } - } - } - - @Override - protected Integer doInBackground(Void... voids) { - int ret = -1; - ArrayList urls = new ArrayList(); - - if (isCancelled()) - return ret; - - try { - JSONParser jsonParser = new JSONParser(); - JSONObject customKb = null; - if (isDirect) { - customKb = jsonParser.getJSONObjectFromUrl(jsonUrl); - } else { - String deviceType = context.getResources().getString(R.string.device_type); - if (deviceType.equals("AndroidTablet")) { - deviceType = "androidtablet"; - } else { - deviceType = "androidphone"; - } - - String encodedUrl = URLEncoder.encode(jsonUrl, "utf-8"); - String remoteUrl = String.format("%s%s&device=%s", kKeymanApiRemoteURL, encodedUrl, deviceType); - customKb = jsonParser.getJSONObjectFromUrl(remoteUrl); - } - - String exceptionStr = "Failed to fetch JSON object from the URL"; - if (customKb == null) { - throw new Exception(exceptionStr); - } - - JSONObject options = customKb.optJSONObject(KMKey_Options); - exceptionStr = "The keyboard could not be installed"; - if (options == null) { - throw new Exception(exceptionStr); - } - - String kbBaseUri = options.optString(KMKey_KeyboardBaseURI, ""); - String fontBaseUri = options.optString(KMKey_FontBaseURI, ""); - - JSONObject keyboard = customKb.getJSONObject(KMKey_Keyboard); - - if (keyboard == null || kbBaseUri.isEmpty()) { - throw new Exception(exceptionStr); - } - - JSONArray languages = keyboard.optJSONArray(KMKey_Languages); - - packageID = keyboard.optString(KMKey_PackageID, ""); - if (packageID == null || packageID.isEmpty()) { - packageID = KMDefault_LegacyPackageID; - } - keyboardID = keyboard.optString(KMKey_ID, ""); - keyboardName = keyboard.optString(KMKey_Name, ""); - kbVersion = keyboard.optString(KMKey_KeyboardVersion, "1.0"); - font = keyboard.optString(KMKey_Font, "").replace("\"" + KMManager.KMKey_Filename + "\"", "\"" + KMManager.KMKey_FontSource + "\""); - oskFont = keyboard.optString(KMKey_OskFont, "").replace("\"" + KMManager.KMKey_Filename + "\"", "\"" + KMManager.KMKey_FontSource + "\""); - if (oskFont.isEmpty()) { - oskFont = null; - } - String kbFilename = keyboard.optString(KMKey_Filename, ""); - - if (packageID == null || packageID.isEmpty() || - languages == null || keyboardID.isEmpty() || - keyboardName.isEmpty() || kbFilename.isEmpty()) { - throw new Exception(exceptionStr); - } - - String kbUrl = kbBaseUri + kbFilename; - urls.add(kbUrl); - JSONObject jsonFont = keyboard.optJSONObject(KMKey_Font); - JSONObject jsonOskFont = keyboard.optJSONObject(KMKey_OskFont); - ArrayList fontUrls = fontUrls(jsonFont, fontBaseUri, true); - ArrayList oskFontUrls = fontUrls(jsonOskFont, fontBaseUri, true); - if (fontUrls != null) { - urls.addAll(fontUrls); - } - if (oskFontUrls != null) { - for (String url : oskFontUrls) { - if (!urls.contains(url)) { - urls.add(url); - } - } - } - - languageID = ""; - languageName = ""; - int langCount = languages.length(); - for (int i = 0; i < langCount; i++) { - languageID += languages.getJSONObject(i).getString(KMKey_ID); - languageName += languages.getJSONObject(i).getString(KMKey_Name); - if (i < langCount - 1) { - languageID += ";"; - languageName += ";"; - } - } - - // Notify listeners: onDownloadStarted - if (kbDownloadEventListeners != null) { - HashMap keyboardInfo = new HashMap(); - keyboardInfo.put(KMKey_PackageID, packageID); - keyboardInfo.put(KMKey_KeyboardID, keyboardID); - keyboardInfo.put(KMKey_LanguageID, languageID); - keyboardInfo.put(KMKey_KeyboardName, keyboardName); - keyboardInfo.put(KMKey_LanguageName, languageName); - keyboardInfo.put(KMKey_KeyboardVersion, kbVersion); - keyboardInfo.put(KMKey_CustomKeyboard, isCustom); - keyboardInfo.put(KMKey_Font, font); - if (oskFont != null) { - keyboardInfo.put(KMKey_OskFont, oskFont); - } - KeyboardEventHandler.notifyListeners(kbDownloadEventListeners, EventType.KEYBOARD_DOWNLOAD_STARTED, keyboardInfo, 0); - } - - ret = 1; - int result = 0; - for (String url : urls) { - String directory = KMDefault_AssetPackages + File.separator + packageID; - File dir = new File(directory); - String filename = ""; - if (url.endsWith(".js")) { - int start = kbFilename.lastIndexOf("/"); - if (start < 0) { - start = 0; - } else { - start++; - } - if (!kbFilename.contains("-")) { - filename = kbFilename.substring(start, kbFilename.length() - 3) + "-" + kbVersion + ".js"; - } else { - filename = kbFilename.substring(start); - } - } - - result = FileDownloader.download(context, url, directory, filename); - if (result < 0) { - ret = -1; - break; - } - } - } catch (Exception e) { - ret = -1; - Log.e("Keyboard download", "Error: " + e); - e.printStackTrace(); - } - - return ret; - } - - @Override - protected void onProgressUpdate(Integer... progress) { - // Do nothing - } - - @Override - protected void onPostExecute(Integer result) { - if (showProgressDialog) { - if (progressDialog != null && progressDialog.isShowing()) { - try { - progressDialog.dismiss(); - progressDialog = null; - } catch (Exception e) { - progressDialog = null; - } - } - } - - // Notify listeners: onDownloadFinished - if (kbDownloadEventListeners != null) { - HashMap keyboardInfo = new HashMap(); - keyboardInfo.put(KMKey_PackageID, packageID); - keyboardInfo.put(KMKey_KeyboardID, keyboardID); - keyboardInfo.put(KMKey_LanguageID, languageID); - keyboardInfo.put(KMKey_KeyboardName, keyboardName); - keyboardInfo.put(KMKey_LanguageName, languageName); - keyboardInfo.put(KMKey_KeyboardVersion, kbVersion); - keyboardInfo.put(KMKey_CustomKeyboard, isCustom); - keyboardInfo.put(KMKey_Font, font); - if (oskFont != null) - keyboardInfo.put(KMKey_OskFont, oskFont); - KeyboardEventHandler.notifyListeners(kbDownloadEventListeners, EventType.KEYBOARD_DOWNLOAD_FINISHED, keyboardInfo, result); - } - - if (result > 0) { - if (KMManager.InAppKeyboard != null) { - InAppKeyboard.loadKeyboard(); - } - if ( KMManager.SystemKeyboard != null) { - SystemKeyboard.loadKeyboard(); - } - } - } - }.execute(); - } - - private static ArrayList fontUrls(JSONObject jsonFont, String baseUri, boolean isOskFont) { - if (jsonFont == null) - return null; - - ArrayList urls = new ArrayList(); - JSONArray fontSource = jsonFont.optJSONArray(KMKey_FontSource); - if (fontSource == null) - fontSource = jsonFont.optJSONArray(KMKey_Filename); // Font filename is deprecated - - if (fontSource != null) { - int fcCount = fontSource.length(); - for (int i = 0; i < fcCount; i++) { - String fontSourceString; - try { - fontSourceString = fontSource.getString(i); - if (fontSourceString.endsWith(".ttf") || fontSourceString.endsWith(".otf")) { - urls.add(baseUri + fontSourceString); - } else if (isOskFont && (fontSourceString.endsWith(".svg") || fontSourceString.endsWith(".woff"))) { - urls.add(baseUri + fontSourceString); - } else if (isOskFont && fontSourceString.contains(".svg#")) { - String fontFilename = fontSourceString.substring(0, fontSourceString.indexOf(".svg#") + 5); - urls.add(baseUri + fontFilename); - } - } catch (JSONException e) { - return null; - } - } - } else { - String fontSourceString; - try { - fontSourceString = jsonFont.optString(KMKey_FontSource, null); - if (fontSourceString == null) - fontSourceString = jsonFont.getString(KMKey_Filename); // Font filename is deprecated - - if (fontSourceString.endsWith(".ttf") || fontSourceString.endsWith(".otf")) { - urls.add(baseUri + fontSourceString); - } else if (isOskFont && (fontSourceString.endsWith(".svg") || fontSourceString.endsWith(".woff"))) { - urls.add(baseUri + fontSourceString); - } else if (isOskFont && fontSourceString.contains(".svg#")) { - String fontFilename = fontSourceString.substring(0, fontSourceString.indexOf(".svg#") + 5); - urls.add(baseUri + fontFilename); - } - } catch (JSONException e) { - return null; - } - } - - return urls; - } - } - - private static final class FileDownloader { - - /** - * Utility to download a file from urlStr and store it at directory/filename - * @param context - * @param urlStr URL of the file to download - * @param directory Path to store the file. If directory does not exist, it will be created - * @param filename Destination filename. If blank, it will use the filename from the URL - * @return - */ - public static int download(Context context, String urlStr, String directory, String filename) { - int ret = -1; - String fileName = ""; - String tmpFileName = ""; - File tmpFile = null; - File file = null; - - try { - if (directory == null) { - directory = ""; - } - directory = directory.trim(); - - String dirPath; - if (directory.length() != 0) { - directory = directory + "/"; - dirPath = context.getDir("data", Context.MODE_PRIVATE) + "/" + directory; - } else { - dirPath = context.getDir("data", Context.MODE_PRIVATE).toString(); - } - File dir = new File(dirPath); - if (!dir.exists()) { - dir.mkdir(); - } - - if (Connection.initialize(urlStr)) { - InputStream binStream = new BufferedInputStream(Connection.getInputStream(), 4096); - byte[] buff = new byte[4096]; - - filename = filename.trim(); - if (filename == null || filename.isEmpty()) { - fileName = Connection.getFile().substring(Connection.getFile().lastIndexOf('/') + 1); - if (fileName.lastIndexOf(".js") > 0 && !fileName.contains("-")) { - fileName = fileName.substring(0, filename.lastIndexOf(".js")) + "-1.0.js"; - } - } else { - fileName = filename; - } - tmpFileName = String.format("%s.tmp", fileName); - file = new File(dirPath, fileName); - tmpFile = new File(dirPath, tmpFileName); - FileOutputStream fos = new FileOutputStream(tmpFile); - - int len; - while ((len = binStream.read(buff)) != -1) { - fos.write(buff, 0, len); - } - - fos.flush(); - fos.close(); - binStream.close(); - - ret = 1; - } - } catch (Exception e) { - ret = -1; - Log.e("FD: Download failed!", "Error: " + e); - } finally { - if (ret > 0) { - if (tmpFile.exists() && tmpFile.length() > 0) { - if (file.exists()) { - file.delete(); - } - if (!tmpFile.renameTo(file)) { - ret = -1; - } else if (isDebugMode()) { - Log.d("FD: Download finished", "Filename = " + file.toString()); - } - } else { - ret = -1; - } - } else { - if (file.exists()) { - file.delete(); - } - if (tmpFile.exists()) { - tmpFile.delete(); - } - if (isDebugMode()) { - Log.d("FD: Could not download", "Filename = " + file.toString()); - } - } - - Connection.disconnect(); - } - - return ret; - } - } - public static boolean addKeyboard(Context context, HashMap keyboardInfo) { return KeyboardPickerActivity.addKeyboard(context, keyboardInfo); } @@ -1731,22 +942,6 @@ public final class KMManager { KMKeyboard.removeOnKeyboardEventListener(listener); } - public static void addKeyboardDownloadEventListener(OnKeyboardDownloadEventListener listener) { - if (kbDownloadEventListeners == null) { - kbDownloadEventListeners = new ArrayList(); - } - - if (listener != null && !kbDownloadEventListeners.contains(listener)) { - kbDownloadEventListeners.add(listener); - } - } - - public static void removeKeyboardDownloadEventListener(OnKeyboardDownloadEventListener listener) { - if (kbDownloadEventListeners != null) { - kbDownloadEventListeners.remove(listener); - } - } - public static int getKeyboardHeight(Context context) { return (int) context.getResources().getDimension(R.dimen.keyboard_height); } diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java index 90a0685c99..59293496e4 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java @@ -14,9 +14,8 @@ import org.json.JSONObject; import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardDownloadEventListener; import android.app.Activity; -import android.app.AlertDialog; +import android.app.DialogFragment; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.util.Log; @@ -103,45 +102,23 @@ public final class KeyboardListActivity extends Activity implements OnKeyboardDo @Override public void onItemClick(AdapterView parent, View view, final int position, long id) { HashMap kbInfo = LanguageListActivity.getKeyboardInfo(langIndex, position); - String pkgID = kbInfo.get(KMManager.KMKey_PackageID); - String kbID = kbInfo.get(KMManager.KMKey_KeyboardID); - String langID = kbInfo.get(KMManager.KMKey_LanguageID); + final String pkgID = kbInfo.get(KMManager.KMKey_PackageID); + final String kbID = kbInfo.get(KMManager.KMKey_KeyboardID); + final String langID = kbInfo.get(KMManager.KMKey_LanguageID); String kbName = kbInfo.get(KMManager.KMKey_KeyboardName); String langName = kbInfo.get(KMManager.KMKey_LanguageName); - String kFont = kbInfo.get(KMManager.KMKey_Font); - String kOskFont = kbInfo.get(KMManager.KMKey_OskFont); - KMManager.KeyboardState kbState = KMManager.getKeyboardState(context, pkgID, kbID, langID); - //if (kbState == KMManager.KeyboardState.KEYBOARD_STATE_NEEDS_DOWNLOAD) { - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context); - dialogBuilder.setTitle(langName + ": " + kbName); - dialogBuilder.setMessage("Would you like to download this keyboard?"); - dialogBuilder.setPositiveButton("Download", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - // Download keyboard - if (KMManager.hasConnection(context)) { - KMManager.KMKeyboardDownloader.download(context, langIndex, position, true); - } else { - Toast.makeText(context, "No internet connection", Toast.LENGTH_SHORT).show(); - } - } - }); - dialogBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - // Cancel - } - }); + Bundle args = new Bundle(); + args.putString(KMKeyboardDownloaderActivity.ARG_PKG_ID, pkgID); + args.putString(KMKeyboardDownloaderActivity.ARG_KB_ID, kbID); + args.putString(KMKeyboardDownloaderActivity.ARG_LANG_ID, langID); + args.putString(KMKeyboardDownloaderActivity.ARG_KB_NAME, kbName); + args.putString(KMKeyboardDownloaderActivity.ARG_LANG_NAME, langName); + args.putBoolean(KMKeyboardDownloaderActivity.ARG_IS_CUSTOM, false); + Intent i = new Intent(getApplicationContext(), KMKeyboardDownloaderActivity.class); + i.putExtras(args); + startActivity(i); - AlertDialog dialog = dialogBuilder.create(); - dialog.show(); - /*} else { - KeyboardPickerActivity.addKeyboard(context, kbInfo); - if (KMManager.InAppKeyboard != null) - KMManager.InAppKeyboard.setKeyboard(kbID, langID, kbName, langName, kFont, kOskFont); - if (KMManager.SystemKeyboard != null) - KMManager.SystemKeyboard.setKeyboard(kbID, langID, kbName, langName, kFont, kOskFont); - finish(); - }*/ } }); } catch (JSONException e) { @@ -152,13 +129,13 @@ public final class KeyboardListActivity extends Activity implements OnKeyboardDo @Override protected void onResume() { super.onResume(); - KMManager.addKeyboardDownloadEventListener(this); + KMKeyboardDownloaderActivity.addKeyboardDownloadEventListener(this); } @Override protected void onPause() { super.onPause(); - KMManager.removeKeyboardDownloadEventListener(this); + KMKeyboardDownloaderActivity.removeKeyboardDownloadEventListener(this); } @Override diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java index 164f46c8e5..2bdf0199c7 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java @@ -180,7 +180,7 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard @Override protected void onResume() { super.onResume(); - KMManager.addKeyboardDownloadEventListener(this); + KMKeyboardDownloaderActivity.addKeyboardDownloadEventListener(this); BaseAdapter adapter = (BaseAdapter) listAdapter; adapter.notifyDataSetChanged(); int curKbPos = getCurrentKeyboardIndex(); @@ -234,7 +234,7 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard @Override protected void onPause() { super.onPause(); - KMManager.removeKeyboardDownloadEventListener(this); + KMKeyboardDownloaderActivity.removeKeyboardDownloadEventListener(this); if (didUpdate) { if (KMManager.InAppKeyboard != null) { KMManager.InAppKeyboard.loadKeyboard(); @@ -400,7 +400,7 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard list = (ArrayList>) inputStream.readObject(); inputStream.close(); } catch (Exception e) { - Log.e("Failed to read keyboards list", "Error: " + e); + Log.e("KeyboardPickerActivity", "Failed to read keyboards list. Error: " + e); list = null; } } @@ -563,7 +563,7 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard String languageID = keyboardsList.get(i).get(KMManager.KMKey_LanguageID); String keyboardID = keyboardsList.get(i).get(KMManager.KMKey_KeyboardID); String kbVersion = keyboardsList.get(i).get(KMManager.KMKey_KeyboardVersion); - String url = String.format("%slanguages/%s/%s?device=%s", KMManager.kKeymanApiBaseURL, languageID, keyboardID, deviceType); + String url = String.format("%slanguages/%s/%s?device=%s", KMKeyboardDownloaderActivity.kKeymanApiBaseURL, languageID, keyboardID, deviceType); JSONObject kbData = jsonParser.getJSONObjectFromUrl(url); JSONObject language = kbData.optJSONObject(KMManager.KMKey_Language); JSONArray keyboards = language.getJSONArray(KMManager.KMKey_LanguageKeyboards); @@ -615,10 +615,10 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard if (KMManager.hasConnection(context)) { int len = keyboardsList.size(); for (int i = 0; i < len; i++) { - String packageID = keyboardsList.get(i).get(KMManager.KMKey_PackageID); - String languageID = keyboardsList.get(i).get(KMManager.KMKey_LanguageID); - String keyboardID = keyboardsList.get(i).get(KMManager.KMKey_KeyboardID); - String kbKey = String.format("%s_%s", languageID, keyboardID); + String pkgID = keyboardsList.get(i).get(KMManager.KMKey_PackageID); + String kbID = keyboardsList.get(i).get(KMManager.KMKey_KeyboardID); + String langID = keyboardsList.get(i).get(KMManager.KMKey_LanguageID); + String kbKey = String.format("%s_%s", langID, kbID); String kbVersion = keyboardsList.get(i).get(KMManager.KMKey_KeyboardVersion); String newKbVersion = keyboardVersions.get(kbKey); if (newKbVersion != null) { @@ -631,7 +631,14 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard updateProgress.show(); } - KMManager.KMKeyboardDownloader.download(context, packageID, keyboardID, languageID, false); + Bundle bundle = new Bundle(); + bundle.putString(KMKeyboardDownloaderActivity.ARG_PKG_ID, pkgID); + bundle.putString(KMKeyboardDownloaderActivity.ARG_KB_ID, kbID); + bundle.putString(KMKeyboardDownloaderActivity.ARG_LANG_ID, langID); + bundle.putBoolean(KMKeyboardDownloaderActivity.ARG_IS_CUSTOM, false); + Intent intent = new Intent(context, KMKeyboardDownloaderActivity.class); + intent.putExtras(bundle); + context.startActivity(intent); } } } diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java index 076da058b8..08c23a621f 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java @@ -24,10 +24,8 @@ import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardDownloadEventListener import android.annotation.SuppressLint; import android.app.Activity; -import android.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; @@ -97,7 +95,7 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo @Override protected void onResume() { super.onResume(); - KMManager.addKeyboardDownloadEventListener(this); + KMKeyboardDownloaderActivity.addKeyboardDownloadEventListener(this); if (!didExecuteParser) { didExecuteParser = true; new JSONParse().execute(); @@ -107,7 +105,7 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo @Override protected void onPause() { super.onPause(); - KMManager.removeKeyboardDownloadEventListener(this); + KMKeyboardDownloaderActivity.removeKeyboardDownloadEventListener(this); } @Override @@ -272,7 +270,7 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo jsonObj = new JSONObject(objInput.readObject().toString()); objInput.close(); } catch (Exception e) { - Log.e("Failed to read from cache file", "Error: " + e); + Log.e("LanguageListActivity", "Failed to read from cache file. Error: " + e); jsonObj = null; } @@ -287,7 +285,7 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo objOutput.writeObject(jsonObj.toString()); objOutput.close(); } catch (Exception e) { - Log.e("Failed to save to cache file", "Error: " + e); + Log.e("LanguageListActivity", "Failed to save to cache file. Error: " + e); } } @@ -361,7 +359,7 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo deviceType = "androidphone"; } - jsonObj = jsonParser.getJSONObjectFromUrl(KMManager.kKeymanApiBaseURL + "languages?device=" + deviceType); + jsonObj = jsonParser.getJSONObjectFromUrl(KMKeyboardDownloaderActivity.kKeymanApiBaseURL + "languages?device=" + deviceType); } catch (Exception e) { jsonObj = null; } @@ -494,44 +492,20 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo startActivity(i); } else { HashMap kbInfo = getKeyboardInfo(selectedIndex, 0); - String pkgID = kbInfo.get(KMManager.KMKey_PackageID); - String kbID = kbInfo.get(KMManager.KMKey_KeyboardID); - String langID = kbInfo.get(KMManager.KMKey_LanguageID); - String kFont = kbInfo.get(KMManager.KMKey_Font); - String kOskFont = kbInfo.get(KMManager.KMKey_OskFont); - KMManager.KeyboardState kbState = KMManager.getKeyboardState(context, pkgID, kbID, langID); - //if (kbState == KMManager.KeyboardState.KEYBOARD_STATE_NEEDS_DOWNLOAD) { - AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context); - dialogBuilder.setTitle(langName + ": " + kbName); - dialogBuilder.setMessage("Would you like to download this keyboard?"); - dialogBuilder.setPositiveButton("Download", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - // Download keyboard - if (KMManager.hasConnection(context)) { - KMManager.KMKeyboardDownloader.download(context, position, 0, true); - } else { - Toast.makeText(context, "No internet connection", Toast.LENGTH_SHORT).show(); - } - } - }); + final String pkgID = kbInfo.get(KMManager.KMKey_PackageID); + final String kbID = kbInfo.get(KMManager.KMKey_KeyboardID); + final String langID = kbInfo.get(KMManager.KMKey_LanguageID); - dialogBuilder.setNegativeButton("Cancel", new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - // Cancel - } - }); - - AlertDialog dialog = dialogBuilder.create(); - dialog.show(); - /*} - else { - KeyboardPickerActivity.addKeyboard(context, kbInfo); - if (KMManager.InAppKeyboard != null) - KMManager.InAppKeyboard.setKeyboard(kbID, langID, kbName, langName, kFont, kOskFont); - if (KMManager.SystemKeyboard != null) - KMManager.SystemKeyboard.setKeyboard(kbID, langID, kbName, langName, kFont, kOskFont); - finish(); - }*/ + Bundle bundle = new Bundle(); + bundle.putString(KMKeyboardDownloaderActivity.ARG_PKG_ID, pkgID); + bundle.putString(KMKeyboardDownloaderActivity.ARG_KB_ID, kbID); + bundle.putString(KMKeyboardDownloaderActivity.ARG_LANG_ID, langID); + bundle.putString(KMKeyboardDownloaderActivity.ARG_KB_NAME, kbName); + bundle.putString(KMKeyboardDownloaderActivity.ARG_LANG_NAME, langName); + bundle.putBoolean(KMKeyboardDownloaderActivity.ARG_IS_CUSTOM, false); + Intent i = new Intent(getApplicationContext(), KMKeyboardDownloaderActivity.class); + i.putExtras(bundle); + startActivity(i); } } }); diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/FileDownloader.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/FileDownloader.java new file mode 100644 index 0000000000..a2769cbc3f --- /dev/null +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/util/FileDownloader.java @@ -0,0 +1,113 @@ +package com.tavultesoft.kmea.util; + +import android.content.Context; +import android.util.Log; + +import com.tavultesoft.kmea.KMManager; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; + +public final class FileDownloader { + + /** + * Utility to download a file from urlStr and store it at directory/filename. + * If the directory does not exist, it will be created. + * @param context + * @param urlStr Source + * @param directory Destination path. + * @param filename Destination filename. If blank, it will use the filename from the URL + * @return + */ + public static int download(Context context, String urlStr, String directory, String filename) { + final int BUFFER_SIZE = 4096; + int ret = -1; + String fileName = ""; + String tmpFileName = ""; + File tmpFile = null; + File file = null; + + try { + if (directory == null) { + directory = ""; + } + directory = directory.trim(); + + String dirPath; + if (directory.length() != 0) { + directory = directory + "/"; + dirPath = context.getDir("data", Context.MODE_PRIVATE) + "/" + directory; + } else { + dirPath = context.getDir("data", Context.MODE_PRIVATE).toString(); + } + File dir = new File(dirPath); + if (!dir.exists()) { + dir.mkdir(); + } + + if (Connection.initialize(urlStr)) { + InputStream binStream = new BufferedInputStream(Connection.getInputStream(), BUFFER_SIZE); + byte[] buff = new byte[BUFFER_SIZE]; + + filename = filename.trim(); + if (filename == null || filename.isEmpty()) { + fileName = Connection.getFile().substring(Connection.getFile().lastIndexOf('/') + 1); + if (fileName.lastIndexOf(".js") > 0 && !fileName.contains("-")) { + fileName = fileName.substring(0, filename.lastIndexOf(".js")) + "-1.0.js"; + } + } else { + fileName = filename; + } + tmpFileName = String.format("%s.tmp", fileName); + file = new File(dirPath, fileName); + tmpFile = new File(dirPath, tmpFileName); + FileOutputStream fos = new FileOutputStream(tmpFile); + + int len; + while ((len = binStream.read(buff)) != -1) { + fos.write(buff, 0, len); + } + + fos.flush(); + fos.close(); + binStream.close(); + + ret = 1; + } + } catch (Exception e) { + ret = -1; + Log.e("FileDownloader", "Download failed! Error: " + e); + } finally { + if (ret > 0) { + if (tmpFile.exists() && tmpFile.length() > 0) { + if (file.exists()) { + file.delete(); + } + if (!tmpFile.renameTo(file)) { + ret = -1; + } else if (KMManager.isDebugMode()) { + Log.d("FileDownloader", "Download finished for filename " + file.toString()); + } + } else { + ret = -1; + } + } else { + if (file.exists()) { + file.delete(); + } + if (tmpFile.exists()) { + tmpFile.delete(); + } + if (KMManager.isDebugMode()) { + Log.d("FileDownloader", "Could not download filename " + file.toString()); + } + } + + Connection.disconnect(); + } + + return ret; + } +} From ebafec1626327cbdb532049b9a6184ee77ebc71e Mon Sep 17 00:00:00 2001 From: darcywong00 Date: Wed, 13 Dec 2017 05:39:38 +0700 Subject: [PATCH 12/22] Fix url key and isCustom() check --- .../com/tavultesoft/kmapro/MainActivity.java | 9 ++++----- .../kmea/KMKeyboardDownloaderActivity.java | 20 +++++++++---------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index a7c43fe385..9dae4da3de 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -192,7 +192,7 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O isDirect = true; } - String url = data.getQueryParameter(KMKeyboardDownloaderActivity.ARG_URL); + String url = data.getQueryParameter(KMKeyboardDownloaderActivity.KMKey_URL); if (url != null) { boolean isCustom = KMKeyboardDownloaderActivity.isCustom(url); @@ -204,13 +204,12 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O Bundle bundle = new Bundle(); bundle.putString(KMKeyboardDownloaderActivity.ARG_KEYBOARD, - data.getQueryParameter(KMKeyboardDownloaderActivity.ARG_KEYBOARD)); + data.getQueryParameter(KMKeyboardDownloaderActivity.KMKey_Keyboard)); bundle.putString(KMKeyboardDownloaderActivity.ARG_LANGUAGE, - data.getQueryParameter(KMKeyboardDownloaderActivity.ARG_LANGUAGE)); + data.getQueryParameter(KMKeyboardDownloaderActivity.KMKey_Language)); bundle.putBoolean(KMKeyboardDownloaderActivity.ARG_IS_CUSTOM, isCustom); bundle.putBoolean(KMKeyboardDownloaderActivity.ARG_IS_DIRECT, isDirect); - bundle.putString(KMKeyboardDownloaderActivity.ARG_URL, - data.getQueryParameter(KMKeyboardDownloaderActivity.ARG_URL)); + bundle.putString(KMKeyboardDownloaderActivity.ARG_URL, url); bundle.putString(KMKeyboardDownloaderActivity.ARG_JSON_URL, jsonFilename); Intent i = new Intent(getApplicationContext(), KMKeyboardDownloaderActivity.class); i.putExtras(bundle); diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java index e49886ed04..7f7d4b9572 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java @@ -39,8 +39,11 @@ public class KMKeyboardDownloaderActivity extends Activity { public static final String kKeymanApiBaseURL = "https://r.keymanweb.com/api/3.0/"; public static final String kKeymanApiRemoteURL = "https://r.keymanweb.com/api/2.0/remote?url="; public static final String KMKey_KeyboardBaseURI = "keyboardBaseUri"; + public static final String KMKey_Keyboard = "keyboard"; + public static final String KMKey_Language = "language"; public static final String KMKey_FontBaseURI = "fontBaseUri"; public static final String KMKey_Direct = "direct"; + public static final String KMKey_URL = "url"; private static String pkgID; private static String kbID; @@ -76,6 +79,9 @@ public class KMKeyboardDownloaderActivity extends Activity { isDirect = bundle.getBoolean(ARG_IS_DIRECT); url = bundle.getString(ARG_URL); jsonUrl = bundle.getString(ARG_JSON_URL); + if (jsonUrl == null) { + jsonUrl = "unknown"; + } } else { return; } @@ -83,13 +89,7 @@ public class KMKeyboardDownloaderActivity extends Activity { Bundle args = new Bundle(); String title = ""; if (url != null) { - int index = url.lastIndexOf("/") + 1; - String jsonFilename = "unknown"; - if (index >= 0 && index <= url.length()) { - jsonFilename = url.substring(index); - } - - title = "Custom Keyboard: " + jsonFilename; + title = "Custom Keyboard: " + jsonUrl; } else if (customKeyboard != null && customLanguage != null && !customKeyboard.trim().isEmpty() && !customLanguage.trim().isEmpty()) { int kbIndex = KMManager.getKeyboardIndex(getApplicationContext(), customKeyboard, customLanguage); @@ -168,7 +168,6 @@ public class KMKeyboardDownloaderActivity extends Activity { deviceType = "androidphone"; } - // Formerly from KMManager.KMCustomKeyboardDownloader JSONParser jsonParser = new JSONParser(); JSONObject kbData = null; String remoteUrl = ""; @@ -180,11 +179,12 @@ public class KMKeyboardDownloaderActivity extends Activity { remoteUrl = String.format("%s%s&device=%s", kKeymanApiRemoteURL, encodedUrl, deviceType); } } else { + // Keyman cloud remoteUrl = String.format("%slanguages/%s/%s?device=%s", kKeymanApiBaseURL, langID, kbID, deviceType); } kbData = jsonParser.getJSONObjectFromUrl(remoteUrl); - exceptionStr = "Could not reach Keyman server"; + exceptionStr = "Could not reach server"; if (kbData == null) { throw new Exception(exceptionStr); } @@ -319,7 +319,7 @@ public class KMKeyboardDownloaderActivity extends Activity { boolean ret = false; if (u != null && !u.contains(KMKeyboardDownloaderActivity.kKeymanApiBaseURL) && !u.contains(KMKeyboardDownloaderActivity.kKeymanApiRemoteURL)) { - isCustom = true; + ret = true; } return ret; } From 4f5bd09cdb838264c77735b677c80cbbfdd164c7 Mon Sep 17 00:00:00 2001 From: darcywong00 Date: Wed, 13 Dec 2017 12:47:36 +0700 Subject: [PATCH 13/22] Tweak event handlers and fix processing custom JSON For now, disable calls to `KMKeyboardDownloaderActivity.removeKEyboardDownloadEventListener()`. The parent activities will always pause when KMKeyboardDownloaderActivity runs, and we want the event handlers to trigger when the keyboards finish downloading. Also fixed processing custom JSON to handle the JSONArray `languages`. --- .../com/tavultesoft/kmapro/MainActivity.java | 9 +- android/KMEA/app/src/main/AndroidManifest.xml | 1 - .../kmea/KMKeyboardDownloaderActivity.java | 103 +++++++++++++----- .../kmea/KeyboardListActivity.java | 3 +- .../kmea/LanguageListActivity.java | 2 +- 5 files changed, 83 insertions(+), 35 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 9dae4da3de..7354d58953 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -197,9 +197,9 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O boolean isCustom = KMKeyboardDownloaderActivity.isCustom(url); int index = url.lastIndexOf("/") + 1; - String jsonFilename = "unknown:"; + String filename = "unknown:"; if (index >= 0 && index <= url.length()) { - jsonFilename = url.substring(index); + filename = url.substring(index); } Bundle bundle = new Bundle(); @@ -210,7 +210,7 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O bundle.putBoolean(KMKeyboardDownloaderActivity.ARG_IS_CUSTOM, isCustom); bundle.putBoolean(KMKeyboardDownloaderActivity.ARG_IS_DIRECT, isDirect); bundle.putString(KMKeyboardDownloaderActivity.ARG_URL, url); - bundle.putString(KMKeyboardDownloaderActivity.ARG_JSON_URL, jsonFilename); + bundle.putString(KMKeyboardDownloaderActivity.ARG_FILENAME, filename); Intent i = new Intent(getApplicationContext(), KMKeyboardDownloaderActivity.class); i.putExtras(bundle); startActivity(i); @@ -224,7 +224,8 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O super.onPause(); KMManager.onPause(); KMManager.removeKeyboardEventListener(this); - KMKeyboardDownloaderActivity.removeKeyboardDownloadEventListener(this); + // TODO: Investigate if it's proper to not remove this listener + // KMKeyboardDownloaderActivity.removeKeyboardDownloadEventListener(this); SharedPreferences prefs = getSharedPreferences(getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putString(userTextKey, textView.getText().toString()); diff --git a/android/KMEA/app/src/main/AndroidManifest.xml b/android/KMEA/app/src/main/AndroidManifest.xml index 22b2fae11d..b94832507d 100644 --- a/android/KMEA/app/src/main/AndroidManifest.xml +++ b/android/KMEA/app/src/main/AndroidManifest.xml @@ -31,7 +31,6 @@ diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java index 7f7d4b9572..4c8977a746 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java @@ -34,7 +34,7 @@ public class KMKeyboardDownloaderActivity extends Activity { public static final String ARG_LANGUAGE = "KMKeyboardActivity.language"; public static final String ARG_IS_DIRECT = "KMKeyboardActivity.isDirect"; public static final String ARG_URL = "KMKeyboardActivity.url"; - public static final String ARG_JSON_URL = "KMKeyboardActivity.jsonUrl"; + public static final String ARG_FILENAME = "KMKeyboardActivity.filename"; public static final String kKeymanApiBaseURL = "https://r.keymanweb.com/api/3.0/"; public static final String kKeymanApiRemoteURL = "https://r.keymanweb.com/api/2.0/remote?url="; @@ -56,7 +56,7 @@ public class KMKeyboardDownloaderActivity extends Activity { private static String customLanguage; private static Boolean isDirect; private static String url; - private static String jsonUrl; + private static String filename; private static ArrayList kbDownloadEventListeners = null; @@ -67,6 +67,9 @@ public class KMKeyboardDownloaderActivity extends Activity { Bundle bundle = getIntent().getExtras(); if (bundle != null) { pkgID = bundle.getString(ARG_PKG_ID); + if (pkgID == null || pkgID.isEmpty()) { + pkgID = KMManager.KMDefault_LegacyPackageID; + } kbID = bundle.getString(ARG_KB_ID); langID = bundle.getString(ARG_LANG_ID); kbName = bundle.getString(ARG_KB_NAME); @@ -78,9 +81,9 @@ public class KMKeyboardDownloaderActivity extends Activity { customLanguage = bundle.getString(ARG_LANGUAGE); isDirect = bundle.getBoolean(ARG_IS_DIRECT); url = bundle.getString(ARG_URL); - jsonUrl = bundle.getString(ARG_JSON_URL); - if (jsonUrl == null) { - jsonUrl = "unknown"; + filename = bundle.getString(ARG_FILENAME); + if (filename == null || filename.isEmpty()) { + filename = "unknown"; } } else { return; @@ -89,7 +92,7 @@ public class KMKeyboardDownloaderActivity extends Activity { Bundle args = new Bundle(); String title = ""; if (url != null) { - title = "Custom Keyboard: " + jsonUrl; + title = "Custom Keyboard: " + filename; } else if (customKeyboard != null && customLanguage != null && !customKeyboard.trim().isEmpty() && !customLanguage.trim().isEmpty()) { int kbIndex = KMManager.getKeyboardIndex(getApplicationContext(), customKeyboard, customLanguage); @@ -123,8 +126,8 @@ public class KMKeyboardDownloaderActivity extends Activity { new AsyncTask() { private ProgressDialog progressDialog; - private String languageName = ""; - private String keyboardName = ""; + //private String languageName = ""; + //private String keyboardName = ""; private String kbVersion = "1.0"; private String kbIsCustom = isCustom ? "Y" : "N"; private String font = ""; @@ -157,9 +160,9 @@ public class KMKeyboardDownloaderActivity extends Activity { try { String exceptionStr = "Invalid keyboard"; if (pkgID == null || pkgID.trim().isEmpty() || - langID == null || langID.trim().isEmpty() || - kbID == null || kbID.trim().isEmpty()) + (!isCustom && (langID == null || langID.trim().isEmpty() || kbID == null || kbID.trim().isEmpty()))) { throw new Exception(exceptionStr); + } String deviceType = context.getResources().getString(R.string.device_type); if (deviceType.equals("AndroidTablet")) { @@ -173,15 +176,16 @@ public class KMKeyboardDownloaderActivity extends Activity { String remoteUrl = ""; if (isCustom) { if (isDirect) { - remoteUrl = jsonUrl; + remoteUrl = url; } else { - String encodedUrl = URLEncoder.encode(jsonUrl, "utf-8"); + String encodedUrl = URLEncoder.encode(filename, "utf-8"); remoteUrl = String.format("%s%s&device=%s", kKeymanApiRemoteURL, encodedUrl, deviceType); } } else { // Keyman cloud remoteUrl = String.format("%slanguages/%s/%s?device=%s", kKeymanApiBaseURL, langID, kbID, deviceType); } + kbData = jsonParser.getJSONObjectFromUrl(remoteUrl); exceptionStr = "Could not reach server"; @@ -189,32 +193,75 @@ public class KMKeyboardDownloaderActivity extends Activity { throw new Exception(exceptionStr); } - JSONObject options = kbData.optJSONObject(KMManager.KMKey_Options); - JSONObject language = kbData.optJSONObject(KMManager.KMKey_Language); exceptionStr = "The keyboard could not be installed"; - if (options == null || language == null) { + JSONObject options = kbData.optJSONObject(KMManager.KMKey_Options); + if (options == null) { + throw new Exception(exceptionStr); + } + String kbBaseUri = options.optString(KMKey_KeyboardBaseURI, ""); + if (kbBaseUri.isEmpty()) { throw new Exception(exceptionStr); } - JSONArray keyboards = language.getJSONArray(KMManager.KMKey_LanguageKeyboards); - String kbBaseUri = options.optString(KMKey_KeyboardBaseURI, ""); String fontBaseUri = options.optString(KMKey_FontBaseURI, ""); + JSONObject language, keyboard; + if (!isCustom) { + // JSON from Keyman cloud server + language = kbData.optJSONObject(KMManager.KMKey_Language); + if (language == null) { + throw new Exception(exceptionStr); + } - if (keyboards == null || kbBaseUri.isEmpty()) - throw new Exception(exceptionStr); + langName = language.optString(KMManager.KMKey_Name, ""); - JSONObject keyboard = keyboards.getJSONObject(0); - if (keyboard == null) - throw new Exception(exceptionStr); + JSONArray keyboards = language.getJSONArray(KMManager.KMKey_LanguageKeyboards); + if (keyboards == null ) { + throw new Exception(exceptionStr); + } - languageName = language.optString(KMManager.KMKey_Name, ""); - keyboardName = keyboard.optString(KMManager.KMKey_Name, ""); + keyboard = keyboards.getJSONObject(0); + if (keyboard == null) { + throw new Exception(exceptionStr); + } + + + } else { + // JSON from custom URL + keyboard = kbData.optJSONObject(KMManager.KMKey_Keyboard); + if (keyboard == null) { + throw new Exception(exceptionStr); + } + + JSONArray languages = keyboard.optJSONArray(KMManager.KMKey_Languages); + if (languages == null) { + throw new Exception(exceptionStr); + } + + // Concatenate langID and langName + langID = ""; + langName = ""; + int langCount = languages.length(); + for (int i=0; i < langCount; i++) { + langID += languages.getJSONObject(i).getString(KMManager.KMKey_ID); + langName += languages.getJSONObject(i).getString(KMManager.KMKey_Name); + if (i < langCount -1) { + langID += ";"; + langName += ";"; + } + } + pkgID = keyboard.optString(KMManager.KMKey_PackageID, KMManager.KMDefault_LegacyPackageID); + kbID = keyboard.optString(KMManager.KMKey_ID, ""); + + } + + kbName = keyboard.optString(KMManager.KMKey_Name, ""); kbVersion = keyboard.optString(KMManager.KMKey_KeyboardVersion, "1.0"); + // TODO: replace? font = keyboard.optString(KMManager.KMKey_Font, ""); oskFont = keyboard.optString(KMManager.KMKey_OskFont, null); String kbFilename = keyboard.optString(KMManager.KMKey_Filename, ""); - if (keyboardName.isEmpty() || languageName.isEmpty() || kbFilename.isEmpty()) + if (kbName.isEmpty() || langName.isEmpty() || kbFilename.isEmpty()) throw new Exception(exceptionStr); String kbUrl = kbBaseUri + kbFilename; @@ -288,7 +335,7 @@ public class KMKeyboardDownloaderActivity extends Activity { } notifyListeners(KeyboardEventHandler.EventType.KEYBOARD_DOWNLOAD_FINISHED, result); - super.onPostExecute(result); + //super.onPostExecute(result); } /** @@ -302,8 +349,8 @@ public class KMKeyboardDownloaderActivity extends Activity { keyboardInfo.put(KMManager.KMKey_PackageID, pkgID); keyboardInfo.put(KMManager.KMKey_KeyboardID, kbID); keyboardInfo.put(KMManager.KMKey_LanguageID, langID); - keyboardInfo.put(KMManager.KMKey_KeyboardName, keyboardName); - keyboardInfo.put(KMManager.KMKey_LanguageName, languageName); + keyboardInfo.put(KMManager.KMKey_KeyboardName, kbName); + keyboardInfo.put(KMManager.KMKey_LanguageName, langName); keyboardInfo.put(KMManager.KMKey_KeyboardVersion, kbVersion); keyboardInfo.put(KMManager.KMKey_CustomKeyboard, kbIsCustom); keyboardInfo.put(KMManager.KMKey_Font, font); diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java index 59293496e4..4b887ce82d 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java @@ -135,7 +135,8 @@ public final class KeyboardListActivity extends Activity implements OnKeyboardDo @Override protected void onPause() { super.onPause(); - KMKeyboardDownloaderActivity.removeKeyboardDownloadEventListener(this); + // TODO: Investigate if it's proper to not remove this listener + // KMKeyboardDownloaderActivity.removeKeyboardDownloadEventListener(this); } @Override diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java index 08c23a621f..af8a69d6d0 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java @@ -105,7 +105,7 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo @Override protected void onPause() { super.onPause(); - KMKeyboardDownloaderActivity.removeKeyboardDownloadEventListener(this); + //KMKeyboardDownloaderActivity.removeKeyboardDownloadEventListener(this); } @Override From a81387f043f72d3e8c8b63040891843e4963dbc5 Mon Sep 17 00:00:00 2001 From: darcywong00 Date: Thu, 14 Dec 2017 08:19:03 +0700 Subject: [PATCH 14/22] More more keys from KMManager to KMKeyboardDownloaderActivity --- .../kmea/ConfirmDialogFragment.java | 5 +- .../kmea/KMKeyboardDownloaderActivity.java | 47 ++++++++++++------- .../java/com/tavultesoft/kmea/KMManager.java | 6 --- .../kmea/KeyboardListActivity.java | 2 +- .../kmea/KeyboardPickerActivity.java | 4 +- .../kmea/LanguageListActivity.java | 14 +++--- 6 files changed, 41 insertions(+), 37 deletions(-) diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/ConfirmDialogFragment.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/ConfirmDialogFragment.java index a740bccbec..4871bc5aed 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/ConfirmDialogFragment.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/ConfirmDialogFragment.java @@ -13,9 +13,6 @@ import android.widget.Toast; public class ConfirmDialogFragment extends DialogFragment { public static String ARG_TITLE = "ConfirmDialogFragment.title"; - public ConfirmDialogFragment() { - } - @Override public Dialog onCreateDialog(Bundle savedInstanceState) { Bundle args = getArguments(); @@ -37,7 +34,7 @@ public class ConfirmDialogFragment extends DialogFragment { }) .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { - // Cancel + // Cancel } }) .create(); diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java index 4c8977a746..75c392f1be 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java @@ -21,7 +21,8 @@ import com.tavultesoft.kmea.util.FileDownloader; import static com.tavultesoft.kmea.KMManager.KMDefault_AssetPackages; public class KMKeyboardDownloaderActivity extends Activity { - // Keys for cloud keyboard + // Bundle Keys + // Cloud public static final String ARG_PKG_ID = "KMKeyboardActivity.pkgID"; public static final String ARG_KB_ID = "KMKeyboardActivity.kbID"; public static final String ARG_LANG_ID = "KMKeyboardActivity.langID"; @@ -29,7 +30,7 @@ public class KMKeyboardDownloaderActivity extends Activity { public static final String ARG_LANG_NAME = "KMKeyboardActivity.langName"; public static final String ARG_IS_CUSTOM = "KMKeyboardActivity.isCustom"; - // Keys for custom keyboard + // custom keyboard public static final String ARG_KEYBOARD = "KMKeyboardActivity.keyboard"; public static final String ARG_LANGUAGE = "KMKeyboardActivity.language"; public static final String ARG_IS_DIRECT = "KMKeyboardActivity.isDirect"; @@ -38,12 +39,20 @@ public class KMKeyboardDownloaderActivity extends Activity { public static final String kKeymanApiBaseURL = "https://r.keymanweb.com/api/3.0/"; public static final String kKeymanApiRemoteURL = "https://r.keymanweb.com/api/2.0/remote?url="; - public static final String KMKey_KeyboardBaseURI = "keyboardBaseUri"; - public static final String KMKey_Keyboard = "keyboard"; - public static final String KMKey_Language = "language"; - public static final String KMKey_FontBaseURI = "fontBaseUri"; + + // Keyman public keys public static final String KMKey_Direct = "direct"; public static final String KMKey_URL = "url"; + public static final String KMKey_Keyboard = "keyboard"; + public static final String KMKey_LanguageKeyboards = "keyboards"; + public static final String KMKey_Options = "options"; + public static final String KMKey_Language = "language"; + public static final String KMKey_Languages = "languages"; + public static final String KMKey_Filename = "filename"; + + // Keyman internal keys + public static final String KMKey_KeyboardBaseURI = "keyboardBaseUri"; + public static final String KMKey_FontBaseURI = "fontBaseUri"; private static String pkgID; private static String kbID; @@ -112,9 +121,7 @@ public class KMKeyboardDownloaderActivity extends Activity { DialogFragment dialog = new ConfirmDialogFragment(); args.putString(ConfirmDialogFragment.ARG_TITLE, title); dialog.setArguments(args); - dialog.show( - - getFragmentManager(), "dialog"); + dialog.show(getFragmentManager(), "dialog"); } /** @@ -171,6 +178,10 @@ public class KMKeyboardDownloaderActivity extends Activity { deviceType = "androidphone"; } + // Placeholder here to attempt to process .kmp keyboard first: + + //// + JSONParser jsonParser = new JSONParser(); JSONObject kbData = null; String remoteUrl = ""; @@ -194,7 +205,7 @@ public class KMKeyboardDownloaderActivity extends Activity { } exceptionStr = "The keyboard could not be installed"; - JSONObject options = kbData.optJSONObject(KMManager.KMKey_Options); + JSONObject options = kbData.optJSONObject(KMKey_Options); if (options == null) { throw new Exception(exceptionStr); } @@ -207,14 +218,14 @@ public class KMKeyboardDownloaderActivity extends Activity { JSONObject language, keyboard; if (!isCustom) { // JSON from Keyman cloud server - language = kbData.optJSONObject(KMManager.KMKey_Language); + language = kbData.optJSONObject(KMKey_Language); if (language == null) { throw new Exception(exceptionStr); } langName = language.optString(KMManager.KMKey_Name, ""); - JSONArray keyboards = language.getJSONArray(KMManager.KMKey_LanguageKeyboards); + JSONArray keyboards = language.getJSONArray(KMKey_LanguageKeyboards); if (keyboards == null ) { throw new Exception(exceptionStr); } @@ -227,12 +238,12 @@ public class KMKeyboardDownloaderActivity extends Activity { } else { // JSON from custom URL - keyboard = kbData.optJSONObject(KMManager.KMKey_Keyboard); + keyboard = kbData.optJSONObject(KMKey_Keyboard); if (keyboard == null) { throw new Exception(exceptionStr); } - JSONArray languages = keyboard.optJSONArray(KMManager.KMKey_Languages); + JSONArray languages = keyboard.optJSONArray(KMKey_Languages); if (languages == null) { throw new Exception(exceptionStr); } @@ -256,10 +267,12 @@ public class KMKeyboardDownloaderActivity extends Activity { kbName = keyboard.optString(KMManager.KMKey_Name, ""); kbVersion = keyboard.optString(KMManager.KMKey_KeyboardVersion, "1.0"); - // TODO: replace? + // TODO: Investigate why previous KMMManager.KMCustomKeyboardDownloader() had the following + // font = keyboard.optString(KMKey_Font, "").replace("\"" + KMManager.KMKey_Filename + "\"", "\"" + KMManager.KMKey_FontSource + "\""); + // oskFont = keyboard.optString(KMKey_OskFont, "").replace("\"" + KMManager.KMKey_Filename + "\"", "\"" + KMManager.KMKey_FontSource + "\""); font = keyboard.optString(KMManager.KMKey_Font, ""); oskFont = keyboard.optString(KMManager.KMKey_OskFont, null); - String kbFilename = keyboard.optString(KMManager.KMKey_Filename, ""); + String kbFilename = keyboard.optString(KMKey_Filename, ""); if (kbName.isEmpty() || langName.isEmpty() || kbFilename.isEmpty()) throw new Exception(exceptionStr); @@ -335,7 +348,7 @@ public class KMKeyboardDownloaderActivity extends Activity { } notifyListeners(KeyboardEventHandler.EventType.KEYBOARD_DOWNLOAD_FINISHED, result); - //super.onPostExecute(result); + super.onPostExecute(result); } /** diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java index 0c990b2ac4..ff3082d58e 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java @@ -96,18 +96,12 @@ public final class KMManager { public static final String KMKey_KeyboardID = "kbId"; public static final String KMKey_KeyboardName = "kbName"; public static final String KMKey_KeyboardVersion = "version"; - public static final String KMKey_Keyboard = "keyboard"; - public static final String KMKey_LanguageKeyboards = "keyboards"; public static final String KMKey_KeyboardFileSize = "fileSize"; public static final String KMKey_Font = "font"; public static final String KMKey_OskFont = "oskFont"; public static final String KMKey_FontFamily = "family"; public static final String KMKey_FontSource = "source"; public static final String KMKey_FontFiles = "files"; - public static final String KMKey_Options = "options"; - public static final String KMKey_Language = "language"; - public static final String KMKey_Languages = "languages"; - public static final String KMKey_Filename = "filename"; public static final String KMKey_KeyboardModified = "lastModified"; public static final String KMKey_KeyboardRTL = "rtl"; public static final String KMKey_CustomKeyboard = "CustomKeyboard"; diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java index 4b887ce82d..3f5d164e0a 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java @@ -72,7 +72,7 @@ public final class KeyboardListActivity extends Activity implements OnKeyboardDo final String langName = language.getString(KMManager.KMKey_Name); textView.setText(langName); - keyboards = language.getJSONArray(KMManager.KMKey_LanguageKeyboards); + keyboards = language.getJSONArray(KMKeyboardDownloaderActivity.KMKey_LanguageKeyboards); keyboardsArrayList = new ArrayList>(); int length = keyboards.length(); diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java index 2bdf0199c7..3b387ac7fc 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java @@ -565,8 +565,8 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard String kbVersion = keyboardsList.get(i).get(KMManager.KMKey_KeyboardVersion); String url = String.format("%slanguages/%s/%s?device=%s", KMKeyboardDownloaderActivity.kKeymanApiBaseURL, languageID, keyboardID, deviceType); JSONObject kbData = jsonParser.getJSONObjectFromUrl(url); - JSONObject language = kbData.optJSONObject(KMManager.KMKey_Language); - JSONArray keyboards = language.getJSONArray(KMManager.KMKey_LanguageKeyboards); + JSONObject language = kbData.optJSONObject(KMKeyboardDownloaderActivity.KMKey_Language); + JSONArray keyboards = language.getJSONArray(KMKeyboardDownloaderActivity.KMKey_LanguageKeyboards); JSONObject keyboard = keyboards.getJSONObject(0); String newKbVersion = keyboard.optString(KMManager.KMKey_KeyboardVersion, "1.0"); String kbKey = String.format("%s_%s", languageID, keyboardID); diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java index af8a69d6d0..b253bae0c2 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java @@ -146,7 +146,7 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo String langName = language.getString(KMManager.KMKey_Name); String pkgID; - JSONArray keyboards = language.getJSONArray(KMManager.KMKey_LanguageKeyboards); + JSONArray keyboards = language.getJSONArray(KMKeyboardDownloaderActivity.KMKey_LanguageKeyboards); if (keyboards.getJSONObject(keyboardIndex).has(KMManager.KMKey_PackageID)) { pkgID = keyboards.getJSONObject(keyboardIndex).getString(KMManager.KMKey_PackageID); } else { @@ -185,8 +185,8 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo return null; } - languages = jsonObj.getJSONObject(KMManager.KMKey_Languages).getJSONArray(KMManager.KMKey_Languages); - options = jsonObj.getJSONObject(KMManager.KMKey_Options); + languages = jsonObj.getJSONObject(KMKeyboardDownloaderActivity.KMKey_Languages).getJSONArray(KMKeyboardDownloaderActivity.KMKey_Languages); + options = jsonObj.getJSONObject(KMKeyboardDownloaderActivity.KMKey_Options); keyboardsInfo = new HashMap>(); keyboardModifiedDates = new HashMap(); @@ -202,7 +202,7 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo String kbVersion = "1.0"; String isCustom = "N"; String kbFont = ""; - JSONArray langKeyboards = language.getJSONArray(KMManager.KMKey_LanguageKeyboards); + JSONArray langKeyboards = language.getJSONArray(KMKeyboardDownloaderActivity.KMKey_LanguageKeyboards); JSONObject keyboard = null; int kbLength = langKeyboards.length(); @@ -390,8 +390,8 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo } try { - languages = jsonObj.getJSONObject(KMManager.KMKey_Languages).getJSONArray(KMManager.KMKey_Languages); - options = jsonObj.getJSONObject(KMManager.KMKey_Options); + languages = jsonObj.getJSONObject(KMKeyboardDownloaderActivity.KMKey_Languages).getJSONArray(KMKeyboardDownloaderActivity.KMKey_Languages); + options = jsonObj.getJSONObject(KMKeyboardDownloaderActivity.KMKey_Options); keyboardsInfo = new HashMap>(); keyboardModifiedDates = new HashMap(); @@ -409,7 +409,7 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo String kbFont = ""; String icon = "0"; String isEnabled = "true"; - JSONArray langKeyboards = language.getJSONArray(KMManager.KMKey_LanguageKeyboards); + JSONArray langKeyboards = language.getJSONArray(KMKeyboardDownloaderActivity.KMKey_LanguageKeyboards); JSONObject keyboard = null; int kbLength = langKeyboards.length(); From 9a47fb3eac3fbf41ee47aa06e918a822ee96ddb7 Mon Sep 17 00:00:00 2001 From: Gabriel Wong Date: Thu, 14 Dec 2017 10:35:50 +0700 Subject: [PATCH 15/22] Correctly mark custom keyboards --- .../Classes/KeyboardRepository/KeyboardRepository.swift | 2 +- ios/engine/KMEI/KeymanEngine/Classes/Manager.swift | 2 +- .../KMEI/KeymanEngine/Classes/Model/InstallableKeyboard.swift | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/KeyboardRepository/KeyboardRepository.swift b/ios/engine/KMEI/KeymanEngine/Classes/KeyboardRepository/KeyboardRepository.swift index ab65eed202..13e24a2101 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/KeyboardRepository/KeyboardRepository.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/KeyboardRepository/KeyboardRepository.swift @@ -26,7 +26,7 @@ public extension KeyboardRepository { guard let keyboard = language.keyboards?.first(where: { $0.id == keyboardID }) else { return nil } - return InstallableKeyboard(keyboard: keyboard, language: language) + return InstallableKeyboard(keyboard: keyboard, language: language, isCustom: false) } public func fetch(completionHandler: CompletionHandler? = nil) { diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift index 5bd71d477a..8c17cff727 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift @@ -499,7 +499,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat private func downloadKeyboard(_ keyboardAPI: KeyboardAPICall) { let keyboard = keyboardAPI.keyboard let installableKeyboards = keyboard.languages!.map { language in - InstallableKeyboard(keyboard: keyboard, language: language) + InstallableKeyboard(keyboard: keyboard, language: language, isCustom: true) } let filename = keyboard.filename diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Model/InstallableKeyboard.swift b/ios/engine/KMEI/KeymanEngine/Classes/Model/InstallableKeyboard.swift index d98681108e..407c7cd1c2 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Model/InstallableKeyboard.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Model/InstallableKeyboard.swift @@ -40,7 +40,7 @@ public struct InstallableKeyboard: Codable { self.isCustom = isCustom } - public init(keyboard: Keyboard, language: Language) { + public init(keyboard: Keyboard, language: Language, isCustom: Bool) { self.id = keyboard.id self.name = keyboard.name self.languageID = language.id @@ -49,6 +49,6 @@ public struct InstallableKeyboard: Codable { self.isRTL = keyboard.isRTL self.font = keyboard.font self.oskFont = keyboard.oskFont - self.isCustom = false + self.isCustom = isCustom } } From 27629a578bff122a2b49074aee82eac1269db8c3 Mon Sep 17 00:00:00 2001 From: darcywong00 Date: Thu, 14 Dec 2017 13:38:15 +0700 Subject: [PATCH 16/22] Tweaks for keyboard update Include language name and keyboard name when updating keyboard version. Dismiss the updateProgress dialog when keyboard has finished downloading. Also fixed a typo in comment --- .../main/java/com/tavultesoft/kmapro/MainActivity.java | 2 +- .../com/tavultesoft/kmea/KeyboardPickerActivity.java | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 7354d58953..15cc04495d 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -182,7 +182,7 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O KMManager.addKeyboardEventListener(this); KMKeyboardDownloaderActivity.addKeyboardDownloadEventListener(this); - // If URL provided, start KMKEeyboardDownloaderActivity + // If URL provided, start KMKeyboardDownloaderActivity Intent intent = getIntent(); Uri data = intent.getData(); if (data != null) { diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java index 3b387ac7fc..117716a430 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java @@ -234,7 +234,7 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard @Override protected void onPause() { super.onPause(); - KMKeyboardDownloaderActivity.removeKeyboardDownloadEventListener(this); + //KMKeyboardDownloaderActivity.removeKeyboardDownloadEventListener(this); if (didUpdate) { if (KMManager.InAppKeyboard != null) { KMManager.InAppKeyboard.loadKeyboard(); @@ -619,6 +619,8 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard String kbID = keyboardsList.get(i).get(KMManager.KMKey_KeyboardID); String langID = keyboardsList.get(i).get(KMManager.KMKey_LanguageID); String kbKey = String.format("%s_%s", langID, kbID); + String langName = keyboardsList.get(i).get(KMManager.KMKey_LanguageName); + String kbName = keyboardsList.get(i).get(KMManager.KMKey_KeyboardName); String kbVersion = keyboardsList.get(i).get(KMManager.KMKey_KeyboardVersion); String newKbVersion = keyboardVersions.get(kbKey); if (newKbVersion != null) { @@ -635,6 +637,8 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard bundle.putString(KMKeyboardDownloaderActivity.ARG_PKG_ID, pkgID); bundle.putString(KMKeyboardDownloaderActivity.ARG_KB_ID, kbID); bundle.putString(KMKeyboardDownloaderActivity.ARG_LANG_ID, langID); + bundle.putString(KMKeyboardDownloaderActivity.ARG_KB_NAME, kbName); + bundle.putString(KMKeyboardDownloaderActivity.ARG_LANG_NAME, langName); bundle.putBoolean(KMKeyboardDownloaderActivity.ARG_IS_CUSTOM, false); Intent intent = new Intent(context, KMKeyboardDownloaderActivity.class); intent.putExtras(bundle); @@ -745,5 +749,8 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard checkingUpdates = false; } } + if (updateProgress != null && updateProgress.isShowing()) { + updateProgress.dismiss(); + } } } \ No newline at end of file From 9cf5e5114c497a709fb22afde9075e471812998d Mon Sep 17 00:00:00 2001 From: darcywong00 Date: Thu, 14 Dec 2017 15:26:47 +0700 Subject: [PATCH 17/22] Enable regex for font and oskFont filepaths. --- .../kmea/KMKeyboardDownloaderActivity.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java index 75c392f1be..cc110691ed 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMKeyboardDownloaderActivity.java @@ -267,11 +267,12 @@ public class KMKeyboardDownloaderActivity extends Activity { kbName = keyboard.optString(KMManager.KMKey_Name, ""); kbVersion = keyboard.optString(KMManager.KMKey_KeyboardVersion, "1.0"); - // TODO: Investigate why previous KMMManager.KMCustomKeyboardDownloader() had the following - // font = keyboard.optString(KMKey_Font, "").replace("\"" + KMManager.KMKey_Filename + "\"", "\"" + KMManager.KMKey_FontSource + "\""); - // oskFont = keyboard.optString(KMKey_OskFont, "").replace("\"" + KMManager.KMKey_Filename + "\"", "\"" + KMManager.KMKey_FontSource + "\""); - font = keyboard.optString(KMManager.KMKey_Font, ""); - oskFont = keyboard.optString(KMManager.KMKey_OskFont, null); + font = keyboard.optString(KMManager.KMKey_Font, "").replace( + "\"" + KMKeyboardDownloaderActivity.KMKey_Filename + "\"", + "\"" + KMManager.KMKey_FontSource + "\""); + oskFont = keyboard.optString(KMManager.KMKey_OskFont, "").replace( + "\"" + KMKeyboardDownloaderActivity.KMKey_Filename + "\"", + "\"" + KMManager.KMKey_FontSource + "\""); String kbFilename = keyboard.optString(KMKey_Filename, ""); if (kbName.isEmpty() || langName.isEmpty() || kbFilename.isEmpty()) From 82f78f005cafc5fcf65e44279d866609ddb2a70d Mon Sep 17 00:00:00 2001 From: darcywong00 Date: Fri, 15 Dec 2017 09:43:51 +0700 Subject: [PATCH 18/22] Document why `removeKeyboardDownloadEventListeners` are removed Calls to `removeKEyboardDownloadEventListener()` are removed to ensure `onKeyboardDownloadFinished()` gets called --- .../src/main/java/com/tavultesoft/kmapro/MainActivity.java | 6 ++++-- .../java/com/tavultesoft/kmea/KeyboardListActivity.java | 5 +++-- .../java/com/tavultesoft/kmea/KeyboardPickerActivity.java | 5 ++++- .../java/com/tavultesoft/kmea/LanguageListActivity.java | 4 +++- .../src/main/java/com/keyman/kmsample1/MainActivity.java | 7 +++++-- .../keyman/android/tests/keyboardHarness/MainActivity.java | 7 +++++-- 6 files changed, 24 insertions(+), 10 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java index 15cc04495d..5fb78313ed 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java +++ b/android/KMAPro/kMAPro/src/main/java/com/tavultesoft/kmapro/MainActivity.java @@ -224,8 +224,10 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O super.onPause(); KMManager.onPause(); KMManager.removeKeyboardEventListener(this); - // TODO: Investigate if it's proper to not remove this listener - // KMKeyboardDownloaderActivity.removeKeyboardDownloadEventListener(this); + + // Intentionally not removing KeyboardDownloadEventListener to + // ensure onKeyboardDownloadFinished() gets called + SharedPreferences prefs = getSharedPreferences(getString(R.string.kma_prefs_name), Context.MODE_PRIVATE); SharedPreferences.Editor editor = prefs.edit(); editor.putString(userTextKey, textView.getText().toString()); diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java index 3f5d164e0a..1540f7adaa 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardListActivity.java @@ -135,8 +135,9 @@ public final class KeyboardListActivity extends Activity implements OnKeyboardDo @Override protected void onPause() { super.onPause(); - // TODO: Investigate if it's proper to not remove this listener - // KMKeyboardDownloaderActivity.removeKeyboardDownloadEventListener(this); + + // Intentionally not removing KeyboardDownloadEventListener to + // ensure onKeyboardDownloadFinished() gets called } @Override diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java index 117716a430..cc46102fca 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java @@ -234,7 +234,10 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard @Override protected void onPause() { super.onPause(); - //KMKeyboardDownloaderActivity.removeKeyboardDownloadEventListener(this); + + // Intentionally not removing KeyboardDownloadEventListener to + // ensure onKeyboardDownloadFinished() gets called + if (didUpdate) { if (KMManager.InAppKeyboard != null) { KMManager.InAppKeyboard.loadKeyboard(); diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java index b253bae0c2..1d7c15c588 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/LanguageListActivity.java @@ -105,7 +105,9 @@ public final class LanguageListActivity extends Activity implements OnKeyboardDo @Override protected void onPause() { super.onPause(); - //KMKeyboardDownloaderActivity.removeKeyboardDownloadEventListener(this); + + // Intentionally not removing KeyboardDownloadEventListener to + // ensure onKeyboardDownloadFinished() gets called } @Override diff --git a/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java b/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java index e7d89850f5..21add09757 100644 --- a/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java +++ b/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java @@ -7,6 +7,7 @@ import android.util.Log; import android.view.Menu; import android.view.MenuItem; +import com.tavultesoft.kmea.KMKeyboardDownloaderActivity; import com.tavultesoft.kmea.KMManager; import com.tavultesoft.kmea.KMTextView; import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardEventListener; @@ -68,7 +69,7 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O super.onResume(); KMManager.onResume(); KMManager.addKeyboardEventListener(this); - KMManager.addKeyboardDownloadEventListener(this); + KMKeyboardDownloaderActivity.addKeyboardDownloadEventListener(this); } @Override @@ -76,7 +77,9 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O super.onPause(); KMManager.onPause(); KMManager.removeKeyboardEventListener(this); - KMManager.removeKeyboardDownloadEventListener(this); + + // Intentionally not removing KeyboardDownloadEventListener to + // ensure onKeyboardDownloadFinished() gets called } @Override diff --git a/android/Tests/KeyboardHarness/app/src/main/java/com/keyman/android/tests/keyboardHarness/MainActivity.java b/android/Tests/KeyboardHarness/app/src/main/java/com/keyman/android/tests/keyboardHarness/MainActivity.java index f79e7835b4..bb0da07e7b 100644 --- a/android/Tests/KeyboardHarness/app/src/main/java/com/keyman/android/tests/keyboardHarness/MainActivity.java +++ b/android/Tests/KeyboardHarness/app/src/main/java/com/keyman/android/tests/keyboardHarness/MainActivity.java @@ -7,6 +7,7 @@ import android.util.Log; import android.view.Menu; import android.view.MenuItem; +import com.tavultesoft.kmea.KMKeyboardDownloaderActivity; import com.tavultesoft.kmea.KMManager; import com.tavultesoft.kmea.KMTextView; import com.tavultesoft.kmea.KeyboardEventHandler.OnKeyboardEventListener; @@ -82,7 +83,7 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O super.onResume(); KMManager.onResume(); KMManager.addKeyboardEventListener(this); - KMManager.addKeyboardDownloadEventListener(this); + KMKeyboardDownloaderActivity.addKeyboardDownloadEventListener(this); } @Override @@ -90,7 +91,9 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O super.onPause(); KMManager.onPause(); KMManager.removeKeyboardEventListener(this); - KMManager.removeKeyboardDownloadEventListener(this); + + // Intentionally not removing KeyboardDownloadEventListener to + // ensure onKeyboardDownloadFinished() gets called } @Override From 867c8cc3ab6b18663f8e3dc11f3a0b3492bebe3a Mon Sep 17 00:00:00 2001 From: darcywong00 Date: Fri, 15 Dec 2017 11:39:26 +0700 Subject: [PATCH 19/22] Ensure downloaded keyboard is added before attempting to set it --- .../java/com/tavultesoft/kmea/KeyboardPickerActivity.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java index cc46102fca..28eb27ad09 100644 --- a/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java +++ b/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KeyboardPickerActivity.java @@ -716,6 +716,11 @@ public final class KeyboardPickerActivity extends Activity implements OnKeyboard String languageID = keyboardInfo.get(KMManager.KMKey_LanguageID); String kbKey = String.format("%s_%s", languageID, keyboardID); int index = getKeyboardIndex(this, kbKey); + if (index == -1) { + // Add the downloaded keyboard if not found + addKeyboard(this, keyboardInfo); + index = getKeyboardIndex(this, kbKey); + } keyboardsList.set(index, keyboardInfo); saveKeyboardsList(this); } else if (result < 0) { From fa772a057de6c3cff0edda156ea2e3d4efdb7698 Mon Sep 17 00:00:00 2001 From: darcywong00 Date: Fri, 15 Dec 2017 13:42:06 +0700 Subject: [PATCH 20/22] Add commented code for default font help.keyman.com [in-app guide](http://help.keyman.com.local/developer/engine/android/guides/in-app/) refers to setting default keyboard font --- .../app/src/main/java/com/keyman/kmsample1/MainActivity.java | 1 + .../app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java | 1 + 2 files changed, 2 insertions(+) diff --git a/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java b/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java index 21add09757..ed77cc9ac2 100644 --- a/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java +++ b/android/Samples/KMSample1/app/src/main/java/com/keyman/kmsample1/MainActivity.java @@ -39,6 +39,7 @@ public class MainActivity extends Activity implements OnKeyboardEventListener, O kbInfo.put(KMManager.KMKey_LanguageName, "Tamil"); kbInfo.put(KMManager.KMKey_KeyboardVersion, "1.1"); kbInfo.put(KMManager.KMKey_Font, "aava1.ttf"); + //kbInfo.put(KMManager.KMKey_Font, KMManager.KMDefault_KeyboardFont); // Use the default font KMManager.addKeyboard(this, kbInfo); } diff --git a/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java b/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java index 9e38d1f01b..2f245c6750 100644 --- a/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java +++ b/android/Samples/KMSample2/app/src/main/java/com/keyman/kmsample2/SystemKeyboard.java @@ -45,6 +45,7 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven kbInfo.put(KMManager.KMKey_LanguageName, "Tamil"); kbInfo.put(KMManager.KMKey_KeyboardVersion, "1.1"); kbInfo.put(KMManager.KMKey_Font, "aava1.ttf"); + //kbInfo.put(KMManager.KMKey_Font, KMManager.KMDefault_KeyboardFont); // Use the default font KMManager.addKeyboard(this, kbInfo); } From 047716f0fb6ce384f5a0e9f1233c143474357fd8 Mon Sep 17 00:00:00 2001 From: Gabriel Wong Date: Mon, 11 Dec 2017 14:58:20 +0700 Subject: [PATCH 21/22] Migrate v2 keyboard dictionaries to structs --- .../KeymanEngine/Classes/Migrations.swift | 107 +++++++++++++++++- 1 file changed, 104 insertions(+), 3 deletions(-) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift b/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift index e35e8177f0..39de5c6123 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Migrations.swift @@ -10,18 +10,119 @@ import Foundation private enum MigrationLevel { static let initial = 0 - static let migratedForKMP = 10 + static let migratedUserDefaultsToStructs = 10 + static let migratedForKMP = 20 } enum Migrations { static func migrate(storage: Storage) { + if storage.userDefaults.migrationLevel < MigrationLevel.migratedUserDefaultsToStructs { + migrateUserDefaultsToStructs(storage: storage) + storage.userDefaults.migrationLevel = MigrationLevel.migratedUserDefaultsToStructs + } else { + log.info("UserDefaults migration to structs already performed. Skipping.") + } if storage.userDefaults.migrationLevel < MigrationLevel.migratedForKMP { migrateForKMP(storage: storage) storage.userDefaults.migrationLevel = MigrationLevel.migratedForKMP - storage.userDefaults.synchronize() } else { - log.debug("KMP directory migration already performed. Skipping.") + log.info("KMP directory migration already performed. Skipping.") } + storage.userDefaults.synchronize() + } + + static func migrateUserDefaultsToStructs(storage: Storage) { + guard let userKeyboardObject = storage.userDefaults.object(forKey: Key.userKeyboardsList), + let currentKeyboardObject = storage.userDefaults.object(forKey: Key.userCurrentKeyboard) + else { + log.info("User keyboard list or current keyboard missing. Skipping migration.") + return + } + guard let oldUserKeyboards = userKeyboardObject as? [[String: String]], + let oldCurrentKeyboard = currentKeyboardObject as? [String: String] + else { + log.error("User keyboard list or current keyboard has an unexpected type") + return + } + + let userKeyboards = oldUserKeyboards.flatMap { installableKeyboard(from: $0) } + let currentKeyboardID = fullKeyboardID(from: oldCurrentKeyboard) + + storage.userDefaults.userKeyboards = userKeyboards + if userKeyboards.contains(where: { $0.fullID == currentKeyboardID }) { + storage.userDefaults.currentKeyboardID = currentKeyboardID + } else { + storage.userDefaults.currentKeyboardID = nil + } + } + + private static func installableKeyboard(from kbDict: [String: String]) -> InstallableKeyboard? { + log.debug("Migrating keyboard dictionary: \(kbDict)") + guard let id = kbDict["kbId"], + let name = kbDict["kbName"], + let languageID = kbDict["langId"], + let languageName = kbDict["langName"], + let version = kbDict["version"] + else { + log.error("Missing required fields in keyboard dictionary: \(kbDict)") + return nil + } + let rtl = kbDict["rtl"] == "Y" + let isCustom = kbDict["CustomKeyboard"] == "Y" + let displayFont = font(from: kbDict["font"]) + let oskFont = font(from: kbDict["oskFont"]) + let kb = InstallableKeyboard(id: id, + name: name, + languageID: languageID, + languageName: languageName, + version: version, + isRTL: rtl, + font: displayFont, + oskFont: oskFont, + isCustom: isCustom) + log.debug("Migrated keyboard dictionary to keyboard \(kb)") + return kb + } + + private static func fullKeyboardID(from kbDict: [String: String]) -> FullKeyboardID? { + log.debug("Migrating keyboard dictionary to FullKeyboardID: \(kbDict)") + guard let keyboardID = kbDict["kbId"], + let languageID = kbDict["langId"] + else { + log.error("Missing required fields in keyboard dictionary for FullKeyboardID: \(kbDict)") + return nil + } + let id = FullKeyboardID(keyboardID: keyboardID, languageID: languageID) + log.debug("Migrated keyboard dictionary to \(id)") + return id + } + + private static func font(from jsonString: String?) -> Font? { + guard let jsonString = jsonString else { + return nil + } + guard let data = jsonString.data(using: .utf8) else { + log.error("Failed to encode string: \(jsonString)") + return nil + } + guard let fontDict = (try? JSONSerialization.jsonObject(with: data, options: [])) as? [String: Any] else { + log.error("Error parsing String as JSON: \(jsonString)") + return nil + } + guard let family = fontDict["family"] as? String else { + log.error("Missing 'family' String: \(fontDict)") + return nil + } + let files: [String] + if let filesString = fontDict["files"] as? String { + files = [filesString] + } else if let filesArray = fontDict["files"] as? [String] { + files = filesArray + } else { + log.error("Missing 'files': \(fontDict)") + return nil + } + return Font(family: family, source: files) } static func migrateForKMP(storage: Storage) { From c95eac1dc72ed85f51733e25082d117640a16c87 Mon Sep 17 00:00:00 2001 From: Gabriel Wong Date: Tue, 12 Dec 2017 15:28:12 +0700 Subject: [PATCH 22/22] Move adding of default keyboard to Manager init --- .../LanguagePicker/KeyboardPickerViewController.swift | 11 +---------- ios/engine/KMEI/KeymanEngine/Classes/Manager.swift | 3 +++ 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/LanguagePicker/KeyboardPickerViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/LanguagePicker/KeyboardPickerViewController.swift index 703b6ed7ac..8e0967c5d3 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/LanguagePicker/KeyboardPickerViewController.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/LanguagePicker/KeyboardPickerViewController.swift @@ -273,16 +273,7 @@ class KeyboardPickerViewController: UITableViewController, UIAlertViewDelegate { } private func loadUserKeyboards() { - let userData = Storage.active.userDefaults - - if let userKeyboards = userData.userKeyboards { - self.userKeyboards = userKeyboards - } else { - userKeyboards = [Defaults.keyboard] - userData.userKeyboards = userKeyboards - userData.synchronize() - } - + userKeyboards = Storage.active.userDefaults.userKeyboards ?? [] tableView.reloadData() } diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift index d9b32495f5..105b59e1ea 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Manager.swift @@ -139,6 +139,9 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat URLProtocol.registerClass(KeymanURLProtocol.self) Migrations.migrate(storage: Storage.active) + if Storage.active.userDefaults.userKeyboards?.isEmpty ?? true { + Storage.active.userDefaults.userKeyboards = [Defaults.keyboard] + } if Util.isSystemKeyboard || Storage.active.userDefaults.bool(forKey: Key.keyboardPickerDisplayed) { isKeymanHelpOn = false