fix rotation issue

pull/2/head
Michael Kirk 5 years ago
parent 4c64aade24
commit 78ce3583ed

@ -1,5 +1,5 @@
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
import XCTest
@ -16,7 +16,7 @@ class StringAdditionsTest: SignalBaseTest {
super.tearDown()
}
func testASCII() {
func test_truncated_ASCII() {
let originalString = "Hello World"
var truncatedString = originalString.truncated(toByteCount: 8)
@ -35,7 +35,7 @@ class StringAdditionsTest: SignalBaseTest {
XCTAssertEqual("Hello World", truncatedString)
}
func testMultiByte() {
func test_truncated_MultiByte() {
let originalString = "🇨🇦🇨🇦🇨🇦🇨🇦"
var truncatedString = originalString.truncated(toByteCount: 0)
@ -63,7 +63,7 @@ class StringAdditionsTest: SignalBaseTest {
XCTAssertEqual("🇨🇦🇨🇦", truncatedString)
}
func testMixed() {
func test_truncated_Mixed() {
let originalString = "Oh🇨🇦Canada🇨🇦"
var truncatedString = originalString.truncated(toByteCount: 0)
@ -97,4 +97,30 @@ class StringAdditionsTest: SignalBaseTest {
XCTAssertEqual("Oh🇨🇦Canada🇨🇦", truncatedString)
}
func test_caesar() {
XCTAssertEqual("abc", try! "abc".caesar(shift: 0))
XCTAssertEqual("abc", try! "abc".caesar(shift: 127))
XCTAssertEqual("bcd", try! "abc".caesar(shift: 1))
XCTAssertEqual("bcd", try! "abc".caesar(shift: 128))
XCTAssertEqual("z{b", try! "yza".caesar(shift: 1))
XCTAssertEqual("|}d", try! "yza".caesar(shift: 3))
XCTAssertEqual("ef=g", try! "bc:d".caesar(shift: 3))
let shifted = try! "abc".caesar(shift: 32)
let roundTrip = try! shifted.caesar(shift: 127 - 32)
XCTAssertEqual("abc", roundTrip)
}
func test_encodedForSelector() {
XCTAssertEqual("cnN0", "abc".encodedForSelector)
XCTAssertEqual("abc", "abc".encodedForSelector!.decodedForSelector)
XCTAssertNotEqual("abcWithFoo:bar:", "abcWithFoo:bar:".encodedForSelector)
XCTAssertEqual("abcWithFoo:bar:", "abcWithFoo:bar:".encodedForSelector!.decodedForSelector)
XCTAssertNotEqual("abcWithFoo:bar:zaz1:", "abcWithFoo:bar:zaz1:".encodedForSelector)
XCTAssertEqual("abcWithFoo:bar:zaz1:", "abcWithFoo:bar:zaz1:".encodedForSelector!.decodedForSelector)
}
}

@ -500,6 +500,8 @@ const UIWindowLevel UIWindowLevel_MessageActions(void)
// In the normal case, that means the SignalViewController will call `becomeFirstResponder`
// on the vc on top of its navigation stack.
[self.rootWindow makeKeyAndVisible];
[self fixit_workAroundRotationIssue];
}
- (void)ensureRootWindowHidden
@ -618,6 +620,91 @@ const UIWindowLevel UIWindowLevel_MessageActions(void)
[self showCallView];
}
#pragma mark - Fixit
- (void)fixit_workAroundRotationIssue
{
// ### Symptom
//
// The app can get into a degraded state where the main window will incorrectly remain locked in
// portrait mode. Worse yet, the status bar and input window will continue to rotate with respect
// to the device orientation. So once you're in this degraded state, the status bar and input
// window can be in landscape while simultaneoulsy the view controller behind them is in portrait.
//
// ### To Reproduce
//
// On an iPhone6 (not reproducible on an iPhoneX)
//
// 0. Ensure "screen protection" is enabled (not necessarily screen lock)
// 1. Enter Conversation View Controller
// 2. Pop Keyboard
// 3. Begin dismissing keyboard with one finger, but stopping when it's about 50% dismissed,
// keep your finger there with the keyboard partially dismissed.
// 4. With your other hand, hit the home button to leave Signal.
// 5. Re-enter Signal
// 6. Rotate to landscape
//
// Expected: Conversation View, Input Toolbar window, and Settings Bar should all rotate to landscape.
// Actual: The input toolbar and the settings toolbar rotate to landscape, but the Conversation
// View remains in portrait, this looks super broken.
//
// ### Background
//
// Some debugging shows that the `ConversationViewController.view.window.isInterfaceAutorotationDisabled`
// is true. This is a private property, whose function we don't exactly know, but it seems like
// `interfaceAutorotation` is disabled when certain transition animations begin, and then
// re-enabled once the animation completes.
//
// My best guess is that autorotation is intended to be disabled for the duration of the
// interactive-keyboard-dismiss-transition, so when we start the interactive dismiss, autorotation
// has been disabled, but because we hide the main app window in the middle of the transition,
// autorotation doesn't have a chance to be re-enabled.
//
// ## So, The Fix
//
// If we find ourself in a situation where autorotation is disabled while showing the rootWindow,
// we re-enable autorotation.
// NSString *encodedSelectorString1 = @"isInterfaceAutorotationDisabled".encodedForSelector;
NSString *encodedSelectorString1 = @"egVaAAZ2BHdydHZSBwYBBAEGcgZ6AQBVegVyc312dQ==";
NSString *_Nullable selectorString1 = encodedSelectorString1.decodedForSelector;
if (selectorString1 == nil) {
OWSFailDebug(@"selectorString1 was unexpectedly nil");
return;
}
SEL selector1 = NSSelectorFromString(selectorString1);
if (![self.rootWindow respondsToSelector:selector1]) {
OWSFailDebug(@"failure: doesn't respond to selector1");
return;
}
IMP imp1 = [self.rootWindow methodForSelector:selector1];
BOOL (*func1)(id, SEL) = (void *)imp1;
BOOL isDisabled = func1(self.rootWindow, selector1);
OWSLogInfo(@"autorotation is disabled: %d", isDisabled);
if (isDisabled) {
// NSString *encodedSelectorString2 = @"endDisablingInterfaceAutorotation".encodedForSelector;
NSString *encodedSelectorString2 = @"dgB1VXoFcnN9egB4WgAGdgR3cnR2UgcGAQQBBnIGegEA";
NSString *selectorString2 = encodedSelectorString2.decodedForSelector;
if (selectorString2 == nil) {
OWSFailDebug(@"selectorString2 was unexpectedly nil");
return;
}
SEL selector2 = NSSelectorFromString(selectorString2);
if (![self.rootWindow respondsToSelector:selector2]) {
OWSFailDebug(@"failure: doesn't respond to selector2");
return;
}
IMP imp2 = [self.rootWindow methodForSelector:selector2];
void (*func2)(id, SEL) = (void *)imp2;
func2(self.rootWindow, selector2);
OWSLogInfo(@"re-enabling autorotation");
}
}
@end
NS_ASSUME_NONNULL_END

@ -20,4 +20,67 @@ public extension String {
public func substring(to index: Int) -> String {
return String(prefix(index))
}
enum StringError: Error {
case invalidCharacterShift
}
}
// MARK: - Selector Encoding
private let selectorOffset: UInt32 = 17
public extension String {
public func caesar(shift: UInt32) throws -> String {
let shiftedScalars: [UnicodeScalar] = try unicodeScalars.map { c in
guard let shiftedScalar = UnicodeScalar((c.value + shift) % 127) else {
owsFailDebug("invalidCharacterShift")
throw StringError.invalidCharacterShift
}
return shiftedScalar
}
return String(String.UnicodeScalarView(shiftedScalars))
}
public var encodedForSelector: String? {
guard let shifted = try? self.caesar(shift: selectorOffset) else {
owsFailDebug("shifted was unexpectedly nil")
return nil
}
guard let data = shifted.data(using: .utf8) else {
owsFailDebug("data was unexpectedly nil")
return nil
}
return data.base64EncodedString()
}
public var decodedForSelector: String? {
guard let data = Data(base64Encoded: self) else {
owsFailDebug("data was unexpectedly nil")
return nil
}
guard let shifted = String(data: data, encoding: .utf8) else {
owsFailDebug("shifted was unexpectedly nil")
return nil
}
return try? shifted.caesar(shift: 127 - selectorOffset)
}
}
public extension NSString {
@objc
public var encodedForSelector: String? {
return (self as String).encodedForSelector
}
@objc
public var decodedForSelector: String? {
return (self as String).decodedForSelector
}
}

Loading…
Cancel
Save