Skip to content

Commit

Permalink
Add comments and update deps
Browse files Browse the repository at this point in the history
  • Loading branch information
KevinX8 committed Mar 1, 2023
1 parent 31b2dc6 commit 7e9bf91
Show file tree
Hide file tree
Showing 12 changed files with 233 additions and 122 deletions.
13 changes: 13 additions & 0 deletions lib/logger/android_logger.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class AndroidLogger extends SentenceLogger {
@override
Future<void> startLogger(TfliteRequest request) async {
await super.startLogger(request);
// Foreground task initialization to keep app running in background
FlutterForegroundTask.init(
androidNotificationOptions: AndroidNotificationOptions(
channelId: 'sentence_logger',
Expand Down Expand Up @@ -52,8 +53,10 @@ class AndroidLogger extends SentenceLogger {
}

Future<void> startAccessibility() async {
// check for notification permission and request if not granted
var statusNotify = await Permission.notification.request();
if (statusNotify.isGranted) {
// Start notification service to keep app running in background
FlutterForegroundTask.startService(
notificationTitle: "Sentiment Tracker",
notificationText: "Analyzing sentence sentiment");
Expand All @@ -65,17 +68,21 @@ class AndroidLogger extends SentenceLogger {
await FlutterAccessibilityService.requestAccessibilityPermission();
}
if (status) {
// if permission granted, start listening to accessibility events
FlutterAccessibilityService.accessStream.listen(_accessibilityListener);
}
}

static void _accessibilityListener(AccessibilityEvent event) {
// if the package is blacklisted, ignore the event
if (AndroidLogger().blacklist.hasMatch(event.packageName!)) {
return;
}
// if the event is a window state change, update the foreground app in use
if (event.eventType == EventType.typeWindowStateChanged) {
event.packageTitle().then((title) {
AndroidLogger().updateFGApp(title!);
// log the app icon if it is not already logged
if (!AndroidLogger().hasAppIcon(title)) {
DeviceApps.getApp(event.packageName!, true).then((app) {
var appWIcon = (app as ApplicationWithIcon?)!;
Expand All @@ -85,8 +92,12 @@ class AndroidLogger extends SentenceLogger {
});
return;
}
// if the event is a text change, update the current sentence being typed
var textNow = event.nodesText![0];
log(textNow);
// if the sentence is a single character (the minimum allowed to be logged
// by android accessibility, and hence when it's most likely a user started
// a new sentence), log the app in use
if (textNow.length == 1) {
AndroidLogger().addAppEntry();
}
Expand All @@ -96,6 +107,8 @@ class AndroidLogger extends SentenceLogger {
}
}

// This extension is used to get the colloquial name of the app that is in use
// instead of the package name for better display in the UI
extension NameConversion on AccessibilityEvent {
Future<String?> packageTitle() async {
if (packageName != null) {
Expand Down
24 changes: 21 additions & 3 deletions lib/logger/logger.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,38 @@ import '../sentiment_analysis.dart';
class AppList {
DateTime lastTimeUsed;
double totalTimeUsed;
int numPositive = 0;
int numNegative = 0;
int numPositive = 0; // number of positive sentences
int numNegative = 0; // number of negative sentences

AppList(this.lastTimeUsed, this.totalTimeUsed, this.numPositive, this.numNegative);
}

class SentenceLogger {
// Singleton
static final SentenceLogger _instance = SentenceLogger.init();
// sentence buffer for the current sentence being typed
static final StringBuffer _sentence = StringBuffer();
static final HashMap<String, AppList> _appMap = HashMap<String, AppList>();
static late final HashSet<String> _appIcons;
static late DriftIsolate _iso;
static late TfParams _tfp;
static const int _updateFreq = 1; //update db every 5 minutes
static const int _updateFreq = 1; //update db every 1 minute
// default app blacklist
RegExp blacklist =
RegExp(r".*system.*|.*keyboard.*|.*input.*|.*honeyboard.*|.*swiftkey.*");
// last app used
String _lastUsedApp = "";
// has the db just been updated? (to prevent race conditions)
bool _dbUpdated = false;

// Singleton
factory SentenceLogger() {
return _instance;
}

SentenceLogger.init();

// Save the current sentiment logs to the database
void logToDB() {
Isolate.spawn(SentimentDB.addSentiments,
AddSentimentRequest(_appMap, _iso.connectPort));
Expand All @@ -57,15 +64,18 @@ class SentenceLogger {
request.sendDriftIsolate.send(driftIsolate);
}

// Entry point for the background isolate
Future<void> startLogger(TfliteRequest request) async {
var rPort = ReceivePort();
// start the drift database in a background isolate
await Isolate.spawn(
_startBackground,
IsolateStartRequest(
sendDriftIsolate: rPort.sendPort, targetPath: request.targetPath),
);

var prefs = request.prefs;
// get custom blacklist from preferences
if (prefs.getString('blacklist') != null) {
blacklist = RegExp(prefs.getString('blacklist')!);
}
Expand All @@ -74,6 +84,7 @@ class SentenceLogger {
_tfp = request.tfp;
var sdb = SentimentDB.connect(await _iso.connect());
_appIcons = await sdb.getListOfIcons();
// close connection to database and send the drift isolate back to the main isolate
sdb.close();
request.sendDriftIsolate.send(_iso.connectPort);
}
Expand Down Expand Up @@ -111,6 +122,9 @@ class SentenceLogger {
}
newHour = true;
}
// used = false if the app was not used in the last 10 minutes,
// but we still want to update the average score as it still affects the
// user's mood
if (used || newHour) {
app.lastTimeUsed = now;
app.totalTimeUsed = totalTimeUsed;
Expand All @@ -119,6 +133,7 @@ class SentenceLogger {

void addAppEntry() async {
log(getSentence());
// if loggable sentence is too short, clear it and return
if (getSentence().length < 6) {
clearSentence();
return;
Expand All @@ -145,6 +160,7 @@ class SentenceLogger {
_appMap.putIfAbsent(name, () => AppList(now, 0, 1, 1));
}

//Update database every _updateFreq minutes
if (now.minute % _updateFreq == 0 && !_dbUpdated) {
logToDB();
_dbUpdated = true;
Expand All @@ -155,6 +171,7 @@ class SentenceLogger {

void updateFGApp(String name) {
DateTime now = DateTime.now();
// ignore apps in blacklist
if (blacklist.hasMatch(name.toLowerCase())) {
return;
}
Expand All @@ -163,6 +180,7 @@ class SentenceLogger {
now.difference(_appMap[name]!.lastTimeUsed).inSeconds.toDouble() / 60;
if (name == _lastUsedApp) {
if (now.hour != _appMap[name]!.lastTimeUsed.hour) {
// if the next hour has been reached reset the time used
if (timeUsedSince / now.minute > 1) {
_appMap[name]!.totalTimeUsed = now.minute.toDouble();
} else {
Expand Down
41 changes: 23 additions & 18 deletions lib/logger/logger_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import 'android_logger.dart';
import 'win_logger.dart';

class LoggerFactory {
// start the logger based on the platform
@pragma('vm:entry-point')
static Future<void> startLoggerFactory(TfliteRequest request) async {
if (Platform.isWindows) {
Expand All @@ -19,6 +20,7 @@ class LoggerFactory {
}
}

// return the default blacklist for the logger based on the platform
static RegExp getLoggerRegex() {
if (Platform.isWindows) {
return RegExp(
Expand All @@ -32,29 +34,32 @@ class LoggerFactory {
}
}

// return the disclosure text for the logger based on the platform as the play store requires
// a disclosure for the accessibility API, which is not available on Windows
static Widget getDisclosureText(BuildContext context) {
if (Platform.isAndroid) {
return SizedBox(
width: double.maxFinite,
height: MediaQuery.of(context).size.height * 0.7,
child:
const Markdown(data: "It is important for you to understand how Negate makes use of your data. Below are the key points on why Negate must use the Accessibility API and what features it uses the Accessibility APi for.\n"
"# Accessibility API\n"
"## Listening for Text Changes\n"
"Negate listens for all text typed into textboxes on your device, features that require this access are:\n"
"* Generation of a positivity score using Artificial Intelligence based on the text received.\n"
"* Usage of the average of these scores over time for:\n"
" * The Hourly Dashboard\n"
" * The Daily Breakdown\n"
" * The Weekly Recommendations\n\n"
"## Listening for Window State Changes\n"
"Negate listens for the current app that is in view for the features of:\n"
"* Seeing which apps have been used in the last 10 minutes that could have affected your mood and hence are factored into the positivity scoring for the current app as well.\n"
"* Getting the amount of time spent in each app and hence how much influence they have had on you in the past hour.\n\n"
"The next page details the app's privacy policy and how all user data is handled."));
height: MediaQuery.of(context).size.height * 0.7,
child: const Markdown(
data:
"It is important for you to understand how Negate makes use of your data. Below are the key points on why Negate must use the Accessibility API and what features it uses the Accessibility APi for.\n"
"# Accessibility API\n"
"## Listening for Text Changes\n"
"Negate listens for all text typed into textboxes on your device, features that require this access are:\n"
"* Generation of a positivity score using Artificial Intelligence based on the text received.\n"
"* Usage of the average of these scores over time for:\n"
" * The Hourly Dashboard\n"
" * The Daily Breakdown\n"
" * The Weekly Recommendations\n\n"
"## Listening for Window State Changes\n"
"Negate listens for the current app that is in view for the features of:\n"
"* Seeing which apps have been used in the last 10 minutes that could have affected your mood and hence are factored into the positivity scoring for the current app as well.\n"
"* Getting the amount of time spent in each app and hence how much influence they have had on you in the past hour.\n\n"
"The next page details the app's privacy policy and how all user data is handled."));
} else {
return SingleChildScrollView(child: ListBody(
children: const [
return SingleChildScrollView(
child: ListBody(children: const [
Text('This app tracks the text of messages '
'and what app is currently in use.'),
Text(
Expand Down
14 changes: 14 additions & 0 deletions lib/logger/win_logger.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class WinLogger extends SentenceLogger {
static final WinLogger _instance = WinLogger.init();
static int _keyHook = 0;
static int _mouseHook = 0;
// used to prevent logging keystrokes when the user is not typing
static DateTime _lastLogged = DateTime.now();

factory WinLogger() {
Expand All @@ -23,6 +24,8 @@ class WinLogger extends SentenceLogger {
Future<void> startLogger(TfliteRequest request) async {
await super.startLogger(request);
_setHook();
// Keeps the app waiting for messages from the OS so that the hooks can be
// called
final msg = calloc<MSG>();
while (GetMessage(msg, NULL, 0, 0) != 0) {
TranslateMessage(msg);
Expand All @@ -31,6 +34,7 @@ class WinLogger extends SentenceLogger {
}

static int _hookCallback(int nCode, int wParam, int lParam) {
// Only log keydown events
if (nCode == HC_ACTION) {
if (wParam == WM_KEYDOWN) {
final kbs = Pointer<KBDLLHOOKSTRUCT>.fromAddress(lParam);
Expand All @@ -41,6 +45,7 @@ class WinLogger extends SentenceLogger {
}

static int _mouseCallback(int nCode, int wParam, int lParam) {
// log the app name and icon of whatever window is clicked on
if (wParam == WM_LBUTTONDOWN) {
var name = WinLogger()._getFGAppName();
WinLogger().updateFGApp(name);
Expand All @@ -64,16 +69,22 @@ class WinLogger extends SentenceLogger {
lowercase = !lowercase;
}

// if the user presses enter, add the sentence to the database
if (keyStroke == 13) {
// if the user presses enter within 10 seconds of the last time they
// pressed any key, add the sentence to the database, otherwise clear
if (_lastLogged.difference(DateTime.now()).inSeconds > 10) {
clearSentence();
} else {
addAppEntry();
}
// if the user presses backspace, remove the last character from the
// sentence
} else if (keyStroke == 8) {
var temp = getSentence().substring(0, getSentence().length - 1);
clearSentence();
writeToSentence(temp);
// if the user presses left shift or right shift do nothing
} else if (keyStroke != 161 && keyStroke != 160) {
var key = String.fromCharCode(keyStroke);
key = !lowercase ? key.toLowerCase() : key;
Expand All @@ -89,6 +100,7 @@ class WinLogger extends SentenceLogger {
Pointer.fromFunction<CallWndProc>(_mouseCallback, 0), NULL, 0);
}

// Gets the name of the app that is currently in focus
String _getFGAppName() {
int nChar = 256;
Pointer<Utf16> sPtr = malloc.allocate<Utf16>(nChar);
Expand All @@ -102,10 +114,12 @@ class WinLogger extends SentenceLogger {
sPtr.toDartString().substring(0, sPtr.toDartString().length - 4));
}

// Formats the name of the app to be more readable
String _formatName(String name) {
return name[0].toUpperCase() + name.toLowerCase().substring(1);
}

// Finds the icon of an app based on the window handle
Uint8List? findAppIcon(int hWnd, {background = 0xffffff, hover = false}) {
var icon =
SendMessage(hWnd, WM_GETICON, 2, 0); // ICON_SMALL2 - User Made Apps
Expand Down
Loading

0 comments on commit 7e9bf91

Please sign in to comment.