Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a0ee7c35 authored by Amith Yamasani's avatar Amith Yamasani Committed by Android (Google) Code Review
Browse files

Merge "Track last job run time"

parents aacb53a3 53f06eae
Loading
Loading
Loading
Loading
+48 −16
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package android.app.usage;

import android.annotation.UserIdInt;
import android.app.usage.UsageStatsManager.StandbyBuckets;
import android.content.ComponentName;
import android.content.res.Configuration;
@@ -37,7 +38,7 @@ public abstract class UsageStatsManagerInternal {
     * @param eventType The event that occurred. Valid values can be found at
     * {@link UsageEvents}
     */
    public abstract void reportEvent(ComponentName component, int userId, int eventType);
    public abstract void reportEvent(ComponentName component, @UserIdInt int userId, int eventType);

    /**
     * Reports an event to the UsageStatsManager.
@@ -47,14 +48,14 @@ public abstract class UsageStatsManagerInternal {
     * @param eventType The event that occurred. Valid values can be found at
     * {@link UsageEvents}
     */
    public abstract void reportEvent(String packageName, int userId, int eventType);
    public abstract void reportEvent(String packageName, @UserIdInt int userId, int eventType);

    /**
     * Reports a configuration change to the UsageStatsManager.
     *
     * @param config The new device configuration.
     */
    public abstract void reportConfigurationChange(Configuration config, int userId);
    public abstract void reportConfigurationChange(Configuration config, @UserIdInt int userId);

    /**
     * Reports that an action equivalent to a ShortcutInfo is taken by the user.
@@ -65,7 +66,8 @@ public abstract class UsageStatsManagerInternal {
     *
     * @see android.content.pm.ShortcutManager#reportShortcutUsed(String)
     */
    public abstract void reportShortcutUsage(String packageName, String shortcutId, int userId);
    public abstract void reportShortcutUsage(String packageName, String shortcutId,
            @UserIdInt int userId);

    /**
     * Reports that a content provider has been accessed by a foreground app.
@@ -73,7 +75,8 @@ public abstract class UsageStatsManagerInternal {
     * @param pkgName The package name of the content provider
     * @param userId The user in which the content provider was accessed.
     */
    public abstract void reportContentProviderUsage(String name, String pkgName, int userId);
    public abstract void reportContentProviderUsage(String name, String pkgName,
            @UserIdInt int userId);

    /**
     * Prepares the UsageStatsService for shutdown.
@@ -89,7 +92,7 @@ public abstract class UsageStatsManagerInternal {
     * @param userId
     * @return
     */
    public abstract boolean isAppIdle(String packageName, int uidForAppId, int userId);
    public abstract boolean isAppIdle(String packageName, int uidForAppId, @UserIdInt int userId);

    /**
     * Returns the app standby bucket that the app is currently in.  This accessor does
@@ -101,15 +104,15 @@ public abstract class UsageStatsManagerInternal {
     * @return the AppStandby bucket code the app currently resides in.  If the app is
     *     unknown in the given user, STANDBY_BUCKET_NEVER is returned.
     */
    @StandbyBuckets public abstract int getAppStandbyBucket(String packageName, int userId,
            long nowElapsed);
    @StandbyBuckets public abstract int getAppStandbyBucket(String packageName,
            @UserIdInt int userId, long nowElapsed);

    /**
     * Returns all of the uids for a given user where all packages associating with that uid
     * are in the app idle state -- there are no associated apps that are not idle.  This means
     * all of the returned uids can be safely considered app idle.
     */
    public abstract int[] getIdleUidsForUser(int userId);
    public abstract int[] getIdleUidsForUser(@UserIdInt int userId);

    /**
     * @return True if currently app idle parole mode is on.  This means all idle apps are allow to
@@ -134,8 +137,8 @@ public abstract class UsageStatsManagerInternal {
    public static abstract class AppIdleStateChangeListener {

        /** Callback to inform listeners that the idle state has changed to a new bucket. */
        public abstract void onAppIdleStateChanged(String packageName, int userId, boolean idle,
                                                   int bucket);
        public abstract void onAppIdleStateChanged(String packageName, @UserIdInt int userId,
                boolean idle, int bucket);

        /**
         * Callback to inform listeners that the parole state has changed. This means apps are
@@ -144,10 +147,16 @@ public abstract class UsageStatsManagerInternal {
        public abstract void onParoleStateChanged(boolean isParoleOn);
    }

    /*  Backup/Restore API */
    public abstract byte[] getBackupPayload(int user, String key);
    /**  Backup/Restore API */
    public abstract byte[] getBackupPayload(@UserIdInt int userId, String key);

    public abstract void applyRestoredPayload(int user, String key, byte[] payload);
    /**
     * ?
     * @param userId
     * @param key
     * @param payload
     */
    public abstract void applyRestoredPayload(@UserIdInt int userId, String key, byte[] payload);

    /**
     * Return usage stats.
@@ -155,6 +164,29 @@ public abstract class UsageStatsManagerInternal {
     * @param obfuscateInstantApps whether instant app package names need to be obfuscated in the
     *     result.
     */
    public abstract List<UsageStats> queryUsageStatsForUser(
            int userId, int interval, long beginTime, long endTime, boolean obfuscateInstantApps);
    public abstract List<UsageStats> queryUsageStatsForUser(@UserIdInt int userId, int interval,
            long beginTime, long endTime, boolean obfuscateInstantApps);

    /**
     * Used to persist the last time a job was run for this app, in order to make decisions later
     * whether a job should be deferred until later. The time passed in should be in elapsed
     * realtime since boot.
     * @param packageName the app that executed a job.
     * @param userId the user associated with the job.
     * @param elapsedRealtime the time when the job was executed, in elapsed realtime millis since
     *                        boot.
     */
    public abstract void setLastJobRunTime(String packageName, @UserIdInt int userId,
            long elapsedRealtime);

    /**
     * Returns the time in millis since a job was executed for this app, in elapsed realtime
     * timebase. This value can be larger than the current elapsed realtime if the job was executed
     * before the device was rebooted. The default value is {@link Long#MAX_VALUE}.
     * @param packageName the app you're asking about.
     * @param userId the user associated with the job.
     * @return the time in millis since a job was last executed for the app, provided it was
     * indicated here before by a call to {@link #setLastJobRunTime(String, int, long)}.
     */
    public abstract long getTimeSinceLastJobRun(String packageName, @UserIdInt int userId);
}
+16 −0
Original line number Diff line number Diff line
@@ -21,6 +21,10 @@ import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import android.app.usage.UsageStatsManager;
import android.os.FileUtils;
import android.test.AndroidTestCase;
@@ -117,4 +121,16 @@ public class AppIdleHistoryTests extends AndroidTestCase {
        assertFalse(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_RARE));
        assertTrue(aih.shouldInformListeners(PACKAGE_1, USER_ID, 5000, STANDBY_BUCKET_FREQUENT));
    }

    public void testJobRunTime() throws Exception {
        AppIdleHistory aih = new AppIdleHistory(mStorageDir, 1000);

        aih.setLastJobRunTime(PACKAGE_1, USER_ID, 2000);
        assertEquals(Long.MAX_VALUE, aih.getTimeSinceLastJobRun(PACKAGE_2, USER_ID, 0));
        assertEquals(4000, aih.getTimeSinceLastJobRun(PACKAGE_1, USER_ID, 6000));

        aih.setLastJobRunTime(PACKAGE_2, USER_ID, 6000);
        assertEquals(1000, aih.getTimeSinceLastJobRun(PACKAGE_2, USER_ID, 7000));
        assertEquals(5000, aih.getTimeSinceLastJobRun(PACKAGE_1, USER_ID, 7000));
    }
}
 No newline at end of file
