diff --git a/Profile Manifest/com.twocanoes.xcreds.plist b/Profile Manifest/com.twocanoes.xcreds.plist index b3e1d129..345bad7f 100644 --- a/Profile Manifest/com.twocanoes.xcreds.plist +++ b/Profile Manifest/com.twocanoes.xcreds.plist @@ -5,7 +5,7 @@ pfm_app_url https://github.com/twocanoes/xcreds pfm_description - XCreds 4.0 (6177) OAuth Settings + XCreds 4.0 (6186) OAuth Settings pfm_documentation_url https://twocanoes.com/knowledge-base/xcreds-admin-guide/#preferences pfm_domain @@ -356,8 +356,6 @@ A profile can consist of payloads with different version numbers. For example, c pfm_type integer - - pfm_default Please Wait.... @@ -386,11 +384,6 @@ A profile can consist of payloads with different version numbers. For example, c pfm_type string - - - - - pfm_default file:///System/Library/Desktop Pictures/Monterey Graphic.heic diff --git a/TCTaskWrapperWithBlocks.h b/TCTaskWrapperWithBlocks.h new file mode 100644 index 00000000..cbbdfbae --- /dev/null +++ b/TCTaskWrapperWithBlocks.h @@ -0,0 +1 @@ +#import @interface TCTaskWrapperWithBlocks : NSObject { NSTask *task; void (^StartBlock)(void); void (^EndBlock)(void); void (^OutputBlock)(NSString *output); void (^ErrorOutputBlock)(NSString *errorOutput); NSArray *arguments; BOOL isBinaryOutput; } // This is the designated initializer - pass in your controller and any task arguments. // The first argument should be the path to the executable to launch with the NSTask. - (id)initWithStartBlock:(void(^)(void))inStartBlock endBlock:(void(^)(void))inEndBlock outputBlock:(void(^)(NSString *output))inOutputBlock errorOutputBlock:(void(^)(NSString *errorOutput))inErrorOutputBlockBlock arguments:(NSArray *)args; // This method launches the process, setting up asynchronous feedback notifications. - (void) startProcess; // This method stops the process, stoping asynchronous feedback notifications. - (void) stopProcess; -(int)terminationStatus; -(BOOL)isRunning; -(void)terminate; @end \ No newline at end of file diff --git a/TCTaskWrapperWithBlocks.m b/TCTaskWrapperWithBlocks.m new file mode 100644 index 00000000..57615fec --- /dev/null +++ b/TCTaskWrapperWithBlocks.m @@ -0,0 +1 @@ +#import "TCTaskWrapperWithBlocks.h" @implementation TCTaskWrapperWithBlocks // Do basic initialization //- (id)initWithController:(id )cont arguments:(NSArray *)args //{ // // self = [super init]; // controller = cont; // arguments = args; // // return self; //} -(id)initWithStartBlock:(void (^)(void))inStartBlock endBlock:(void (^)(void))inEndBlock outputBlock:(void (^)(NSString *))inOutputBlock errorOutputBlock:(void (^)(NSString *))inErrorOutputBlockBlock arguments:(NSArray *)args{ self = [super init]; if (self){ StartBlock=inStartBlock; EndBlock=inEndBlock; OutputBlock=inOutputBlock; ErrorOutputBlock=inErrorOutputBlockBlock; arguments=args; } return self; } // tear things down -(int)terminationStatus{ if ([task isRunning]==YES) { sleep(5); if ([task isRunning]==YES) return -999; } return [task terminationStatus]; } // Here's where we actually kick off the process via an NSTask. - (void) startProcess { // We first let the controller know that we are starting StartBlock(); task=nil; task = [[NSTask alloc] init]; // The output of stdout and stderr is sent to a pipe so that we can catch it later // and send it along to the controller; notice that we don't bother to do anything with stdin, // so this class isn't as useful for a task that you need to send info to, not just receive. [task setStandardOutput: [NSPipe pipe]]; [task setStandardError: [NSPipe pipe]]; [task setStandardInput:[NSPipe pipe]]; [task setLaunchPath: [arguments objectAtIndex:0]]; // The rest of the task arguments are just grabbed from the array [task setArguments: [arguments subarrayWithRange: NSMakeRange (1, ([arguments count] - 1))]]; // Here we register as an observer of the NSFileHandleReadCompletionNotification, which lets // us know when there is data waiting for us to grab it in the task's file handle (the pipe // to which we connected stdout and stderr above). -getData: will be called when there // is data waiting. The reason we need to do this is because if the file handle gets // filled up, the task will block waiting to send data and we'll never get anywhere. // So we have to keep reading data from the file handle as we go. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getData:) name: NSFileHandleReadCompletionNotification object: [[task standardOutput] fileHandleForReading]]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(getStdErr:) name: NSFileHandleReadCompletionNotification object: [[task standardError] fileHandleForReading]]; // We tell the file handle to go ahead and read in the background asynchronously, and notify // us via the callback registered above when we signed up as an observer. The file handle will // send a NSFileHandleReadCompletionNotification when it has data that is available. [[[task standardOutput] fileHandleForReading] readInBackgroundAndNotify]; [[[task standardError] fileHandleForReading] readInBackgroundAndNotify]; // launch the task asynchronously [task launch]; } // If the task ends, there is no more data coming through the file handle even when the notification is // sent, or the process object is released, then this method is called. - (void) stopProcess { /* // we tell the controller that we finished, via the callback, and then blow away our connection // to the controller. NSTasks are one-shot (not for reuse), so we might as well be too. [controller processFinished]; controller = nil;*/ // It is important to clean up after ourselves so that we don't leave potentially deallocated // objects as observers in the notification center; this can lead to crashes. [[NSNotificationCenter defaultCenter] removeObserver:self name:NSFileHandleReadCompletionNotification object: nil]; // [[NSNotificationCenter defaultCenter] removeObserver:self name:NSTaskDidTerminateNotification object:nil]; // Make sure the task has actually stopped! [task terminate]; EndBlock(); // we tell the controller that we finished, via the callback, and then blow away our connection // to the controller. NSTasks are one-shot (not for reuse), so we might as well be too. // [controller processFinished]; // controller = nil; } // This method is called asynchronously when data is available from the task's file handle. // We just pass the data along to the controller as an NSString. - (void) getData: (NSNotification *)aNotification { NSData *data = [[aNotification userInfo] objectForKey:NSFileHandleNotificationDataItem]; // If the length of the data is zero, then the task is basically over - there is nothing // more to get from the handle so we may as well shut down. if ([data length]) { OutputBlock( [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); } else { // We're finished here [self stopProcess]; return; } // we need to schedule the file handle go read more data in the background again. [[aNotification object] readInBackgroundAndNotify]; } - (void) getStdErr: (NSNotification *)aNotification { NSData *data = [[aNotification userInfo] objectForKey:NSFileHandleNotificationDataItem]; // // If the length of the data is zero, then the task is basically over - there is nothing // // more to get from the handle so we may as well shut down. if ([data length]) { ErrorOutputBlock([[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); } // // // we need to schedule the file handle go read more data in the background again. [[aNotification object] readInBackgroundAndNotify]; } -(void)dealloc{ } -(void)terminate{ if (task && self.isRunning){ [task terminate]; } } -(BOOL)isRunning{ return task.running; } -(int)pid{ return task==nil?-1:task.processIdentifier; } @end \ No newline at end of file diff --git a/XCreds/XCreds-Bridging-Header.h b/XCreds/XCreds-Bridging-Header.h index 34367e3f..cd54d9a2 100644 --- a/XCreds/XCreds-Bridging-Header.h +++ b/XCreds/XCreds-Bridging-Header.h @@ -14,6 +14,7 @@ #import #import "TCSLoginWindowUtilities.h" #import "DNSResolver.h" +#import "TCTaskWrapperWithBlocks.h" // Kerb bits #import "KerbUtil.h" diff --git a/XCreds/XCredsLoginPlugin-Bridging-Header.h b/XCreds/XCredsLoginPlugin-Bridging-Header.h index 64712a67..dcce58b6 100644 --- a/XCreds/XCredsLoginPlugin-Bridging-Header.h +++ b/XCreds/XCredsLoginPlugin-Bridging-Header.h @@ -9,7 +9,7 @@ #import #include #import "DNSResolver.h" - +#import "TCTaskWrapperWithBlocks.h" // Kerb bits #import "KerbUtil.h" #import "GSSItem.h" diff --git a/XCredsLoginPlugIn/WifiManager.swift b/XCredsLoginPlugIn/WifiManager.swift index 7e1904f2..368199bd 100644 --- a/XCredsLoginPlugIn/WifiManager.swift +++ b/XCredsLoginPlugIn/WifiManager.swift @@ -18,6 +18,10 @@ enum SecurityType { case enterpriseUserPassword //show user and password } +enum WiFiPowerState:String { + case off = "off" + case on = "on" +} @objc protocol WifiManagerDelegate: AnyObject { func wifiManagerFullyFinishedInternetConnectionTimer() @objc optional func wifiManagerConnectedToNetwork() @@ -30,7 +34,7 @@ class WifiManager: CWEventDelegate { var timer: Timer? weak var delegate: WifiManagerDelegate? var monitor:NWPathMonitor? - + var task:TCTaskWrapperWithBlocks? init() { let defaultInterface = CWWiFiClient.shared().interface() // CWWiFiClient.shared().delegate = self @@ -50,8 +54,64 @@ class WifiManager: CWEventDelegate { currentInterface = CWWiFiClient.shared().interface(withName: "en1") } } + } + func wifiState(completion:@escaping(WiFiPowerState)->Void) { + let interface = wifiInterface() + + var output:String = "" + var errOutput = "" + task = TCTaskWrapperWithBlocks(start: { + + }, end: { + + if output.contains("On") { + completion(.on) + } + else { + completion(.off) + } + }, outputBlock: { outputMsg in + if let outputMsg = outputMsg { + output += outputMsg + } + }, errorOutputBlock: { outputErr in + if let outputErr = outputErr { + errOutput += outputErr + } + }, arguments: ["/usr/sbin/networksetup","-getairportpower", interface]) + task?.startProcess() + + + } + + func setWiFiState(_ powerState:WiFiPowerState,completion:@escaping()->Void) { + + let interface = wifiInterface() + + task = TCTaskWrapperWithBlocks(start: { + + }, end: { + completion() + }, outputBlock: { outputMsg in + TCSLogWithMark(outputMsg ?? "") + }, errorOutputBlock: { outputErr in + TCSLogWithMark(outputErr ?? "") + }, arguments: ["/usr/sbin/networksetup","-setairportpower", interface,powerState.rawValue]) + task?.startProcess() + + } + func wifiInterface() -> String { + let names = CWWiFiClient.interfaceNames() + + if names?.contains("en0") != nil { + return "en0" + } + else { + return "en1" + } + } func getCurrentSSID() -> String? { TCSLogWithMark() return currentInterface?.ssid() @@ -132,6 +192,10 @@ class WifiManager: CWEventDelegate { return false } + func turnWiFiOff() { + + + } func connectWifi(with network: CWNetwork, password: String?, username: String?, identity: SecIdentity? = nil) -> Bool { var result = false diff --git a/XCredsLoginPlugIn/WifiWindowController.swift b/XCredsLoginPlugIn/WifiWindowController.swift index 21c27754..f774842e 100644 --- a/XCredsLoginPlugIn/WifiWindowController.swift +++ b/XCredsLoginPlugIn/WifiWindowController.swift @@ -28,6 +28,7 @@ class WifiWindowController: NSWindowController, WifiManagerDelegate, NSMenuDeleg @IBOutlet weak var addSSIDText: NSTextField? @IBOutlet weak var addSSIDLabel: NSTextField? + @IBOutlet weak var wifiSwitch: NSSwitch! @IBOutlet weak var networkUsernameLabel: NSTextField! @IBOutlet weak var wifiPopupMenu: NSMenu! @IBAction func help(_ sender: Any) { @@ -65,6 +66,16 @@ class WifiWindowController: NSWindowController, WifiManagerDelegate, NSMenuDeleg TCSLogWithMark("adding wifi networks") certificatePopupButton.addItems(withTitles: WifiManager().identityCommonNames()) + + wifiManager.wifiState() { state in + switch state { + case .off: + self.wifiSwitch.state = .off + + case .on: + self.wifiSwitch.state = .on + } + } } @@ -105,7 +116,6 @@ class WifiWindowController: NSWindowController, WifiManagerDelegate, NSMenuDeleg self.networkConnectionSpinner?.stopAnimation(self) self.networkConnectionSpinner?.isHidden=true self.updateNetworks() - } } @@ -199,6 +209,31 @@ class WifiWindowController: NSWindowController, WifiManagerDelegate, NSMenuDeleg } + @IBAction func wifiButtonPressed(_ sender: NSSwitch) { + + if sender.state == .off { + wifiManager.setWiFiState(.off) { + self.updateAvailableNetworks() + + + + } + } + else { + wifiManager.setWiFiState(.on) { + self.networkWifiPopup?.isEnabled=false + self.networkConnectionSpinner?.startAnimation(true) + self.networkConnectionSpinner?.isHidden=false + + DispatchQueue.main.asyncAfter(deadline: .now() + 3) { + self.updateAvailableNetworks() + } + } + } + TCSLogWithMark("Wifi Button Switch changed") + + + } func configureUIForSelectedNetwork(network: CWNetwork) { self.networkUsername?.stringValue = "" self.networkPassword?.stringValue = "" diff --git a/XCredsLoginPlugIn/WifiWindowController.xib b/XCredsLoginPlugIn/WifiWindowController.xib index 6c6ae684..c271e2f6 100644 --- a/XCredsLoginPlugIn/WifiWindowController.xib +++ b/XCredsLoginPlugIn/WifiWindowController.xib @@ -1,8 +1,8 @@ - + - + @@ -19,6 +19,7 @@ + @@ -28,20 +29,19 @@ - + - + - - - + + - + - - + + - + @@ -85,12 +85,37 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + - + @@ -193,7 +221,7 @@ DQ - + @@ -201,7 +229,7 @@ DQ - + @@ -209,7 +237,7 @@ DQ - + @@ -217,7 +245,7 @@ DQ - + diff --git a/xCreds.xcodeproj/project.pbxproj b/xCreds.xcodeproj/project.pbxproj index 85b57cfa..5937e9a8 100644 --- a/xCreds.xcodeproj/project.pbxproj +++ b/xCreds.xcodeproj/project.pbxproj @@ -183,6 +183,8 @@ 76D175742B23C57500E64A62 /* LocalUsersViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 7651EDEC2A1451590075980B /* LocalUsersViewController.xib */; }; 76D175772B23C62A00E64A62 /* UpdatePasswordWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76E466642B1A4C16006529B6 /* UpdatePasswordWindowController.swift */; }; 76D1757E2B24096C00E64A62 /* MainLoginWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 76D1757D2B24096C00E64A62 /* MainLoginWindow.swift */; }; + 76D4726D2B43B8FB0064380C /* TCTaskWrapperWithBlocks.m in Sources */ = {isa = PBXBuildFile; fileRef = 76D4726C2B43B8FB0064380C /* TCTaskWrapperWithBlocks.m */; }; + 76D4726E2B43B8FB0064380C /* TCTaskWrapperWithBlocks.m in Sources */ = {isa = PBXBuildFile; fileRef = 76D4726C2B43B8FB0064380C /* TCTaskWrapperWithBlocks.m */; }; 76D7ADFB284EB15100332EBC /* TCSUnifiedLogger.m in Sources */ = {isa = PBXBuildFile; fileRef = 76D7ADF9284EB15000332EBC /* TCSUnifiedLogger.m */; }; 76D7ADFE284EB18600332EBC /* NSFileManager+TCSRealHomeFolder.m in Sources */ = {isa = PBXBuildFile; fileRef = 76D7ADFC284EB18600332EBC /* NSFileManager+TCSRealHomeFolder.m */; }; 76D925D32894ADB4005C3245 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 76EE069F27FD1D01009E0F3A /* Assets.xcassets */; }; @@ -455,6 +457,8 @@ 76CCF5422B12E478003F85E9 /* SelectLocalAccountWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectLocalAccountWindowController.swift; sourceTree = ""; }; 76CCF5432B12E478003F85E9 /* SelectLocalAccountWindowController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = SelectLocalAccountWindowController.xib; sourceTree = ""; }; 76D1757D2B24096C00E64A62 /* MainLoginWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainLoginWindow.swift; sourceTree = ""; }; + 76D4726B2B43B8FA0064380C /* TCTaskWrapperWithBlocks.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCTaskWrapperWithBlocks.h; sourceTree = ""; }; + 76D4726C2B43B8FB0064380C /* TCTaskWrapperWithBlocks.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TCTaskWrapperWithBlocks.m; sourceTree = ""; }; 76D7ADF9284EB15000332EBC /* TCSUnifiedLogger.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TCSUnifiedLogger.m; sourceTree = ""; }; 76D7ADFA284EB15100332EBC /* TCSUnifiedLogger.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TCSUnifiedLogger.h; sourceTree = ""; }; 76D7ADFC284EB18600332EBC /* NSFileManager+TCSRealHomeFolder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSFileManager+TCSRealHomeFolder.m"; sourceTree = ""; }; @@ -766,6 +770,8 @@ 76EE069127FD1D00009E0F3A = { isa = PBXGroup; children = ( + 76D4726B2B43B8FA0064380C /* TCTaskWrapperWithBlocks.h */, + 76D4726C2B43B8FB0064380C /* TCTaskWrapperWithBlocks.m */, 76C4ACBE2B3D0F8D003B3605 /* ShareMounter.swift */, 76C4BAB92B353B3F007B2C57 /* NoMAD */, 760148A82B23639D00E119A2 /* NSBundle+FindBundlePath.swift */, @@ -1193,6 +1199,7 @@ 7677908628908E40004E7085 /* WifiWindowController.swift in Sources */, 76E466662B1A4C16006529B6 /* UpdatePasswordWindowController.swift in Sources */, 76EECCFD2873E9ED00483C66 /* VerifyLocalPasswordWindowController.swift in Sources */, + 76D4726E2B43B8FB0064380C /* TCTaskWrapperWithBlocks.m in Sources */, 76BEF7EC28724A0B0013E2A1 /* XCredsLoginMechanism.swift in Sources */, 766355CA2870DCF5002E3867 /* TCSUnifiedLogger.m in Sources */, 76C4BAB02B353A30007B2C57 /* KlistUtil.swift in Sources */, @@ -1321,6 +1328,7 @@ 76E74DCF2B3902F0004C6429 /* XCredsMechanismProtocol.swift in Sources */, 76EE06C227FD1F50009E0F3A /* MainMenu.swift in Sources */, 76EE06B027FD1DD8009E0F3A /* Window+ForceToFront.swift in Sources */, + 76D4726D2B43B8FB0064380C /* TCTaskWrapperWithBlocks.m in Sources */, 767116B1284B021500CCD6FF /* MainController.swift in Sources */, 7657DECC2B35061E003A23DB /* SiteManager.swift in Sources */, 76B040A428EFC788002A289B /* Helper+JWTDecode.swift in Sources */, @@ -1424,7 +1432,7 @@ buildSettings = { CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 6185; + CURRENT_PROJECT_VERSION = 6199; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = UXP6YEHSPW; FRAMEWORK_SEARCH_PATHS = ( @@ -1461,7 +1469,7 @@ buildSettings = { CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 6185; + CURRENT_PROJECT_VERSION = 6199; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = UXP6YEHSPW; FRAMEWORK_SEARCH_PATHS = ( @@ -1582,7 +1590,7 @@ CODE_SIGN_ENTITLEMENTS = "XCreds Login Overlay/XCreds_Login_Overlay.entitlements"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 6185; + CURRENT_PROJECT_VERSION = 6199; DEVELOPMENT_TEAM = UXP6YEHSPW; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -1619,7 +1627,7 @@ CODE_SIGN_ENTITLEMENTS = "XCreds Login Overlay/XCreds_Login_Overlay.entitlements"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 6185; + CURRENT_PROJECT_VERSION = 6199; DEVELOPMENT_TEAM = UXP6YEHSPW; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -1769,7 +1777,7 @@ CODE_SIGN_ENTITLEMENTS = XCreds/xCreds.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 6185; + CURRENT_PROJECT_VERSION = 6199; DEVELOPMENT_TEAM = UXP6YEHSPW; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( @@ -1811,7 +1819,7 @@ CODE_SIGN_ENTITLEMENTS = XCreds/xCreds.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 6185; + CURRENT_PROJECT_VERSION = 6199; DEVELOPMENT_TEAM = UXP6YEHSPW; ENABLE_HARDENED_RUNTIME = YES; FRAMEWORK_SEARCH_PATHS = ( diff --git a/xCreds.xcodeproj/project.xcworkspace/xcuserdata/tperfitt.xcuserdatad/UserInterfaceState.xcuserstate b/xCreds.xcodeproj/project.xcworkspace/xcuserdata/tperfitt.xcuserdatad/UserInterfaceState.xcuserstate index 35b4f9ee..80bd9dbe 100644 Binary files a/xCreds.xcodeproj/project.xcworkspace/xcuserdata/tperfitt.xcuserdatad/UserInterfaceState.xcuserstate and b/xCreds.xcodeproj/project.xcworkspace/xcuserdata/tperfitt.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/xCreds.xcodeproj/xcuserdata/tperfitt.xcuserdatad/xcschemes/xcschememanagement.plist b/xCreds.xcodeproj/xcuserdata/tperfitt.xcuserdatad/xcschemes/xcschememanagement.plist index c7ebebde..fa475150 100644 --- a/xCreds.xcodeproj/xcuserdata/tperfitt.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/xCreds.xcodeproj/xcuserdata/tperfitt.xcuserdatad/xcschemes/xcschememanagement.plist @@ -32,7 +32,7 @@ auth_mech_fixup.xcscheme_^#shared#^_ orderHint - 7 + 8 authrights.xcscheme_^#shared#^_ @@ -42,7 +42,7 @@ test.xcscheme_^#shared#^_ orderHint - 8 + 7 SuppressBuildableAutocreation