Index: ios/chrome/today_extension/lock_screen_state.mm |
diff --git a/ios/chrome/today_extension/lock_screen_state.mm b/ios/chrome/today_extension/lock_screen_state.mm |
new file mode 100644 |
index 0000000000000000000000000000000000000000..f7da4c64ea2126f9bc1498150e05d5380f1aa9c9 |
--- /dev/null |
+++ b/ios/chrome/today_extension/lock_screen_state.mm |
@@ -0,0 +1,197 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#import "ios/chrome/today_extension/lock_screen_state.h" |
+ |
+#import <UIKit/UIKit.h> |
+ |
+#import "base/ios/weak_nsobject.h" |
+ |
+namespace { |
+ |
+// Name of the protected file used to detect if extension is triggered on lock |
+// screen. |
+NSString* const kLockScreenFileName = @"LockScreenDetector"; |
+ |
+// Return the path of a file that will always have NSFileProtectionComplete |
+// protection option. This file cannot be accessed from the lock screen and can |
+// be used to detect if the device is locked. |
+NSString* LockedDeviceFilename() { |
+ NSString* path = [NSSearchPathForDirectoriesInDomains( |
+ NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; |
+ return [path stringByAppendingPathComponent:kLockScreenFileName]; |
+} |
+ |
+} // namespace |
+ |
+@interface LockScreenState () |
+ |
+- (instancetype)init; |
+ |
+// Returns if the protected file is accessible. |
+// Note: the function tests the access to protected file. Access is revoked 10 |
+// seconds after screen is locked. So this method returns YES if screen has been |
+// locked for more than 10 seconds. |
+// Returns NO if CreateLockScreenDetector has never been called. |
+- (BOOL)isProtectedFileAccessible; |
+ |
+// Periodically checks (every second) if the protected file is still accessible. |
+// Checks |remainingTests| times. |
+- (void)recheckProtectedFile:(int)remainingTests; |
+ |
+// Creates a protected file that will be used to detect if screen is locked. |
+- (void)createLockScreenDetector; |
+ |
+@end |
+ |
+@implementation LockScreenState { |
+ id<LockScreenStateDelegate> _delegate; |
+ BOOL _screenLocked; |
+ BOOL _lockScreenStateKnown; |
+ BOOL _protectedFileCreated; |
+} |
+ |
+- (instancetype)init { |
+ self = [super init]; |
+ if (self) { |
+ [[NSNotificationCenter defaultCenter] |
+ addObserver:self |
+ selector:@selector(screenLocked:) |
+ name:UIApplicationProtectedDataWillBecomeUnavailable |
+ object:nil]; |
+ [[NSNotificationCenter defaultCenter] |
+ addObserver:self |
+ selector:@selector(screenUnlocked:) |
+ name:UIApplicationProtectedDataDidBecomeAvailable |
+ object:nil]; |
+ _delegate = nil; |
+ } |
+ return self; |
+} |
+ |
++ (instancetype)sharedInstance { |
+ static LockScreenState* instance = [[LockScreenState alloc] init]; |
+ return instance; |
+} |
+ |
+- (void)screenLocked:(id)arguments { |
+ _screenLocked = YES; |
+ _lockScreenStateKnown = YES; |
+ [_delegate lockScreenStateDidChange:self]; |
+} |
+ |
+- (void)screenUnlocked:(id)arguments { |
+ _screenLocked = NO; |
+ _lockScreenStateKnown = YES; |
+ [_delegate lockScreenStateDidChange:self]; |
+} |
+ |
+- (void)setDelegate:(id)delegate { |
+ _delegate = delegate; |
+} |
+ |
+- (BOOL)isScreenLocked { |
+ if (_lockScreenStateKnown) { |
+ return _screenLocked; |
+ } |
+ [self createLockScreenDetector]; |
+ if (!_protectedFileCreated) { |
+ // File could not be created. Screen is locked. |
+ _screenLocked = YES; |
+ _lockScreenStateKnown = YES; |
+ return YES; |
+ } |
+ _screenLocked = ![self isProtectedFileAccessible]; |
+ _lockScreenStateKnown = YES; |
+ |
+ if (!_screenLocked) { |
+ // This test is only valid if screen has been locked for more than 10 |
+ // seconds (https://www.apple.com/business/docs/iOS_Security_Guide.pdf). |
+ // In that case, no notification is received after the delay. |
+ // Reschedule tests every second for 15 seconds. |
+ base::WeakNSObject<LockScreenState> weakSelf(self); |
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, |
+ static_cast<int64_t>(1 * NSEC_PER_SEC)), |
+ dispatch_get_main_queue(), ^{ |
+ [weakSelf recheckProtectedFile:14]; |
+ }); |
+ } |
+ return _screenLocked; |
+} |
+ |
+- (void)recheckProtectedFile:(int)remainingTests { |
+ BOOL screenLocked = ![self isProtectedFileAccessible]; |
+ if (screenLocked) { |
+ // |_screenLocked| may already be set to YES if we received a notification. |
+ if (!_screenLocked) { |
+ _screenLocked = YES; |
+ [_delegate lockScreenStateDidChange:self]; |
+ } |
+ return; |
+ } |
+ if (remainingTests) { |
+ base::WeakNSObject<LockScreenState> weakSelf(self); |
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, |
+ static_cast<int64_t>(1 * NSEC_PER_SEC)), |
+ dispatch_get_main_queue(), ^{ |
+ [weakSelf recheckProtectedFile:remainingTests - 1]; |
+ }); |
+ } |
+} |
+ |
+- (BOOL)isProtectedFileAccessible { |
+ NSError* error = nil; |
+ if (![[NSFileManager defaultManager] |
+ fileExistsAtPath:LockedDeviceFilename()]) { |
+ return NO; |
+ } |
+ [NSData dataWithContentsOfFile:LockedDeviceFilename() |
+ options:NSDataReadingMappedIfSafe |
+ error:&error]; |
+ return !error; |
+} |
+ |
+- (void)createLockScreenDetector { |
+ if (_protectedFileCreated) |
+ return; |
+ NSError* error = nil; |
+ if ([[NSFileManager defaultManager] |
+ fileExistsAtPath:LockedDeviceFilename()]) { |
+ NSDictionary* attributes = [[NSFileManager defaultManager] |
+ attributesOfItemAtPath:LockedDeviceFilename() |
+ error:&error]; |
+ if ([[attributes valueForKey:NSFileProtectionKey] |
+ isEqualToString:NSFileProtectionComplete]) { |
+ _protectedFileCreated = YES; |
+ return; |
+ } |
+ BOOL removed = |
+ [[NSFileManager defaultManager] removeItemAtPath:LockedDeviceFilename() |
+ error:&error]; |
+ if (!removed || error) { |
+ // File could not be removed. FileSystem may be locked ? |
+ return; |
+ } |
+ } |
+ BOOL created = [[NSFileManager defaultManager] |
+ createFileAtPath:LockedDeviceFilename() |
+ contents:[@"Unlock" dataUsingEncoding:NSUTF8StringEncoding] |
+ attributes:@{NSFileProtectionKey : NSFileProtectionComplete}]; |
+ if (!created) { |
+ // File could not be created. FileSystem may be locked ? |
+ return; |
+ } |
+ NSDictionary* attributes = [[NSFileManager defaultManager] |
+ attributesOfItemAtPath:LockedDeviceFilename() |
+ error:&error]; |
+ if ([[attributes valueForKey:NSFileProtectionKey] |
+ isEqualToString:NSFileProtectionComplete]) { |
+ _protectedFileCreated = YES; |
+ return; |
+ } |
+ [[NSFileManager defaultManager] removeItemAtPath:LockedDeviceFilename() |
+ error:&error]; |
+} |
+ |
+@end |