#import "TDAutoTrackManager.h" #import "UIViewController+AutoTrack.h" #import "UIApplication+AutoTrack.h" #import "ThinkingAnalyticsSDKPrivate.h" #import "TDPublicConfig.h" #import "TDAutoClickEvent.h" #import "TDAutoPageViewEvent.h" #import "TDAppLifeCycle.h" #import "TDAppState.h" #import "TDRunTime.h" #import "TDAppStartEvent.h" #import "TDAppEndEvent.h" #import "TDAppEndTracker.h" #import "TDColdStartTracker.h" #import "TDInstallTracker.h" #import "TDAppState.h" #if __has_include() #import #else #import "TDJSONUtil.h" #endif #if __has_include() #import #else #import "NSObject+TDSwizzle.h" #endif #if __has_include() #import #else #import "TDSwizzler.h" #endif #if __has_include() #import #else #import "TDCorePresetDisableConfig.h" #endif #ifndef TD_LOCK #define TD_LOCK(lock) dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER); #endif #ifndef TD_UNLOCK #define TD_UNLOCK(lock) dispatch_semaphore_signal(lock); #endif NSString * const TD_EVENT_PROPERTY_TITLE = @"#title"; NSString * const TD_EVENT_PROPERTY_URL_PROPERTY = @"#url"; NSString * const TD_EVENT_PROPERTY_REFERRER_URL = @"#referrer"; NSString * const TD_EVENT_PROPERTY_SCREEN_NAME = @"#screen_name"; NSString * const TD_EVENT_PROPERTY_ELEMENT_ID = @"#element_id"; NSString * const TD_EVENT_PROPERTY_ELEMENT_TYPE = @"#element_type"; NSString * const TD_EVENT_PROPERTY_ELEMENT_CONTENT = @"#element_content"; NSString * const TD_EVENT_PROPERTY_ELEMENT_POSITION = @"#element_position"; @interface TDAutoTrackManager () @property (atomic, strong) NSMutableDictionary *autoTrackOptions; @property (nonatomic, strong, nonnull) dispatch_semaphore_t trackOptionLock; @property (atomic, copy) NSString *referrerViewControllerUrl; @property (nonatomic, strong) TDHotStartTracker *appHotStartTracker; @property (nonatomic, strong) TDAppEndTracker *appEndTracker; @property (nonatomic, strong) TDColdStartTracker *appColdStartTracker; @property (nonatomic, strong) TDInstallTracker *appInstallTracker; @end @implementation TDAutoTrackManager #pragma mark - Public + (instancetype)sharedManager { static dispatch_once_t once; static TDAutoTrackManager *manager = nil; dispatch_once(&once, ^{ manager = [[[TDAutoTrackManager class] alloc] init]; manager.autoTrackOptions = [NSMutableDictionary new]; manager.trackOptionLock = dispatch_semaphore_create(1); [manager registerAppLifeCycleListener]; }); return manager; } - (void)trackEventView:(UIView *)view { [self trackEventView:view withIndexPath:nil]; } - (void)trackEventView:(UIView *)view withIndexPath:(NSIndexPath *)indexPath { if (view.thinkingAnalyticsIgnoreView) { return; } NSString *elementId = nil; NSString *elementType = nil; NSString *elementContent = nil; NSString *elementPosition = nil; NSString *elementPageTitle = nil; NSString *elementScreenName = nil; NSMutableDictionary *customProperties = [NSMutableDictionary dictionary]; elementId = view.thinkingAnalyticsViewID; elementType = NSStringFromClass([view class]); NSMutableDictionary *properties = [[NSMutableDictionary alloc] init]; properties[TD_EVENT_PROPERTY_ELEMENT_ID] = view.thinkingAnalyticsViewID; properties[TD_EVENT_PROPERTY_ELEMENT_TYPE] = NSStringFromClass([view class]); UIViewController *viewController = [self viewControllerForView:view]; if (viewController != nil) { NSString *screenName = NSStringFromClass([viewController class]); properties[TD_EVENT_PROPERTY_SCREEN_NAME] = screenName; elementScreenName = screenName; NSString *controllerTitle = [self titleFromViewController:viewController]; if (controllerTitle) { properties[TD_EVENT_PROPERTY_TITLE] = controllerTitle; elementPageTitle = controllerTitle; } } NSDictionary *propDict = view.thinkingAnalyticsViewProperties; if ([propDict isKindOfClass:[NSDictionary class]]) { [properties addEntriesFromDictionary:propDict]; [customProperties addEntriesFromDictionary:propDict]; } UIView *contentView; NSDictionary *propertyWithAppid; if (indexPath) { if ([view isKindOfClass:[UITableView class]]) { UITableView *tableView = (UITableView *)view; contentView = [tableView cellForRowAtIndexPath:indexPath]; if (!contentView) { [tableView layoutIfNeeded]; contentView = [tableView cellForRowAtIndexPath:indexPath]; } properties[TD_EVENT_PROPERTY_ELEMENT_POSITION] = [NSString stringWithFormat: @"%ld:%ld", (unsigned long)indexPath.section, (unsigned long)indexPath.row]; elementPosition = [NSString stringWithFormat: @"%ld:%ld", (unsigned long)indexPath.section, (unsigned long)indexPath.row]; if ([tableView.thinkingAnalyticsDelegate conformsToProtocol:@protocol(TDUIViewAutoTrackDelegate)]) { if ([tableView.thinkingAnalyticsDelegate respondsToSelector:@selector(thinkingAnalytics_tableView:autoTrackPropertiesAtIndexPath:)]) { NSDictionary *dic = [view.thinkingAnalyticsDelegate thinkingAnalytics_tableView:tableView autoTrackPropertiesAtIndexPath:indexPath]; if ([dic isKindOfClass:[NSDictionary class]]) { [properties addEntriesFromDictionary:dic]; [customProperties addEntriesFromDictionary:dic]; } } if ([tableView.thinkingAnalyticsDelegate respondsToSelector:@selector(thinkingAnalyticsWithAppid_tableView:autoTrackPropertiesAtIndexPath:)]) { propertyWithAppid = [view.thinkingAnalyticsDelegate thinkingAnalyticsWithAppid_tableView:tableView autoTrackPropertiesAtIndexPath:indexPath]; } } } else if ([view isKindOfClass:[UICollectionView class]]) { UICollectionView *collectionView = (UICollectionView *)view; contentView = [collectionView cellForItemAtIndexPath:indexPath]; if (!contentView) { [collectionView layoutIfNeeded]; contentView = [collectionView cellForItemAtIndexPath:indexPath]; } properties[TD_EVENT_PROPERTY_ELEMENT_POSITION] = [NSString stringWithFormat: @"%ld:%ld", (unsigned long)indexPath.section, (unsigned long)indexPath.row]; elementPosition = [NSString stringWithFormat: @"%ld:%ld", (unsigned long)indexPath.section, (unsigned long)indexPath.row]; if ([collectionView.thinkingAnalyticsDelegate conformsToProtocol:@protocol(TDUIViewAutoTrackDelegate)]) { if ([collectionView.thinkingAnalyticsDelegate respondsToSelector:@selector(thinkingAnalytics_collectionView:autoTrackPropertiesAtIndexPath:)]) { NSDictionary *dic = [view.thinkingAnalyticsDelegate thinkingAnalytics_collectionView:collectionView autoTrackPropertiesAtIndexPath:indexPath]; if ([dic isKindOfClass:[NSDictionary class]]) { [properties addEntriesFromDictionary:dic]; [customProperties addEntriesFromDictionary:dic]; } } if ([collectionView.thinkingAnalyticsDelegate respondsToSelector:@selector(thinkingAnalyticsWithAppid_collectionView:autoTrackPropertiesAtIndexPath:)]) { propertyWithAppid = [view.thinkingAnalyticsDelegate thinkingAnalyticsWithAppid_collectionView:collectionView autoTrackPropertiesAtIndexPath:indexPath]; } } } } else { contentView = view; properties[TD_EVENT_PROPERTY_ELEMENT_POSITION] = [TDAutoTrackManager getPosition:contentView]; elementPosition = [TDAutoTrackManager getPosition:contentView]; } NSString *content = [TDAutoTrackManager getText:contentView]; if (content.length > 0) { properties[TD_EVENT_PROPERTY_ELEMENT_CONTENT] = content; elementContent = content; } NSDate *trackDate = [NSDate date]; for (NSString *appid in self.autoTrackOptions) { TDAutoTrackEventType type = (TDAutoTrackEventType)[self.autoTrackOptions[appid] integerValue]; if (type & ThinkingAnalyticsEventTypeAppClick) { ThinkingAnalyticsSDK *instance = [ThinkingAnalyticsSDK instanceWithAppid:appid]; NSMutableDictionary *trackProperties = [properties mutableCopy]; NSMutableDictionary *finalProperties = [customProperties mutableCopy]; if ([instance innerIsViewTypeIgnored:[view class]]) { continue; } NSDictionary *ignoreViews = view.thinkingAnalyticsIgnoreViewWithAppid; if (ignoreViews != nil && [[ignoreViews objectForKey:appid] isKindOfClass:[NSNumber class]]) { BOOL ignore = [[ignoreViews objectForKey:appid] boolValue]; if (ignore) continue; } if ([instance isViewControllerIgnored:viewController]) { continue; } NSDictionary *viewIDs = view.thinkingAnalyticsViewIDWithAppid; if (viewIDs != nil && [viewIDs objectForKey:appid]) { trackProperties[TD_EVENT_PROPERTY_ELEMENT_ID] = [viewIDs objectForKey:appid]; elementId = [viewIDs objectForKey:appid]; } NSDictionary *viewProperties = view.thinkingAnalyticsViewPropertiesWithAppid; if (viewProperties != nil && [viewProperties objectForKey:appid]) { NSDictionary *properties = [viewProperties objectForKey:appid]; if ([properties isKindOfClass:[NSDictionary class]]) { [trackProperties addEntriesFromDictionary:properties]; [finalProperties addEntriesFromDictionary:properties]; } } if (propertyWithAppid) { NSDictionary *autoTrackproperties = [propertyWithAppid objectForKey:appid]; if ([autoTrackproperties isKindOfClass:[NSDictionary class]]) { [trackProperties addEntriesFromDictionary:autoTrackproperties]; [finalProperties addEntriesFromDictionary:autoTrackproperties]; } } TDAutoClickEvent *clickEvent = [[TDAutoClickEvent alloc] initWithName:TD_APP_CLICK_EVENT]; clickEvent.time = trackDate; clickEvent.elementId = elementId; clickEvent.elementType = elementType; clickEvent.elementContent = elementContent; clickEvent.elementPosition = elementPosition; clickEvent.pageTitle = elementPageTitle; clickEvent.screenName = elementScreenName; [instance autoTrackWithEvent:clickEvent properties:finalProperties]; } } } - (void)trackWithAppid:(NSString *)appid withOption:(TDAutoTrackEventType)type { TD_LOCK(self.trackOptionLock); self.autoTrackOptions[appid] = @(type); TD_UNLOCK(self.trackOptionLock); if (type & ThinkingAnalyticsEventTypeAppClick || type & ThinkingAnalyticsEventTypeAppViewScreen) { [self swizzleVC]; } if (type & ThinkingAnalyticsEventTypeAppInstall) { TDAutoTrackEvent *event = [[TDAutoTrackEvent alloc] initWithName:TD_APP_INSTALL_EVENT]; event.time = [[NSDate date] dateByAddingTimeInterval: -1]; [self.appInstallTracker trackWithInstanceTag:appid event:event params:nil]; } if (type & ThinkingAnalyticsEventTypeAppEnd) { ThinkingAnalyticsSDK *instance = [ThinkingAnalyticsSDK instanceWithAppid:appid]; [instance innerTimeEvent:TD_APP_END_EVENT]; } if (type & ThinkingAnalyticsEventTypeAppStart) { dispatch_block_t mainThreadBlock = ^(){ NSString *eventName = [TDAppState shareInstance].relaunchInBackground ? TD_APP_START_BACKGROUND_EVENT : TD_APP_START_EVENT; TDAppStartEvent *event = [[TDAppStartEvent alloc] initWithName:eventName]; event.resumeFromBackground = NO; if (![TDCorePresetDisableConfig disableStartReason]) { NSString *reason = [TDRunTime getAppLaunchReason]; if (reason && reason.length) { event.startReason = reason; } } [self.appColdStartTracker trackWithInstanceTag:appid event:event params:nil]; }; dispatch_async(dispatch_get_main_queue(), mainThreadBlock); } if (type & ThinkingAnalyticsEventTypeAppViewCrash) { [ThinkingExceptionHandler start]; } TDLogInfo(@"enable auto track: %li", type); } - (void)trackWithEvent:(TDAutoTrackEvent *)event withProperties:(NSDictionary *)properties { for (NSString *appid in self.autoTrackOptions.allKeys) { TDAutoTrackEventType type = (TDAutoTrackEventType)[self.autoTrackOptions[appid] integerValue]; if (type & event.autoTrackEventType) { ThinkingAnalyticsSDK *instance = [ThinkingAnalyticsSDK instanceWithAppid:appid]; [instance autoTrackWithEvent:event properties:properties]; } } } - (void)viewControlWillAppear:(UIViewController *)controller { [self trackViewController:controller]; } + (UIViewController *)topPresentedViewController { UIWindow *keyWindow = [self findWindow]; if (keyWindow != nil && !keyWindow.isKeyWindow) { [keyWindow makeKeyWindow]; } UIViewController *topController = keyWindow.rootViewController; if ([topController isKindOfClass:[UINavigationController class]]) { topController = [(UINavigationController *)topController topViewController]; } while (topController.presentedViewController) { topController = topController.presentedViewController; } return topController; } #pragma mark - Private - (BOOL)isAutoTrackEventType:(TDAutoTrackEventType)eventType { BOOL isIgnored = YES; for (NSString *appid in self.autoTrackOptions) { TDAutoTrackEventType type = (TDAutoTrackEventType)[self.autoTrackOptions[appid] integerValue]; isIgnored = !(type & eventType); if (isIgnored == NO) break; } return !isIgnored; } - (UIViewController *)viewControllerForView:(UIView *)view { UIResponder *responder = view.nextResponder; while (responder) { if ([responder isKindOfClass:[UIViewController class]]) { if ([responder isKindOfClass:[UINavigationController class]]) { responder = [(UINavigationController *)responder topViewController]; continue; } else if ([responder isKindOfClass:UITabBarController.class]) { responder = [(UITabBarController *)responder selectedViewController]; continue; } return (UIViewController *)responder; } responder = responder.nextResponder; } return nil; } - (void)trackViewController:(UIViewController *)controller { if (![self shouldTrackViewContrller:[controller class]]) { return; } NSString *pageUrl = nil; NSString *pageReferrer = nil; NSString *pageTitle = nil; NSString *pageScreenName = nil; NSMutableDictionary *customProperties = [NSMutableDictionary dictionary]; NSMutableDictionary *properties = [[NSMutableDictionary alloc] init]; [properties setValue:NSStringFromClass([controller class]) forKey:TD_EVENT_PROPERTY_SCREEN_NAME]; pageScreenName = NSStringFromClass([controller class]); NSString *controllerTitle = [self titleFromViewController:controller]; if (controllerTitle) { [properties setValue:controllerTitle forKey:TD_EVENT_PROPERTY_TITLE]; pageTitle = controllerTitle; } NSDictionary *autoTrackerAppidDic; if ([controller conformsToProtocol:@protocol(TDAutoTracker)]) { UIViewController *autoTrackerController = (UIViewController *)controller; NSDictionary *autoTrackerDic; if ([controller respondsToSelector:@selector(getTrackPropertiesWithAppid)]) autoTrackerAppidDic = [autoTrackerController getTrackPropertiesWithAppid]; if ([controller respondsToSelector:@selector(getTrackProperties)]) autoTrackerDic = [autoTrackerController getTrackProperties]; if ([autoTrackerDic isKindOfClass:[NSDictionary class]]) { [properties addEntriesFromDictionary:autoTrackerDic]; [customProperties addEntriesFromDictionary:autoTrackerDic]; } } NSDictionary *screenAutoTrackerAppidDic; if ([controller conformsToProtocol:@protocol(TDScreenAutoTracker)]) { UIViewController *screenAutoTrackerController = (UIViewController *)controller; if ([screenAutoTrackerController respondsToSelector:@selector(getScreenUrlWithAppid)]) screenAutoTrackerAppidDic = [screenAutoTrackerController getScreenUrlWithAppid]; if ([screenAutoTrackerController respondsToSelector:@selector(getScreenUrl)]) { NSString *currentUrl = [screenAutoTrackerController getScreenUrl]; [properties setValue:currentUrl forKey:TD_EVENT_PROPERTY_URL_PROPERTY]; pageUrl = currentUrl; [properties setValue:_referrerViewControllerUrl forKey:TD_EVENT_PROPERTY_REFERRER_URL]; pageReferrer = _referrerViewControllerUrl; _referrerViewControllerUrl = currentUrl; } } NSDate *trackDate = [NSDate date]; for (NSString *appid in self.autoTrackOptions) { TDAutoTrackEventType type = [self.autoTrackOptions[appid] integerValue]; if (type & ThinkingAnalyticsEventTypeAppViewScreen) { ThinkingAnalyticsSDK *instance = [ThinkingAnalyticsSDK instanceWithAppid:appid]; NSMutableDictionary *trackProperties = [properties mutableCopy]; NSMutableDictionary *finalProperties = [customProperties mutableCopy]; if ([instance isViewControllerIgnored:controller] || [instance innerIsViewTypeIgnored:[controller class]]) { continue; } if (autoTrackerAppidDic && [autoTrackerAppidDic objectForKey:appid]) { NSDictionary *dic = [autoTrackerAppidDic objectForKey:appid]; if ([dic isKindOfClass:[NSDictionary class]]) { [trackProperties addEntriesFromDictionary:dic]; [finalProperties addEntriesFromDictionary:dic]; } } if (screenAutoTrackerAppidDic && [screenAutoTrackerAppidDic objectForKey:appid]) { NSString *screenUrl = [screenAutoTrackerAppidDic objectForKey:appid]; [trackProperties setValue:screenUrl forKey:TD_EVENT_PROPERTY_URL_PROPERTY]; pageUrl = screenUrl; } TDAutoPageViewEvent *pageEvent = [[TDAutoPageViewEvent alloc] initWithName:TD_APP_VIEW_EVENT]; pageEvent.time = trackDate; pageEvent.pageUrl = pageUrl; pageEvent.pageTitle = pageTitle; pageEvent.referrer = pageReferrer; pageEvent.screenName = pageScreenName; [instance autoTrackWithEvent:pageEvent properties:finalProperties]; } } } - (BOOL)shouldTrackViewContrller:(Class)aClass { return ![TDPublicConfig.controllers containsObject:NSStringFromClass(aClass)]; } - (TDAutoTrackEventType)autoTrackOptionForAppid:(NSString *)appid { return (TDAutoTrackEventType)[[self.autoTrackOptions objectForKey:appid] integerValue]; } - (void)swizzleSelected:(UIView *)view delegate:(id)delegate { if ([view isKindOfClass:[UITableView class]] && [delegate conformsToProtocol:@protocol(UITableViewDelegate)]) { void (^block)(id, SEL, id, id) = ^(id target, SEL command, UITableView *tableView, NSIndexPath *indexPath) { [self trackEventView:tableView withIndexPath:indexPath]; }; [TDSwizzler swizzleSelector:@selector(tableView:didSelectRowAtIndexPath:) onClass:[delegate class] withBlock:block named:@"td_table_select"]; } if ([view isKindOfClass:[UICollectionView class]] && [delegate conformsToProtocol:@protocol(UICollectionViewDelegate)]) { void (^block)(id, SEL, id, id) = ^(id target, SEL command, UICollectionView *collectionView, NSIndexPath *indexPath) { [self trackEventView:collectionView withIndexPath:indexPath]; }; [TDSwizzler swizzleSelector:@selector(collectionView:didSelectItemAtIndexPath:) onClass:[delegate class] withBlock:block named:@"td_collection_select"]; } } - (void)swizzleVC { static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ void (^tableViewBlock)(UITableView *tableView, SEL cmd, id delegate) = ^(UITableView *tableView, SEL cmd, id delegate) { if (!delegate) { return; } [self swizzleSelected:tableView delegate:delegate]; }; [TDSwizzler swizzleSelector:@selector(setDelegate:) onClass:[UITableView class] withBlock:tableViewBlock named:@"td_table_delegate"]; void (^collectionViewBlock)(UICollectionView *, SEL, id) = ^(UICollectionView *collectionView, SEL cmd, id delegate) { if (nil == delegate) { return; } [self swizzleSelected:collectionView delegate:delegate]; }; [TDSwizzler swizzleSelector:@selector(setDelegate:) onClass:[UICollectionView class] withBlock:collectionViewBlock named:@"td_collection_delegate"]; [UIViewController td_swizzleMethod:@selector(viewWillAppear:) withMethod:@selector(td_autotrack_viewWillAppear:) error:NULL]; [UIApplication td_swizzleMethod:@selector(sendAction:to:from:forEvent:) withMethod:@selector(td_sendAction:to:from:forEvent:) error:NULL]; }); } + (NSString *)getPosition:(UIView *)view { NSString *position = nil; if ([view isKindOfClass:[UIView class]] && view.thinkingAnalyticsIgnoreView) { return nil; } if ([view isKindOfClass:[UITabBar class]]) { UITabBar *tabbar = (UITabBar *)view; position = [NSString stringWithFormat: @"%ld", (long)[tabbar.items indexOfObject:tabbar.selectedItem]]; } else if ([view isKindOfClass:[UISegmentedControl class]]) { UISegmentedControl *segment = (UISegmentedControl *)view; position = [NSString stringWithFormat:@"%ld", (long)segment.selectedSegmentIndex]; } else if ([view isKindOfClass:[UIProgressView class]]) { UIProgressView *progress = (UIProgressView *)view; position = [NSString stringWithFormat:@"%f", progress.progress]; } else if ([view isKindOfClass:[UIPageControl class]]) { UIPageControl *pageControl = (UIPageControl *)view; position = [NSString stringWithFormat:@"%ld", (long)pageControl.currentPage]; } return position; } + (NSString *)getText:(NSObject *)obj { NSString *text = nil; if ([obj isKindOfClass:[UIView class]] && [(UIView *)obj thinkingAnalyticsIgnoreView]) { return nil; } if ([obj isKindOfClass:[UIButton class]]) { text = ((UIButton *)obj).currentTitle; } else if ([obj isKindOfClass:[UITextView class]] || [obj isKindOfClass:[UITextField class]]) { //ignore } else if ([obj isKindOfClass:[UILabel class]]) { text = ((UILabel *)obj).text; } else if ([obj isKindOfClass:[UIPickerView class]]) { UIPickerView *picker = (UIPickerView *)obj; NSInteger sections = picker.numberOfComponents; NSMutableArray *titles = [NSMutableArray array]; for(NSInteger i = 0; i < sections; i++) { NSInteger row = [picker selectedRowInComponent:i]; NSString *title; if ([picker.delegate respondsToSelector:@selector(pickerView:titleForRow:forComponent:)]) { title = [picker.delegate pickerView:picker titleForRow:row forComponent:i]; } else if ([picker.delegate respondsToSelector:@selector(pickerView:attributedTitleForRow:forComponent:)]) { title = [picker.delegate pickerView:picker attributedTitleForRow:row forComponent:i].string; } [titles addObject:title ?: @""]; } if (titles.count > 0) { text = [titles componentsJoinedByString:@","]; } } else if ([obj isKindOfClass:[UIDatePicker class]]) { UIDatePicker *picker = (UIDatePicker *)obj; NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; formatter.dateFormat = kDefaultTimeFormat; text = [formatter stringFromDate:picker.date]; } else if ([obj isKindOfClass:[UISegmentedControl class]]) { UISegmentedControl *segment = (UISegmentedControl *)obj; text = [NSString stringWithFormat:@"%@", [segment titleForSegmentAtIndex:segment.selectedSegmentIndex]]; } else if ([obj isKindOfClass:[UISwitch class]]) { UISwitch *switchItem = (UISwitch *)obj; text = switchItem.on ? @"on" : @"off"; } else if ([obj isKindOfClass:[UISlider class]]) { UISlider *slider = (UISlider *)obj; text = [NSString stringWithFormat:@"%f", [slider value]]; } else if ([obj isKindOfClass:[UIStepper class]]) { UIStepper *step = (UIStepper *)obj; text = [NSString stringWithFormat:@"%f", [step value]]; } else { if ([obj isKindOfClass:[UIView class]]) { for(UIView *subView in [(UIView *)obj subviews]) { text = [TDAutoTrackManager getText:subView]; if ([text isKindOfClass:[NSString class]] && text.length > 0) { break; } } } } return text; } - (NSString *)titleFromViewController:(UIViewController *)viewController { if (!viewController) { return nil; } UIView *titleView = viewController.navigationItem.titleView; NSString *elementContent = nil; if (titleView) { elementContent = [TDAutoTrackManager getText:titleView]; } return elementContent.length > 0 ? elementContent : viewController.navigationItem.title; } + (UIWindow *)findWindow { UIApplication *application = [TDAppState sharedApplication]; if (![application isKindOfClass:UIApplication.class]) { return nil; } UIWindow *window = application.keyWindow; if (window == nil || window.windowLevel != UIWindowLevelNormal) { for (window in application.windows) { if (window.windowLevel == UIWindowLevelNormal) { break; } } } if (@available(iOS 13.0, tvOS 13, *)) { NSSet *scenes = [[TDAppState sharedApplication] valueForKey:@"connectedScenes"]; for (id scene in scenes) { if (window) { break; } id activationState = [scene valueForKeyPath:@"activationState"]; BOOL isActive = activationState != nil && [activationState integerValue] == 0; if (isActive) { Class WindowScene = NSClassFromString(@"UIWindowScene"); if ([scene isKindOfClass:WindowScene]) { NSArray *windows = [scene valueForKeyPath:@"windows"]; for (UIWindow *w in windows) { if (w.isKeyWindow) { window = w; break; } } } } } } return window; } //MARK: - App Life Cycle - (void)registerAppLifeCycleListener { NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; [notificationCenter addObserver:self selector:@selector(appStateWillChangeNotification:) name:kTDAppLifeCycleStateWillChangeNotification object:nil]; } - (void)appStateWillChangeNotification:(NSNotification *)notification { TDAppLifeCycleState newState = [[notification.userInfo objectForKey:kTDAppLifeCycleNewStateKey] integerValue]; TDAppLifeCycleState oldState = [[notification.userInfo objectForKey:kTDAppLifeCycleOldStateKey] integerValue]; if (newState == TDAppLifeCycleStateStart) { for (NSString *appid in self.autoTrackOptions.allKeys) { TDAutoTrackEventType type = (TDAutoTrackEventType)[self.autoTrackOptions[appid] integerValue]; // Only open the start event of collecting hot start. Cold start event, reported when automatic collection is turned on if ((type & ThinkingAnalyticsEventTypeAppStart) && oldState != TDAppLifeCycleStateInit) { NSString *eventName = [TDAppState shareInstance].relaunchInBackground ? TD_APP_START_BACKGROUND_EVENT : TD_APP_START_EVENT; TDAppStartEvent *event = [[TDAppStartEvent alloc] initWithName:eventName]; event.resumeFromBackground = YES; if (![TDCorePresetDisableConfig disableStartReason]) { NSString *reason = [TDRunTime getAppLaunchReason]; if (reason && reason.length) { event.startReason = reason; } } [self.appHotStartTracker trackWithInstanceTag:appid event:event params:@{}]; } if (type & ThinkingAnalyticsEventTypeAppEnd) { ThinkingAnalyticsSDK *instance = [ThinkingAnalyticsSDK instanceWithAppid:appid]; [instance innerTimeEvent:TD_APP_END_EVENT]; } } } else if (newState == TDAppLifeCycleStateEnd) { for (NSString *appid in self.autoTrackOptions) { TDAutoTrackEventType type = (TDAutoTrackEventType)[self.autoTrackOptions[appid] integerValue]; if (type & ThinkingAnalyticsEventTypeAppEnd) { TDAppEndEvent *event = [[TDAppEndEvent alloc] initWithName:TD_APP_END_EVENT]; td_dispatch_main_sync_safe(^{ NSString *screenName = NSStringFromClass([[TDAutoTrackManager topPresentedViewController] class]); event.screenName = screenName; [self.appEndTracker trackWithInstanceTag:appid event:event params:@{}]; }); } if (type & ThinkingAnalyticsEventTypeAppStart) { ThinkingAnalyticsSDK *instance = [ThinkingAnalyticsSDK instanceWithAppid:appid]; [instance innerTimeEvent:TD_APP_START_EVENT]; } } } } //MARK: - Getter & Setter - (TDHotStartTracker *)appHotStartTracker { if (!_appHotStartTracker) { _appHotStartTracker = [[TDHotStartTracker alloc] init]; } return _appHotStartTracker; } - (TDColdStartTracker *)appColdStartTracker { if (!_appColdStartTracker) { _appColdStartTracker = [[TDColdStartTracker alloc] init]; } return _appColdStartTracker; } - (TDInstallTracker *)appInstallTracker { if (!_appInstallTracker) { _appInstallTracker = [[TDInstallTracker alloc] init]; } return _appInstallTracker; } - (TDAppEndTracker *)appEndTracker { if (!_appEndTracker) { _appEndTracker = [[TDAppEndTracker alloc] init]; } return _appEndTracker; } @end