/////////////////////////////////////////////////////////////////////////// // Created by Andre Meyer (ameyer@ifi.uzh.ch) from the University of Zurich /////////////////////////////////////////////////////////////////////////// using System; using System.Collections.Generic; using System.Linq; using AnalysisFragmentedWork.Models; namespace AnalysisFragmentedWork.Mappers { /// /// This helper class is used to map a program to an activity /// category based on heuristics (defined as lists below) /// public static class WindowsActivityToCategoryMapper { #region Context Mapping Lists private static readonly List EditorApps = new List { "notepad", "xmlspy", "sublime", "emacs", "vim", "atom", "texteditor", "editplus", "gedit", "textpad" }; private static readonly List EditorNotEnoughInfoList = new List { "go to...", "find", "untitled - notepad", "save", "speichern unter", "speichern", "suche", "replace", "ersetzen", "*new", "open", "reload", "new", "save as" }; // from notepad++ private static readonly List CodingApps = new List { "webplatforminstaller", "xts", "cleardescribe", "clearfindco", "alm-client", "ildasm", "ssms", "mintty", "xming", "clearprojexp", "clearmrgman", "kitty", "bc2", "bcompare", "mobaxterm", "webmatrix", "mexplore", "linqpad", "android sdk manager", "windows phone application deployment", "ilspy", "tortoiseproc", "xsd", "eclipse", "fiddler", "xamarin", "netbeans", "intellij", "sql", "sqlitebrowser", "devenv", "visual studio", "vs_enterprise", "vs2013", "microsoftazuretools", "webstorm", "phpstorm", "source insight", "zend", "console", "powershell", "shell", "cmd", "tasktop", "android studio", "ide", "filezilla", "flashfxp", "charles" }; private static readonly List CodeTypeApps = new List { ".proj", ".cmd", ".ini", ".err", ".sql", ".ksh", ".dat", ".xaml", ".rb", ".kml", ".log", ".bat", ".cs", ".vb", ".py", ".xml", ".dtd", ".xs", ".h", ".cpp", ".java", ".class", ".js", ".asp", ".aspx", ".css", ".html", ".htm", ".js", ".php", ".xhtml", ".sh", ".sln", ".vcxproj", ".pl" }; private static readonly List CodingDebugApps = new List { "xde", "debug" }; // works for visual studio, eclipse (if view changes) private static readonly List CodingReviewApps = new List { "codeflow", "gerrit", "stash", "kallithea", "code review", "rhodecode", "rietveld", "crucible", "phabricator" }; private static readonly List CodingVersionControlApps = new List { "repository", "cleardiffbl", "cleardlg", "cleardiffmrg", "clearhistory", "clearvtree", "sourcetree", "svn", "tortoiseproc", "scm", "tfs", "push", "pull", "commit", "gitlab", "github", "bitbucket", "visual studio online" }; private static readonly List EmailApps = new List { "mail", "outlook", "thunderbird", "outlook.com" }; // incudes gmail, yahoo mail, mac mail, outlook.com private static readonly List PlanningApps = new List { "backlog", "winproj", "sap", "rescuetime", "clearquest", "scrum", "kanban", "codealike", "jira", "rally", "versionone", "calendar", "kalender", "sprint", "user story", "plan", "task", "aufgabe", "vorgangsliste", "work item" }; private static readonly List ReadingWritingApps = new List { "snagiteditor", "confluence", "picasa", "windows photo viewer", "flashmedialiveencoder", "photofiltre", "jmp", "treepad", "winword", "word", "leo", "translate", "übersetzer", "mspub", "excel", "powerpnt", "onenote", "evernote", "acrord", "sharepoint", "pdf", "foxitreader", "adobe reader", "reader", "glcnd", "wiki", "keep", "google docs", "yammer", "docs", "office", "paint", "gimp", "photoshop", "lightroom", "tex", "latex", "photo", "foto" }; //not "note" as notepad is more coding private static readonly List InstantMessagingApps = new List { "skype", "lync", "sip", "g2mlauncher", "ciscowebexstart", "nbrplay", "g2mui", "chatter", "atmgr", "hangout", "viber" }; // includes skype for business private static readonly List BrowserApps = new List { "iexplore", "chrome", "firefox", "opera", "safari", "applicationframehost", "edge" }; // ApplicationFrameHost stands for Edge private static readonly List WorkUnrelatedBrowsingKeywords = new List { "gopro", "saldo", "halo", "book", "party", "swag", "birthday", "therapy", "vacation", "wohnung", "flat", "airbnb", "money", "hotel", "mietwagen", "rental", "credit", "hockeybuzz.com", "empatica", "wallpaper", "flight", "travel", "store", "phone", "buy", "engadget", "motorcycle", "car", "auto", "honda", "bmw", "nissan", "subaru", "winter", "summer", "bike", "bicycle", "arcgis", "finance", "portfolio", "toy", "gadget", "geek", "wellness", "health", "saturday", "sunday", "weekend", "sushi", "eat", "dessert", "restaurant", "holiday", "hotel", "cafe", "gas", "deal", "shop", "shopping", "craigslist", "vancouver", "indoor", "club", "loan", "maps", "flower", "florist", "valentine", "zalando", "tripadvisor", "golem", "tilllate", "heise", "jedipedia", "blick", "daydeal.ch", "renovero", "brack.ch", "skyscanner", "easyjet", "booking.com", "meteocheck", "scientific american", "ars technica", "national post", "sensecore", "core pro", "| time", "hockey inside/out", "netflix", "wired", "popular science", "habsrus", "flickr", "imdb", "xkcd", "derStandard.at", "amazon", "nhl.com", "20 minuten", "facebook", "reddit", "twitter", "google+", "news", "aktuell", "9gag", "youtube", "vimeo", "yahoo", "comic", "ebay", "ricardo", "whatsapp", "stream", "movie", "cinema", "kino", "music", "musik", "tumblr" }; private static readonly List WorkRelatedBrowsingWebsites = new List { "batmon", "calculator", "analytics", "azure", "power bi", "business", "php", "proffix", "centmin", "picturex", "ios", "schmelzmetall", "natur- und tierpark goldau", "tierpark", "amazon web service", "cyon", "salesforce.com", "silverlight", "issue", "junit", "mylyn", "jetbrains", "telerik", "testcomplete", "application lifecycle management", "all reports", "advanced search", ".net", "c#", "java", "vbforums", "dashboard", "virtualbox", "document", "dropbox", "onedrive", "proxy", "jenkins", "databasics", "suite", "abb", "shadowbot", "office", "windows", "namespace", "ventyx", "api", "apache", "oracle", "server", "system", "ibm", "code", "codeplex", "retrospection", "stack overflow", "msdn", "developer", "documentation", "blog", "coding", "programmer" }; private static readonly List OtherMusicApps = new List { "groove", "zune", "itunes", "vlc", "music", "musik", "spotify", "wmplayer" }; private static readonly List OtherRdpApps = new List { "mstsc", "vmware", "vpxclient", "msiexec", "pageant", "putty" }; private static readonly List OtherApps = new List { "mmc", "vpnui", "dinotify", "perfmon", "agentransack", "lockapp", "searchui", "pwsafe", "personalanalytics", "wuauclt", "calc", "zip", "googleearth", "rar", "wwahost", "update", "avpui", "procexp64", "taskmgr", "pgp", "explorer", "groove", "dwm", "rstrui", "snippingtool", "onedrive", "settings", "einstellungen" }; #endregion #region Context Mapping Logic /// /// For each (uncategorized) item in the category-list /// map the activity category. /// /// /// public static List Map(List activities) { foreach (var item in activities) { var cat = GetCategory(item.ProcessName, item.WindowTitle); item.Category = cat.Category; item.Coded = cat.Action; } return activities; } /// /// Mapping algorithm, which maps an activity to an activity /// category, based on the heuristcs (defined in the lists above). /// /// /// /// private static CategoryAndAction GetCategory(string processName, string windowName) { try { if (string.IsNullOrEmpty(processName) && string.IsNullOrEmpty(windowName)) return new CategoryAndAction(ActivityCategory.Unknown, CodedType.A); if (windowName != null) windowName = windowName.ToLower(); if (processName != null) processName = processName.ToLower(); // all IDLE, will later manually check with more info to find meetings, breaks, etc. if (processName != null && processName.Equals("idle")) return new CategoryAndAction(ActivityCategory.Idle, CodedType.A); // check with planning keywords if (IsCategory(ActivityCategory.Planning, processName, windowName)) { // this is needed because "task" could be mapped for the "task manager" return IsCategory(ActivityCategory.Other, processName, windowName) ? new CategoryAndAction(ActivityCategory.Other, CodedType.A) : new CategoryAndAction(ActivityCategory.Planning, CodedType.A); } // if not planning, check with email keywords if (IsCategory(ActivityCategory.Email, processName, windowName)) { return new CategoryAndAction(ActivityCategory.Email, CodedType.A); } // if editor, might be reading/writing OR coding (if common coding file type extension or the window // title has not enough information to accurately map by hand, then map to coding category, // else: manual mapping later) if (IsEditor(processName, windowName)) { return (IsCodeFile(windowName)) ? new CategoryAndAction(ActivityCategory.DevCode, CodedType.A) : new CategoryAndAction(ActivityCategory.ManualEditor, CodedType.M); } // check with debugging keywords (manual because of manual checking later) if (IsCategory(ActivityCategory.DevDebug, processName, windowName)) { return new CategoryAndAction(ActivityCategory.DevDebug, CodedType.M); } // check with review keywords (manual because of manual checking later) if (IsCategory(ActivityCategory.DevReview, processName, windowName)) { return new CategoryAndAction(ActivityCategory.DevReview, CodedType.M); } // check with version control keywords (manual because of manual checking later) if (IsCategory(ActivityCategory.DevVc, processName, windowName)) { return new CategoryAndAction(ActivityCategory.DevVc, CodedType.M); } // check with coding keywords (there might be more from editors, manual mapping) if (IsCategory(ActivityCategory.DevCode, processName, windowName)) { return new CategoryAndAction(ActivityCategory.DevCode, CodedType.A); } // check with read/write keywords (there might be more from editors, manual mapping) if (IsCategory(ActivityCategory.ReadWriteDocument, processName, windowName)) { return new CategoryAndAction(ActivityCategory.ReadWriteDocument, CodedType.A); } // NO automated mapping of formal meetings: will look at self-reported tasks & IDLE times // NO automated mapping of in-formal meetings: will look at self-reported tasks & IDLE times // check with instant messaging keywords (subcategory of INFORMAL MEETING) if (IsCategory(ActivityCategory.InstantMessaging, processName, windowName)) { return new CategoryAndAction(ActivityCategory.InstantMessaging, CodedType.A); } // check if its a browser (did not yet fit into other categories // then it's work related / unrelated web browsing which is manually mapped if (IsBrowser(processName)) { // map according to keywords and websites if (IsWebsiteWorkRelated(windowName)) return new CategoryAndAction(ActivityCategory.WorkRelatedBrowsing, CodedType.M); if (IsWebsiteWorkUnrelated(windowName)) return new CategoryAndAction(ActivityCategory.WorkUnrelatedBrowsing, CodedType.M); // map remaining (manually) return new CategoryAndAction(ActivityCategory.WebBrowsing, CodedType.M); } // check with music keywords (subcategory of Other) if (IsCategory(ActivityCategory.OtherMusic, processName, windowName)) { return new CategoryAndAction(ActivityCategory.OtherMusic, CodedType.A); } // check with rdp keywords (subcategory of Other) if (IsCategory(ActivityCategory.OtherRdp, processName, windowName)) { return new CategoryAndAction(ActivityCategory.OtherRdp, CodedType.A); } // check if it's something else (OS related, navigating, etc.) if (IsCategory(ActivityCategory.Other, processName, windowName)) { return new CategoryAndAction(ActivityCategory.Other, CodedType.A); } } catch (Exception e) { Console.WriteLine("> ERROR while Mapping: {0}", e.Message); } return new CategoryAndAction(ActivityCategory.Unknown, CodedType.U); } private static bool IsCategory(ActivityCategory category, string processName, string windowName) { var listToCheck = GetListForCategory(category); if (listToCheck == null) return false; return listToCheck.Any(processName.Contains) || listToCheck.Any(windowName.Contains); } private static bool IsEditor(string processName, string windowName) { return EditorApps.Any(processName.Contains) || EditorApps.Any(windowName.Contains); } private static bool IsBrowser(string processName) { return BrowserApps.Any(processName.Contains); } private static bool IsWebsiteWorkRelated(string windowName) { return WorkRelatedBrowsingWebsites.Any(windowName.Contains); } private static bool IsWebsiteWorkUnrelated(string windowName) { return WorkUnrelatedBrowsingKeywords.Any(windowName.Contains); } private static bool IsCodeFile(string windowName) { return CodeTypeApps.Any(windowName.Contains); } private static bool EditorNotEnoughInfo(string windowName) { return string.IsNullOrEmpty(windowName) || EditorNotEnoughInfoList.Any(windowName.Contains); } private static List GetListForCategory(ActivityCategory cat) { switch (cat) { case ActivityCategory.DevCode: return CodingApps; case ActivityCategory.DevDebug: return CodingDebugApps; case ActivityCategory.DevReview: return CodingReviewApps; case ActivityCategory.DevVc: return CodingVersionControlApps; case ActivityCategory.Email: return EmailApps; case ActivityCategory.Planning: return PlanningApps; case ActivityCategory.ReadWriteDocument: return ReadingWritingApps; case ActivityCategory.InstantMessaging: return InstantMessagingApps; case ActivityCategory.WebBrowsing: return BrowserApps; case ActivityCategory.Other: return OtherApps; case ActivityCategory.OtherMusic: return OtherMusicApps; case ActivityCategory.OtherRdp: return OtherRdpApps; } return null; } #endregion } /// /// A program is mapped into an activity and the CodedType, /// which marks the mappings which need to be manually refined. /// public class CategoryAndAction { public CategoryAndAction(ActivityCategory cat, CodedType type) { Category = cat; Action = type; } public ActivityCategory Category { get; private set; } public CodedType Action { get; private set; } } }