+31 −0
Original line number Diff line number Diff line
@@ -51,6 +51,8 @@ import android.hardware.display.DisplayManager;
import android.os.Handler;
import android.os.Looper;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.view.Display;
@@ -69,6 +71,8 @@ import java.util.List;
 * Unit test for AppStandbyController.
 */
@RunWith(AndroidJUnit4.class)
@Presubmit
@SmallTest
public class AppStandbyControllerTests {

    private static final String PACKAGE_1 = "com.example.foo";
@@ -285,6 +289,10 @@ public class AppStandbyControllerTests {
                true);
    }

    private void assertBucket(int bucket) {
        assertEquals(bucket, getStandbyBucket(mController));
    }

    @Test
    public void testBuckets() throws Exception {
        assertTimeout(mController, 0, STANDBY_BUCKET_NEVER);
@@ -425,4 +433,27 @@ public class AppStandbyControllerTests {
                REASON_PREDICTED, 2 * HOUR_MS);
        assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController));
    }

    @Test
    public void testTimeout() throws Exception {
        setChargingState(mController, false);

        reportEvent(mController, USER_INTERACTION, 0);
        assertBucket(STANDBY_BUCKET_ACTIVE);

        mInjector.mElapsedRealtime = 2000;
        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
                REASON_PREDICTED, mInjector.mElapsedRealtime);
        assertBucket(STANDBY_BUCKET_ACTIVE);

        // bucketing works after timeout
        mInjector.mElapsedRealtime = FREQUENT_THRESHOLD - 100;
        mController.checkIdleStates(USER_ID);
        assertBucket(STANDBY_BUCKET_WORKING_SET);

        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
                REASON_PREDICTED, mInjector.mElapsedRealtime);
        assertBucket(STANDBY_BUCKET_FREQUENT);

    }
}
+94 −92
Original line number Diff line number Diff line
@@ -23,7 +23,6 @@ import static android.app.usage.UsageStatsManager.REASON_USAGE;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;

