Skip to content

Commit

Permalink
Merge pull request #142 from akaMrNagar/dev
Browse files Browse the repository at this point in the history
Fix: Active period not working properly and Optimized app usage loading
  • Loading branch information
akaMrNagar authored Dec 12, 2024
2 parents b57d83c + b63d1ad commit ef8d557
Show file tree
Hide file tree
Showing 13 changed files with 111 additions and 178 deletions.
14 changes: 5 additions & 9 deletions android/app/src/main/java/com/mindful/android/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -136,21 +136,17 @@ public void onMethodCall(@NonNull MethodCall call, @NonNull MethodChannel.Result
break;
}
case "getDeviceApps": {
DeviceAppsHelper.getDeviceApps(this, result);
DeviceAppsHelper.getDeviceApps(
this,
result,
mTrackerServiceConn.isConnected() ? mTrackerServiceConn.getService().getAppsLaunchCount() : new HashMap<>(0)
);
break;
}
case "getShortsScreenTimeMs": {
result.success(SharedPrefsHelper.getSetShortsScreenTimeMs(this, null));
break;
}
case "getAppLaunchCounts": {
result.success(
mTrackerServiceConn.isConnected() ?
mTrackerServiceConn.getService().getAppsLaunchCount() :
new HashMap<String, Integer>(0)
);
break;
}
case "setDataResetTime": {
SharedPrefsHelper.getSetDataResetTimeMins(this, call.arguments() == null ? 0 : call.arguments());
result.success(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;

Expand Down Expand Up @@ -57,7 +56,11 @@ public class DeviceAppsHelper {
* @param context The context to use for fetching app information.
* @param channelResult The result channel to which the app data will be sent.
*/
public static void getDeviceApps(Context context, MethodChannel.Result channelResult) {
public static void getDeviceApps(
Context context,
MethodChannel.Result channelResult,
HashMap<String, Integer> appsLaunchCountMap
) {
SuccessCallback<List<Map<String, Object>>> callback = new SuccessCallback<List<Map<String, Object>>>() {
@Override
public void onSuccess(List<Map<String, Object>> result) {
Expand All @@ -68,7 +71,7 @@ public void onSuccess(List<Map<String, Object>> result) {
new Thread(new Runnable() {
@Override
public void run() {
List<AndroidApp> apps = fetchAppsAndUsage(context);
List<AndroidApp> apps = fetchAppsAndUsage(context, appsLaunchCountMap);
List<Map<String, Object>> resultMap = new ArrayList<>(apps.size());

for (AndroidApp app : apps) {
Expand All @@ -81,7 +84,7 @@ public void run() {
}

@NonNull
private static List<AndroidApp> fetchAppsAndUsage(@NonNull Context context) {
private static List<AndroidApp> fetchAppsAndUsage(@NonNull Context context, @NonNull HashMap<String, Integer> appsLaunchCountMap) {
PackageManager packageManager = context.getPackageManager();

// Fetch set of important apps like Dialer, Launcher etc.
Expand All @@ -95,12 +98,6 @@ private static List<AndroidApp> fetchAppsAndUsage(@NonNull Context context) {
for (PackageInfo app : fetchedApps) {
// Only include apps which are launchable
if (packageManager.getLaunchIntentForPackage(app.packageName) != null) {

int category = -1;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
category = app.applicationInfo.category;
}

// Check if the app is important or default to system like dialer and launcher
boolean isSysDefault = impSystemApps.contains(app.packageName);
deviceApps.add(
Expand All @@ -109,7 +106,7 @@ private static List<AndroidApp> fetchAppsAndUsage(@NonNull Context context) {
app.packageName, // package name
Utils.getEncodedAppIcon(packageManager.getApplicationIcon(app.applicationInfo)), // icon
isSysDefault, // is default app used by system like dialer or launcher
category, // category
appsLaunchCountMap.getOrDefault(app.packageName, 0), // launch count for today
app.applicationInfo.uid // app uid
)
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.LinkedHashMap;

/**
* ScreenUsageHelper provides utility methods for gathering and calculating screen usage statistics
Expand All @@ -43,7 +43,7 @@ public class ScreenUsageHelper {
public static HashMap<String, Long> fetchUsageForInterval(@NonNull UsageStatsManager usageStatsManager, long start, long end, @Nullable String targetedPackage) {
HashMap<String, Long> oneDayUsageMap = new HashMap<>();
UsageEvents usageEvents = usageStatsManager.queryEvents(start, end);
Map<String, UsageEvents.Event> lastResumedEvents = new HashMap<>();
LinkedHashMap<String, UsageEvents.Event> lastResumedEvents = new LinkedHashMap<>(2);
boolean isFirstEvent = true;


Expand Down Expand Up @@ -86,27 +86,6 @@ public static HashMap<String, Long> fetchUsageForInterval(@NonNull UsageStatsMan
return oneDayUsageMap;
}

/**
* Fetches the screen usage time of a specific application for the current day until now using usage events.
*
* @param usageStatsManager The UsageStatsManager used to query screen usage data.
* @param packageName The package name of the application whose usage time is to be fetched.
* @return The total screen usage time of the specified application in seconds.
*/
public static int fetchAppUsageTodayTillNow(@NonNull UsageStatsManager usageStatsManager, String packageName) {
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();
long screenTime = fetchUsageForInterval(usageStatsManager, start, end, packageName).getOrDefault(packageName, 0L);

// Log.d("Time", "fetchAppUsageFromEvents: package: " + packageName + " screen time seconds : " + screenTime);
return (int) screenTime;
}

/**
* Fetches the screen usage time of a all installed application for the current day until now using usage events.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public class AndroidApp {
public String packageName;
public int appUid;
public String appIcon;
public int category;
public int launchCount;
public boolean isImpSysApp;

// Usage info
Expand All @@ -45,15 +45,15 @@ public class AndroidApp {
* @param packageName The package name of the application.
* @param appIcon The icon of the application.
* @param isImpSysApp Indicates if the application is an important system app.
* @param category The category of the application.
* @param launchCount The launch count of the application.
* @param appUid The UID of the application.
*/
public AndroidApp(String appName, String packageName, String appIcon, boolean isImpSysApp, int category, int appUid) {
public AndroidApp(String appName, String packageName, String appIcon, boolean isImpSysApp, int launchCount, int appUid) {
this.appName = appName;
this.packageName = packageName;
this.appIcon = appIcon;
this.isImpSysApp = isImpSysApp;
this.category = category;
this.launchCount = launchCount;
this.appUid = appUid;
// Initialize usage arrays with 7 days worth of data, defaulting to 0
this.screenTimeThisWeek = new ArrayList<>(Collections.nCopies(7, 0L));
Expand Down Expand Up @@ -85,7 +85,7 @@ public Map<String, Object> toMap() {
appMap.put("packageName", packageName);
appMap.put("appIcon", appIcon);
appMap.put("isImpSysApp", isImpSysApp);
appMap.put("category", category);
appMap.put("launchCount", launchCount);
appMap.put("screenTimeThisWeek", screenTimeThisWeek);
appMap.put("mobileUsageThisWeek", mobileUsageThisWeek);
appMap.put("wifiUsageThisWeek", wifiUsageThisWeek);
Expand All @@ -106,7 +106,7 @@ public String toString() {
", packageName='" + packageName + '\'' +
", appUid=" + appUid +
", isImpSysApp=" + isImpSysApp +
", category=" + category +
", launchCount=" + launchCount +
", screenTimeThisWeek=" + screenTimeThisWeek +
", mobileUsageThisWeek=" + mobileUsageThisWeek +
", wifiUsageThisWeek=" + wifiUsageThisWeek +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,12 +250,13 @@ private void onNewAppLaunched(String packageName) {
/// Return if no restriction applied
AppRestrictions appRestrictions = mAppsRestrictions.get(packageName);
if (appRestrictions == null) return;
long recallDelayMS = Long.MAX_VALUE;
PurgedReason recallReason = null;
boolean isPeriodRecall = false;
PurgedReason timerReason = null;
boolean isActivePeriodTimer = false;
long timerDelayMS = Long.MAX_VALUE;

/// Check for app launch limit
if (appRestrictions.launchLimit > 0 && launchCount > appRestrictions.launchLimit) {
Log.d(TAG, "onNewAppLaunched: App's launch limit ran out");
PurgedReason reason = new PurgedReason(getString(R.string.app_paused_dialog_info_for_launch_count_out));
mPurgedApps.put(packageName, reason);
showOverlayDialog(packageName, reason);
Expand All @@ -268,17 +269,17 @@ private void onNewAppLaunched(String packageName) {

/// Outside active period
if (Utils.isTimeOutsideTODs(appRestrictions.activePeriodStart, appRestrictions.activePeriodEnd)) {
mPurgedApps.put(packageName, reason);
Log.d(TAG, "onNewAppLaunched: App's active period is over");
showOverlayDialog(packageName, reason);
return;
}

/// Between active period so update recall delay
long willOverInMs = Utils.todDifferenceFromNow(appRestrictions.activePeriodEnd);
if (willOverInMs < recallDelayMS) {
recallDelayMS = willOverInMs;
isPeriodRecall = true;
recallReason = reason;
if (willOverInMs < timerDelayMS) {
timerReason = reason;
isActivePeriodTimer = true;
timerDelayMS = willOverInMs;
}
}

Expand All @@ -291,6 +292,7 @@ private void onNewAppLaunched(String packageName) {

/// App timer ran out
if (appScreenTimeSec >= appRestrictions.timerSec) {
Log.d(TAG, "onNewAppLaunched: App's timer is over");
PurgedReason reason = new PurgedReason(getString(R.string.app_paused_dialog_info_for_app_timer_out), appRestrictions.timerSec, appScreenTimeSec);
mPurgedApps.put(packageName, reason);
showOverlayDialog(packageName, reason);
Expand All @@ -299,10 +301,10 @@ private void onNewAppLaunched(String packageName) {

/// App timer left so update recall delay
long leftAppLimitMs = (appRestrictions.timerSec - appScreenTimeSec) * 1000;
if (leftAppLimitMs < recallDelayMS) {
recallDelayMS = leftAppLimitMs;
recallReason = new PurgedReason(getString(R.string.app_paused_dialog_info_for_app_timer_left), appRestrictions.timerSec, appScreenTimeSec);
isPeriodRecall = false;
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 @@ -316,17 +318,17 @@ private void onNewAppLaunched(String packageName) {
PurgedReason reason = new PurgedReason(getString(R.string.group_paused_dialog_info_for_active_period_over, associatedGroup.groupName));
/// Outside active period
if (Utils.isTimeOutsideTODs(associatedGroup.activePeriodStart, associatedGroup.activePeriodEnd)) {
mPurgedApps.put(packageName, reason);
Log.d(TAG, "onNewAppLaunched: App's associated group's active period is over");
showOverlayDialog(packageName, reason);
return;
}

/// Between active period so update recall delay
long willOverInMs = Utils.todDifferenceFromNow(associatedGroup.activePeriodEnd);
if (willOverInMs < recallDelayMS) {
recallDelayMS = willOverInMs;
isPeriodRecall = true;
recallReason = reason;
if (willOverInMs < timerDelayMS) {
timerReason = reason;
isActivePeriodTimer = true;
timerDelayMS = willOverInMs;
}
}

Expand All @@ -339,6 +341,7 @@ private void onNewAppLaunched(String packageName) {

/// Group timer ran out
if (groupScreenTimeSec >= associatedGroup.timerSec) {
Log.d(TAG, "onNewAppLaunched: App's associated group's timer is over");
PurgedReason reason = new PurgedReason(getString(R.string.app_paused_dialog_info_for_group_timer_out, associatedGroup.groupName), associatedGroup.timerSec, groupScreenTimeSec);
mPurgedApps.put(packageName, reason);
showOverlayDialog(packageName, reason);
Expand All @@ -347,25 +350,26 @@ private void onNewAppLaunched(String packageName) {

/// Group timer left so update recall delay
long leftGroupLimitMs = (associatedGroup.timerSec - groupScreenTimeSec) * 1000;
if (leftGroupLimitMs < recallDelayMS) {
recallDelayMS = leftGroupLimitMs;
recallReason = new PurgedReason(getString(R.string.app_paused_dialog_info_for_group_timer_left, associatedGroup.groupName), associatedGroup.timerSec, groupScreenTimeSec);
isPeriodRecall = false;
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;
}
}
}


// Return if delay doesn't changed
if (recallDelayMS == Long.MAX_VALUE) return;
if (timerDelayMS == Long.MAX_VALUE) return;

// schedule timer for lowest time to recall for usage recheck
scheduleUsageAlertCountDownTimer(
packageName,
recallReason,
timerReason,
appRestrictions.alertInterval,
appRestrictions.alertByDialog && !isPeriodRecall,
recallDelayMS
appRestrictions.alertByDialog,
isActivePeriodTimer,
timerDelayMS
);
}

Expand Down Expand Up @@ -396,36 +400,38 @@ 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 alertByDialog True if alerts should be shown as overlay dialogs, otherwise as notifications.
* @param alertIntervalSec The interval at which alerts should occur in SECONDS.
* @param millisInFuture The time in Ms in future till the countdown timer will run.
* @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.
*/
private void scheduleUsageAlertCountDownTimer(
String packageName,
PurgedReason reason,
int alertIntervalSec,
boolean alertByDialog,
boolean isForActivePeriod,
long millisInFuture
) {
cancelTimers();
final Set<Integer> alertMinuteTicks = getAlertTickFromDuration(millisInFuture, alertIntervalSec);

// Schedule the countdown timer on the main thread
// This method is called on a background thread so we have to schedule the timer from the main thread.
// We can't schedule timer(background task) from another background thread. It will throw exception
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
// Ticks every minute
mOngoingAppTimer = new CountDownTimer(millisInFuture, 60 * 1000) {
// Ticks every minute
@Override
public void onTick(long millisUntilFinished) {
// Convert to minutes
int minutesRemaining = (int) (millisUntilFinished / 60000);

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

@Override
public void onFinish() {
mPurgedApps.put(packageName, reason);
onNewAppLaunched(packageName);
if (!isForActivePeriod) mPurgedApps.put(packageName, reason);
showOverlayDialog(packageName, reason);
Log.d(TAG, "scheduleUsageAlertCountDownTimer: Countdown finished for package: " + packageName);
}
};
mOngoingAppTimer.start();
Log.d(TAG, "scheduleUsageAlertCountDownTimer: Timer scheduled for " + packageName + " ending at: " +
new Date(millisInFuture + System.currentTimeMillis()));
mOngoingAppTimer.start();
}
});
}
Expand Down
Loading

0 comments on commit ef8d557

Please sign in to comment.