Skip to content

Commit

Permalink
Merge pull request #2 from akaMrNagar/dev
Browse files Browse the repository at this point in the history
Fix: Popup dialog not working properly
  • Loading branch information
akaMrNagar authored Dec 16, 2024
2 parents 2f1666b + 1444743 commit cf6f6dd
Show file tree
Hide file tree
Showing 19 changed files with 217 additions and 155 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public static void scheduleBedtimeRoutineTasks(@NonNull Context context, @NonNul
long nowInMs = System.currentTimeMillis();
long alertTimeMs = Utils.todToTodayCal(bedtimeSettings.startTimeInMins - 30).getTimeInMillis();
long startTimeMs = Utils.todToTodayCal(bedtimeSettings.startTimeInMins).getTimeInMillis();
long endTimeMs = (startTimeMs + (bedtimeSettings.totalDurationInMins * 60000L));
long endTimeMs = Utils.todToTodayCal(bedtimeSettings.startTimeInMins + bedtimeSettings.totalDurationInMins).getTimeInMillis();

// Bedtime is already ended then reschedule for the next day
if (endTimeMs < nowInMs) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.util.Calendar;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
* ScreenUsageHelper provides utility methods for gathering and calculating screen usage statistics
Expand All @@ -33,74 +34,92 @@ public class ScreenUsageHelper {
* If the target package is not null then this method will fetch usage for that app only
* otherwise for all device apps.
*
* @param usageStatsManager The UsageStatsManager used to query screen usage data.
* @param start The start time of the interval in milliseconds.
* @param end The end time of the interval in milliseconds.
* @param targetedPackage The package name of the app for fetching its screen usage.
* @param usageStatsManager The UsageStatsManager used to query screen usage data.
* @param start The start time of the interval in milliseconds.
* @param end The end time of the interval in milliseconds.
* @param lastActiveAppPackage The package name of the app which is active last time.
* @return A map with package names as keys and their corresponding screen usage time in seconds as values.
*/
@NonNull
public static HashMap<String, Long> fetchUsageForInterval(@NonNull UsageStatsManager usageStatsManager, long start, long end, @Nullable String targetedPackage) {
HashMap<String, Long> oneDayUsageMap = new HashMap<>();
public static HashMap<String, Long> fetchUsageForInterval(
@NonNull UsageStatsManager usageStatsManager,
long start,
long end,
@Nullable String lastActiveAppPackage
) {
HashMap<String, Long> usageMap = new HashMap<>();
UsageEvents usageEvents = usageStatsManager.queryEvents(start, end);
LinkedHashMap<String, UsageEvents.Event> lastResumedEvents = new LinkedHashMap<>(2);
Map<String, UsageEvents.Event> lastResumedEvents = new HashMap<>();
boolean isFirstEvent = true;


while (usageEvents.hasNextEvent()) {
UsageEvents.Event currentEvent = new UsageEvents.Event(); // Do not move this from while loop
usageEvents.getNextEvent(currentEvent);
int eventType = currentEvent.getEventType();

String packageName = currentEvent.getPackageName();
/// If target package is not null
if (targetedPackage != null && !packageName.equals(targetedPackage)) continue;

String className = currentEvent.getClassName();
String eventKey = packageName + className;

if (eventType == UsageEvents.Event.ACTIVITY_RESUMED) {
lastResumedEvents.put(eventKey, currentEvent);
} else if (eventType == UsageEvents.Event.ACTIVITY_STOPPED || eventType == UsageEvents.Event.ACTIVITY_PAUSED) {
Long screenTime = oneDayUsageMap.getOrDefault(packageName, 0L);
UsageEvents.Event lastResumedEvent = lastResumedEvents.get(eventKey);

if (lastResumedEvent != null
&& lastResumedEvent.getPackageName().equals(packageName)
&& lastResumedEvent.getClassName().equals(className)
) {
// Calculate usage from the last ACTIVITY_RESUMED to this ACTIVITY_PAUSED
screenTime += (currentEvent.getTimeStamp() - lastResumedEvent.getTimeStamp());
lastResumedEvents.remove(eventKey);
} else if (isFirstEvent) {
// Fallback logic in case no matching ACTIVITY_RESUMED was found. May be this app was opened before START time
screenTime += (currentEvent.getTimeStamp() - start);
isFirstEvent = false;
}
oneDayUsageMap.put(packageName, screenTime);
UsageEvents.Event event = new UsageEvents.Event();
usageEvents.getNextEvent(event);

String packageName = event.getPackageName();
String eventKey = packageName + event.getClassName();
long timestamp = event.getTimeStamp();

switch (event.getEventType()) {
case UsageEvents.Event.ACTIVITY_RESUMED:
lastResumedEvents.put(eventKey, event);
break;

case UsageEvents.Event.ACTIVITY_PAUSED:
case UsageEvents.Event.ACTIVITY_STOPPED:
Long usageTime = usageMap.getOrDefault(packageName, 0L);
UsageEvents.Event lastResumedEvent = lastResumedEvents.get(eventKey);

if (lastResumedEvent != null) {
// Normal case: App was resumed within the interval
usageTime += (timestamp - lastResumedEvent.getTimeStamp());
lastResumedEvents.remove(eventKey);
} else if (isFirstEvent) {
// Edge case: App was opened before start but stopped after start
usageTime += (timestamp - start);
isFirstEvent = false;
}
usageMap.put(packageName, usageTime);
break;

default:
break;
}
}

// Handle case where the app is still active
if (lastActiveAppPackage != null) {
lastResumedEvents.values().stream()
.filter(event -> lastActiveAppPackage.equals(event.getPackageName()))
.findFirst()
.ifPresent(event -> {
long usageTime = usageMap.getOrDefault(lastActiveAppPackage, 0L);
usageTime += (end - event.getTimeStamp());
usageMap.put(lastActiveAppPackage, usageTime);
});
}

// Convert milliseconds to seconds
oneDayUsageMap.replaceAll((k, v) -> (v / 1000));
return oneDayUsageMap;
usageMap.replaceAll((key, value) -> value / 1000);
return usageMap;
}

/**
* Fetches the screen usage time of a all installed application for the current day until now using usage events.
*
* @param usageStatsManager The UsageStatsManager used to query screen usage data.
* @param usageStatsManager The UsageStatsManager used to query screen usage data.
* @param lastActiveAppPackage The package name of the app which is active last time.
* @return The total screen usage time of the specified application in seconds.
*/
@NonNull
public static HashMap<String, Long> fetchAppUsageTodayTillNow(@NonNull UsageStatsManager usageStatsManager) {
public static HashMap<String, Long> fetchAppUsageTodayTillNow(@NonNull UsageStatsManager usageStatsManager, @Nullable String lastActiveAppPackage) {
Calendar midNightCal = Calendar.getInstance();
midNightCal.set(Calendar.HOUR_OF_DAY, 0);
midNightCal.set(Calendar.MINUTE, 0);
midNightCal.set(Calendar.SECOND, 0);

long start = midNightCal.getTimeInMillis();
long end = System.currentTimeMillis();
return fetchUsageForInterval(usageStatsManager, start, end, null);
return fetchUsageForInterval(usageStatsManager, start, end, lastActiveAppPackage);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -250,8 +250,8 @@ private void onNewAppLaunched(String packageName) {
/// Return if no restriction applied
AppRestrictions appRestrictions = mAppsRestrictions.get(packageName);
if (appRestrictions == null) return;

PurgedReason timerReason = null;
boolean isActivePeriodTimer = false;
long timerDelayMS = Long.MAX_VALUE;

/// Check for app launch limit
Expand All @@ -265,26 +265,26 @@ private void onNewAppLaunched(String packageName) {

/// Check for app's active period
if (appRestrictions.periodDurationInMins > 0) {
PurgedReason reason = new PurgedReason(getString(R.string.app_paused_dialog_info_for_active_period_over));
int periodEndTimeMinutes = appRestrictions.activePeriodStart + appRestrictions.periodDurationInMins;

/// Outside active period
if (Utils.isTimeOutsideTODs(appRestrictions.activePeriodStart, appRestrictions.activePeriodEnd)) {
if (Utils.isTimeOutsideTODs(appRestrictions.activePeriodStart, periodEndTimeMinutes)) {
Log.d(TAG, "onNewAppLaunched: App's active period is over");
PurgedReason reason = new PurgedReason(getString(R.string.app_paused_dialog_info_for_active_period_over));
showOverlayDialog(packageName, reason);
return;
}

/// Between active period so update recall delay
long willOverInMs = Utils.todDifferenceFromNow(appRestrictions.activePeriodEnd);
/// Launched between active period so set timer for period ending
long willOverInMs = Utils.todDifferenceFromNow(periodEndTimeMinutes);
if (willOverInMs < timerDelayMS) {
timerReason = reason;
isActivePeriodTimer = true;
timerReason = null;
timerDelayMS = willOverInMs;
}
}

/// Fetch usage for all apps
HashMap<String, Long> allAppsScreenUsage = ScreenUsageHelper.fetchAppUsageTodayTillNow(mUsageStatsManager);
HashMap<String, Long> allAppsScreenUsage = ScreenUsageHelper.fetchAppUsageTodayTillNow(mUsageStatsManager, packageName);

/// Check for app timer
if (appRestrictions.timerSec > 0) {
Expand All @@ -303,7 +303,6 @@ private void onNewAppLaunched(String packageName) {
long leftAppLimitMs = (appRestrictions.timerSec - appScreenTimeSec) * 1000;
if (leftAppLimitMs < timerDelayMS) {
timerReason = new PurgedReason(getString(R.string.app_paused_dialog_info_for_app_timer_left), appRestrictions.timerSec, appScreenTimeSec);
isActivePeriodTimer = false;
timerDelayMS = leftAppLimitMs;
}
}
Expand All @@ -315,29 +314,28 @@ private void onNewAppLaunched(String packageName) {

/// Check for group's active period
if (associatedGroup.periodDurationInMins > 0) {
PurgedReason reason = new PurgedReason(getString(R.string.group_paused_dialog_info_for_active_period_over, associatedGroup.groupName));
int periodEndTimeMinutes = associatedGroup.activePeriodStart + associatedGroup.periodDurationInMins;

/// Outside active period
if (Utils.isTimeOutsideTODs(associatedGroup.activePeriodStart, associatedGroup.activePeriodEnd)) {
if (Utils.isTimeOutsideTODs(associatedGroup.activePeriodStart, periodEndTimeMinutes)) {
Log.d(TAG, "onNewAppLaunched: App's associated group's active period is over");
PurgedReason reason = new PurgedReason(getString(R.string.group_paused_dialog_info_for_active_period_over, associatedGroup.groupName));
showOverlayDialog(packageName, reason);
return;
}

/// Between active period so update recall delay
long willOverInMs = Utils.todDifferenceFromNow(associatedGroup.activePeriodEnd);
/// Launched between active period so set timer for period ending
long willOverInMs = Utils.todDifferenceFromNow(periodEndTimeMinutes);
if (willOverInMs < timerDelayMS) {
timerReason = reason;
isActivePeriodTimer = true;
timerReason = null;
timerDelayMS = willOverInMs;
}
}


/// Check for associated group's timer
if (associatedGroup.timerSec > 0) {
long groupScreenTimeSec = associatedGroup.distractingApps.stream()
.mapToLong(app -> ScreenUsageHelper.fetchAppUsageTodayTillNow(mUsageStatsManager).getOrDefault(app, 0L))
.sum();
long groupScreenTimeSec = associatedGroup.distractingApps.stream().mapToLong(app -> allAppsScreenUsage.getOrDefault(app, 0L)).sum();

/// Group timer ran out
if (groupScreenTimeSec >= associatedGroup.timerSec) {
Expand All @@ -352,7 +350,6 @@ private void onNewAppLaunched(String packageName) {
long leftGroupLimitMs = (associatedGroup.timerSec - groupScreenTimeSec) * 1000;
if (leftGroupLimitMs < timerDelayMS) {
timerReason = new PurgedReason(getString(R.string.app_paused_dialog_info_for_group_timer_left, associatedGroup.groupName), associatedGroup.timerSec, groupScreenTimeSec);
isActivePeriodTimer = false;
timerDelayMS = leftGroupLimitMs;
}
}
Expand All @@ -368,7 +365,6 @@ private void onNewAppLaunched(String packageName) {
timerReason,
appRestrictions.alertInterval,
appRestrictions.alertByDialog,
isActivePeriodTimer,
timerDelayMS
);
}
Expand Down Expand Up @@ -400,19 +396,17 @@ private boolean isAppAlreadyPurged(String packageName) {
* Schedules a countdown timer to alert the user of remaining time for a specific app.
* Provides notifications or overlay dialogs based on specified alert intervals and thresholds.
*
* @param packageName The package name of the app.
* @param reason The reason for which to schedule timer.
* @param alertIntervalSec The interval at which alerts should occur in SECONDS.
* @param alertByDialog True if alerts should be shown as overlay dialogs, otherwise as notifications.
* @param isForActivePeriod Does the timer scheduling is for app's or group's active period or not.
* @param millisInFuture The time in Ms in future till the countdown timer will run.
* @param packageName The package name of the app.
* @param unfinishedReason The unfinishedReason for which to schedule timer.
* @param alertIntervalSec The interval at which alerts should occur in SECONDS.
* @param alertByDialog True if alerts should be shown as overlay dialogs, otherwise as notifications.
* @param millisInFuture The time in Ms in future till the countdown timer will run.
*/
private void scheduleUsageAlertCountDownTimer(
String packageName,
PurgedReason reason,
@Nullable PurgedReason unfinishedReason,
int alertIntervalSec,
boolean alertByDialog,
boolean isForActivePeriod,
long millisInFuture
) {
cancelTimers();
Expand All @@ -431,8 +425,8 @@ public void onTick(long millisUntilFinished) {

// Trigger alert if remaining time in minutes matches any alert time
if (alertMinuteTicks.contains(minutesRemaining)) {
if (alertByDialog && !isForActivePeriod) {
showOverlayDialog(packageName, reason);
if (unfinishedReason != null && alertByDialog) {
showOverlayDialog(packageName, unfinishedReason);
} else {
pushUsageAlertNotification(packageName, minutesRemaining);
}
Expand All @@ -443,10 +437,8 @@ public void onTick(long millisUntilFinished) {

@Override
public void onFinish() {
onNewAppLaunched(packageName);
if (!isForActivePeriod) mPurgedApps.put(packageName, reason);
showOverlayDialog(packageName, reason);
Log.d(TAG, "scheduleUsageAlertCountDownTimer: Countdown finished for package: " + packageName);
onNewAppLaunched(packageName);
}
};
Log.d(TAG, "scheduleUsageAlertCountDownTimer: Timer scheduled for " + packageName + " ending at: " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ public static Calendar todToTodayCal(int totalMinutes) {
* Calculated the difference between time now and future tod minutes.
*
* @param futureTotalMinutes The total minutes from Time Of Day dart object.
* @return The different in MS. If the difference is negative then return 0
* @return The difference in MS. If the difference is negative then return 0
*/
public static long todDifferenceFromNow(int futureTotalMinutes) {
long diff = todToTodayCal(futureTotalMinutes).getTimeInMillis() - System.currentTimeMillis();
Expand Down
2 changes: 1 addition & 1 deletion android/app/src/main/res/values-ja/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<resources>
<string name="app_name" comment="app_name app values">Mindful</string>
<string name="accessibility_description">Mindful アプリは、ユーザー補助を使用して、ウェブサイトやアプリのショート動画をブロックします。ブロックリストに登録されたURLやコンテンツへのアクセスを制限することで、集中力を維持するのに役立ちます。 \n\n⚠️注意:Mindful はプライバシーを最優先に考えています。100%安全でオフラインで動作します。個人データの収集や保存は一切行いません。 </string>
<string name="admin_description">Mindful は、ユーザーのデータを収集、保存、または送信することはありません。無料でオープンソースのソフトウェア(FOSS)なので、ソースコードを自由に確認・変更できます。管理者権限は、アプリの正常な動作に必要なシステム操作にのみ使用され、プライバシーは完全に保護されます。</string>
<string name="admin_description">Mindfulはユーザーデータを収集、保存、送信しません。管理者権限は、重要なシステム操作にのみ必要であり、プライバシーを侵害することなくアプリが正しく機能することを保証します。</string>
<string name="toast_enable_notification">通知を許可する</string>
<string name="toast_blocked_content">コンテンツをブロックしました。前の画面に戻ります。</string>
<string name="toast_redirecting">検索結果を絞り込む</string>
Expand Down
Loading

0 comments on commit cf6f6dd

Please sign in to comment.