import android.app.usage.UsageStatsManager;
import android.os.SystemClock;
@@ -66,13 +65,7 @@ public class AppIdleHistory {

    // History for all users and all packages
    private SparseArray<ArrayMap<String,AppUsageHistory>> mIdleHistory = new SparseArray<>();
    private long mLastPeriod = 0;
    private static final long ONE_MINUTE = 60 * 1000;
    private static final int HISTORY_SIZE = 100;
    private static final int FLAG_LAST_STATE = 2;
    private static final int FLAG_PARTIAL_ACTIVE = 1;
    private static final long PERIOD_DURATION = UsageStatsService.COMPRESS_TIME ? ONE_MINUTE
            : 60 * ONE_MINUTE;

    @VisibleForTesting
    static final String APP_IDLE_FILENAME = "app_idle_stats.xml";
@@ -89,6 +82,10 @@ public class AppIdleHistory {
    private static final String ATTR_CURRENT_BUCKET = "appLimitBucket";
    // The reason the app was put in the above bucket
    private static final String ATTR_BUCKETING_REASON = "bucketReason";
    // The last time a job was run for this app
    private static final String ATTR_LAST_RUN_JOB_TIME = "lastJobRunTime";
    // The time when the forced active state can be overridden.
    private static final String ATTR_BUCKET_TIMEOUT_TIME = "bucketTimeoutTime";

    // device on time = mElapsedDuration + (timeNow - mElapsedSnapshot)
    private long mElapsedSnapshot; // Elapsed time snapshot when last write of mDeviceOnDuration
@@ -103,8 +100,6 @@ public class AppIdleHistory {
    private boolean mScreenOn;

    static class AppUsageHistory {
        // Debug
        final byte[] recent = new byte[HISTORY_SIZE];
        // Last used time using elapsed timebase
        long lastUsedElapsedTime;
        // Last used time using screen_on timebase
@@ -118,6 +113,13 @@ public class AppIdleHistory {
        String bucketingReason;
        // In-memory only, last bucket for which the listeners were informed
        int lastInformedBucket;
        // The last time a job was run for this app, using elapsed timebase
        long lastJobRunTime;
        // When should the bucket state timeout, in elapsed timebase, if greater than
        // lastUsedElapsedTime.
        // This is used to keep the app in a high bucket regardless of other timeouts and
        // predictions.
        long bucketTimeoutTime;
    }

    AppIdleHistory(File storageDir, long elapsedRealtime) {
@@ -195,81 +197,47 @@ public class AppIdleHistory {
        writeScreenOnTime();
    }

    public int reportUsage(String packageName, int userId, long elapsedRealtime) {
    /**
     * Mark the app as used and update the bucket if necessary. If there is a timeout specified
     * that's in the future, then the usage event is temporary and keeps the app in the specified
     * bucket at least until the timeout is reached. This can be used to keep the app in an
     * elevated bucket for a while until some important task gets to run.
     * @param packageName
     * @param userId
     * @param bucket the bucket to set the app to
     * @param elapsedRealtime mark as used time if non-zero
     * @param timeout set the timeout of the specified bucket, if non-zero
     * @return
     */
    public int reportUsage(String packageName, int userId, int bucket, long elapsedRealtime,
            long timeout) {
        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
                elapsedRealtime, true);

        shiftHistoryToNow(userHistory, elapsedRealtime);

        if (elapsedRealtime != 0) {
            appUsageHistory.lastUsedElapsedTime = mElapsedDuration
                    + (elapsedRealtime - mElapsedSnapshot);
            appUsageHistory.lastUsedScreenTime = getScreenOnTime(elapsedRealtime);
        appUsageHistory.recent[HISTORY_SIZE - 1] = FLAG_LAST_STATE | FLAG_PARTIAL_ACTIVE;
        if (appUsageHistory.currentBucket > STANDBY_BUCKET_ACTIVE) {
            appUsageHistory.currentBucket = STANDBY_BUCKET_ACTIVE;
            if (DEBUG) {
                Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
                        + ", reason=" + appUsageHistory.bucketingReason);
            }
        }
        appUsageHistory.bucketingReason = REASON_USAGE;

        return appUsageHistory.currentBucket;
        }

    public int reportMildUsage(String packageName, int userId, long elapsedRealtime) {
        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
                elapsedRealtime, true);
        if (appUsageHistory.currentBucket > STANDBY_BUCKET_WORKING_SET) {
            appUsageHistory.currentBucket = STANDBY_BUCKET_WORKING_SET;
        if (appUsageHistory.currentBucket > bucket) {
            appUsageHistory.currentBucket = bucket;
            if (DEBUG) {
                Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory.currentBucket
                Slog.d(TAG, "Moved " + packageName + " to bucket=" + appUsageHistory
                        .currentBucket
                        + ", reason=" + appUsageHistory.bucketingReason);
            }
            if (timeout > elapsedRealtime) {
                // Convert to elapsed timebase
                appUsageHistory.bucketTimeoutTime = mElapsedDuration + (timeout - mElapsedSnapshot);
            }
        }
        // TODO: Should this be a different reason for partial usage?
        appUsageHistory.bucketingReason = REASON_USAGE;

        return appUsageHistory.currentBucket;
    }

    public void setIdle(String packageName, int userId, long elapsedRealtime) {
        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
        AppUsageHistory appUsageHistory = getPackageHistory(userHistory, packageName,
                elapsedRealtime, true);

        shiftHistoryToNow(userHistory, elapsedRealtime);

        appUsageHistory.recent[HISTORY_SIZE - 1] &= ~FLAG_LAST_STATE;
    }

    private void shiftHistoryToNow(ArrayMap<String, AppUsageHistory> userHistory,
            long elapsedRealtime) {
        long thisPeriod = elapsedRealtime / PERIOD_DURATION;
        // Has the period switched over? Slide all users' package histories
        if (mLastPeriod != 0 && mLastPeriod < thisPeriod
                && (thisPeriod - mLastPeriod) < HISTORY_SIZE - 1) {
            int diff = (int) (thisPeriod - mLastPeriod);
            final int NUSERS = mIdleHistory.size();
            for (int u = 0; u < NUSERS; u++) {
                userHistory = mIdleHistory.valueAt(u);
                for (AppUsageHistory idleState : userHistory.values()) {
                    // Shift left
                    System.arraycopy(idleState.recent, diff, idleState.recent, 0,
                            HISTORY_SIZE - diff);
                    // Replicate last state across the diff
                    for (int i = 0; i < diff; i++) {
                        idleState.recent[HISTORY_SIZE - i - 1] =
                            (byte) (idleState.recent[HISTORY_SIZE - diff - 1] & FLAG_LAST_STATE);
                    }
                }
            }
        }
        mLastPeriod = thisPeriod;
    }

    private ArrayMap<String, AppUsageHistory> getUserHistory(int userId) {
        ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
        if (userHistory == null) {
@@ -291,6 +259,7 @@ public class AppIdleHistory {
            appUsageHistory.currentBucket = STANDBY_BUCKET_NEVER;
            appUsageHistory.bucketingReason = REASON_DEFAULT;
            appUsageHistory.lastInformedBucket = -1;
            appUsageHistory.lastJobRunTime = Long.MIN_VALUE; // long long time ago
            userHistory.put(packageName, appUsageHistory);
        }
        return appUsageHistory;
@@ -338,6 +307,38 @@ public class AppIdleHistory {
        }
    }

    /**
     * Marks the last time a job was run, with the given elapsedRealtime. The time stored is
     * based on the elapsed timebase.
     * @param packageName
     * @param userId
     * @param elapsedRealtime
     */
    public void setLastJobRunTime(String packageName, int userId, long elapsedRealtime) {
        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
        AppUsageHistory appUsageHistory =
                getPackageHistory(userHistory, packageName, elapsedRealtime, true);
        appUsageHistory.lastJobRunTime = getElapsedTime(elapsedRealtime);
    }

    /**
     * Returns the time since the last job was run for this app. This can be larger than the
     * current elapsedRealtime, in case it happened before boot or a really large value if no jobs
     * were ever run.
     * @param packageName
     * @param userId
     * @param elapsedRealtime
     * @return
     */
    public long getTimeSinceLastJobRun(String packageName, int userId, long elapsedRealtime) {
        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
        AppUsageHistory appUsageHistory =
                getPackageHistory(userHistory, packageName, elapsedRealtime, true);
        // Don't adjust the default, else it'll wrap around to a positive value
        if (appUsageHistory.lastJobRunTime == Long.MIN_VALUE) return Long.MAX_VALUE;
        return getElapsedTime(elapsedRealtime) - appUsageHistory.lastJobRunTime;
    }

    public int getAppStandbyBucket(String packageName, int userId, long elapsedRealtime) {
        ArrayMap<String, AppUsageHistory> userHistory = getUserHistory(userId);
        AppUsageHistory appUsageHistory =
@@ -473,12 +474,8 @@ public class AppIdleHistory {
                                Long.parseLong(parser.getAttributeValue(null, ATTR_ELAPSED_IDLE));
                        appUsageHistory.lastUsedScreenTime =
                                Long.parseLong(parser.getAttributeValue(null, ATTR_SCREEN_IDLE));
                        String lastPredictedTimeString = parser.getAttributeValue(null,
                                ATTR_LAST_PREDICTED_TIME);
                        if (lastPredictedTimeString != null) {
                            appUsageHistory.lastPredictedTime =
                                    Long.parseLong(lastPredictedTimeString);
                        }
                        appUsageHistory.lastPredictedTime = getLongValue(parser,
                                ATTR_LAST_PREDICTED_TIME, 0L);
                        String currentBucketString = parser.getAttributeValue(null,
                                ATTR_CURRENT_BUCKET);
                        appUsageHistory.currentBucket = currentBucketString == null
@@ -486,6 +483,10 @@ public class AppIdleHistory {
                                : Integer.parseInt(currentBucketString);
                        appUsageHistory.bucketingReason =
                                parser.getAttributeValue(null, ATTR_BUCKETING_REASON);
                        appUsageHistory.lastJobRunTime = getLongValue(parser,
                                ATTR_LAST_RUN_JOB_TIME, Long.MIN_VALUE);
                        appUsageHistory.bucketTimeoutTime = getLongValue(parser,
                                ATTR_BUCKET_TIMEOUT_TIME, 0L);
                        if (appUsageHistory.bucketingReason == null) {
                            appUsageHistory.bucketingReason = REASON_DEFAULT;
                        }
@@ -501,6 +502,12 @@ public class AppIdleHistory {
        }
    }

    private long getLongValue(XmlPullParser parser, String attrName, long defValue) {
        String value = parser.getAttributeValue(null, attrName);
        if (value == null) return defValue;
        return Long.parseLong(value);
    }

    public void writeAppIdleTimes(int userId) {
        FileOutputStream fos = null;
        AtomicFile appIdleFile = new AtomicFile(getUserFile(userId));
@@ -531,6 +538,14 @@ public class AppIdleHistory {
                xml.attribute(null, ATTR_CURRENT_BUCKET,
                        Integer.toString(history.currentBucket));
                xml.attribute(null, ATTR_BUCKETING_REASON, history.bucketingReason);
                if (history.bucketTimeoutTime > 0) {
                    xml.attribute(null, ATTR_BUCKET_TIMEOUT_TIME, Long.toString(history
                            .bucketTimeoutTime));
                }
                if (history.lastJobRunTime != Long.MIN_VALUE) {
                    xml.attribute(null, ATTR_LAST_RUN_JOB_TIME, Long.toString(history
                            .lastJobRunTime));
                }
                xml.endTag(null, TAG_PACKAGE);
            }

@@ -565,6 +580,10 @@ public class AppIdleHistory {
            TimeUtils.formatDuration(screenOnTime - appUsageHistory.lastUsedScreenTime, idpw);
            idpw.print(" lastPredictedTime=");
            TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastPredictedTime, idpw);
            idpw.print(" bucketTimeoutTime=");
            TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.bucketTimeoutTime, idpw);
            idpw.print(" lastJobRunTime=");
            TimeUtils.formatDuration(totalElapsedTime - appUsageHistory.lastJobRunTime, idpw);
            idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n"));
            idpw.print(" bucket=" + appUsageHistory.currentBucket
                    + " reason=" + appUsageHistory.bucketingReason);
@@ -579,21 +598,4 @@ public class AppIdleHistory {
        idpw.println();
        idpw.decreaseIndent();
    }

    public void dumpHistory(IndentingPrintWriter idpw, int userId) {
        ArrayMap<String, AppUsageHistory> userHistory = mIdleHistory.get(userId);
        final long elapsedRealtime = SystemClock.elapsedRealtime();
        if (userHistory == null) return;
        final int P = userHistory.size();
        for (int p = 0; p < P; p++) {
            final String packageName = userHistory.keyAt(p);
            final byte[] history = userHistory.valueAt(p).recent;
            for (int i = 0; i < HISTORY_SIZE; i++) {
                idpw.print(history[i] == 0 ? '.' : 'A');
            }
            idpw.print(" idle=" + (isIdle(packageName, userId, elapsedRealtime) ? "y" : "n"));
            idpw.print("  " + packageName);
            idpw.println();
        }
    }
}
+46 −15

File changed.

Preview size limit exceeded, changes collapsed.

Loading