1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.usage;
18
19import static android.app.usage.UsageEvents.Event.NOTIFICATION_SEEN;
20import static android.app.usage.UsageEvents.Event.SLICE_PINNED;
21import static android.app.usage.UsageEvents.Event.SLICE_PINNED_PRIV;
22import static android.app.usage.UsageEvents.Event.SYSTEM_INTERACTION;
23import static android.app.usage.UsageEvents.Event.USER_INTERACTION;
24import static android.app.usage.UsageStatsManager.REASON_MAIN_DEFAULT;
25import static android.app.usage.UsageStatsManager.REASON_MAIN_FORCED;
26import static android.app.usage.UsageStatsManager.REASON_MAIN_PREDICTED;
27import static android.app.usage.UsageStatsManager.REASON_MAIN_TIMEOUT;
28import static android.app.usage.UsageStatsManager.REASON_MAIN_USAGE;
29import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_ACTIVE;
30import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_EXEMPTED;
31import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_FREQUENT;
32import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_NEVER;
33import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_RARE;
34import static android.app.usage.UsageStatsManager.STANDBY_BUCKET_WORKING_SET;
35
36import static org.junit.Assert.assertEquals;
37import static org.junit.Assert.assertFalse;
38import static org.junit.Assert.assertNotEquals;
39import static org.junit.Assert.assertTrue;
40
41import static org.junit.Assert.fail;
42import static org.mockito.ArgumentMatchers.eq;
43import static org.mockito.Matchers.anyInt;
44import static org.mockito.Matchers.anyString;
45import static org.mockito.Mockito.doReturn;
46import static org.mockito.Mockito.mock;
47
48import android.app.usage.UsageEvents;
49import android.app.usage.UsageStatsManagerInternal;
50import android.appwidget.AppWidgetManager;
51import android.content.Context;
52import android.content.ContextWrapper;
53import android.content.pm.ApplicationInfo;
54import android.content.pm.PackageInfo;
55import android.content.pm.PackageManager;
56import android.hardware.display.DisplayManager;
57import android.os.Handler;
58import android.os.Looper;
59import android.os.RemoteException;
60import android.platform.test.annotations.Presubmit;
61import android.support.test.filters.SmallTest;
62import android.support.test.InstrumentationRegistry;
63import android.support.test.runner.AndroidJUnit4;
64import android.util.ArraySet;
65import android.view.Display;
66
67import com.android.server.SystemService;
68
69import org.junit.Before;
70import org.junit.Test;
71import org.junit.runner.RunWith;
72
73import java.io.File;
74import java.util.ArrayList;
75import java.util.Arrays;
76import java.util.List;
77import java.util.Set;
78import java.util.concurrent.CountDownLatch;
79import java.util.concurrent.TimeUnit;
80
81/**
82 * Unit test for AppStandbyController.
83 */
84@RunWith(AndroidJUnit4.class)
85@Presubmit
86@SmallTest
87public class AppStandbyControllerTests {
88
89    private static final String PACKAGE_1 = "com.example.foo";
90    private static final int UID_1 = 10000;
91    private static final String PACKAGE_EXEMPTED_1 = "com.android.exempted";
92    private static final int UID_EXEMPTED_1 = 10001;
93    private static final int USER_ID = 0;
94    private static final int USER_ID2 = 10;
95
96    private static final String ADMIN_PKG = "com.android.admin";
97    private static final String ADMIN_PKG2 = "com.android.admin2";
98    private static final String ADMIN_PKG3 = "com.android.admin3";
99
100    private static final long MINUTE_MS = 60 * 1000;
101    private static final long HOUR_MS = 60 * MINUTE_MS;
102    private static final long DAY_MS = 24 * HOUR_MS;
103
104    private static final long WORKING_SET_THRESHOLD = 12 * HOUR_MS;
105    private static final long FREQUENT_THRESHOLD = 24 * HOUR_MS;
106    private static final long RARE_THRESHOLD = 48 * HOUR_MS;
107    // Short STABLE_CHARGING_THRESHOLD for testing purposes
108    private static final long STABLE_CHARGING_THRESHOLD = 2000;
109
110    private MyInjector mInjector;
111    private AppStandbyController mController;
112
113    static class MyContextWrapper extends ContextWrapper {
114        PackageManager mockPm = mock(PackageManager.class);
115
116        public MyContextWrapper(Context base) {
117            super(base);
118        }
119
120        public PackageManager getPackageManager() {
121            return mockPm;
122        }
123    }
124
125    static class MyInjector extends AppStandbyController.Injector {
126        long mElapsedRealtime;
127        boolean mIsCharging;
128        List<String> mPowerSaveWhitelistExceptIdle = new ArrayList<>();
129        boolean mDisplayOn;
130        DisplayManager.DisplayListener mDisplayListener;
131        String mBoundWidgetPackage = PACKAGE_EXEMPTED_1;
132
133        MyInjector(Context context, Looper looper) {
134            super(context, looper);
135        }
136
137        @Override
138        void onBootPhase(int phase) {
139        }
140
141        @Override
142        int getBootPhase() {
143            return SystemService.PHASE_BOOT_COMPLETED;
144        }
145
146        @Override
147        long elapsedRealtime() {
148            return mElapsedRealtime;
149        }
150
151        @Override
152        long currentTimeMillis() {
153            return mElapsedRealtime;
154        }
155
156        @Override
157        boolean isAppIdleEnabled() {
158            return true;
159        }
160
161        @Override
162        boolean isCharging() {
163            return mIsCharging;
164        }
165
166        @Override
167        boolean isPowerSaveWhitelistExceptIdleApp(String packageName) throws RemoteException {
168            return mPowerSaveWhitelistExceptIdle.contains(packageName);
169        }
170
171        @Override
172        File getDataSystemDirectory() {
173            return new File(getContext().getFilesDir(), Long.toString(Math.randomLongInternal()));
174        }
175
176        @Override
177        void noteEvent(int event, String packageName, int uid) throws RemoteException {
178        }
179
180        @Override
181        boolean isPackageEphemeral(int userId, String packageName) {
182            // TODO: update when testing ephemeral apps scenario
183            return false;
184        }
185
186        @Override
187        int[] getRunningUserIds() {
188            return new int[] {USER_ID};
189        }
190
191        @Override
192        boolean isDefaultDisplayOn() {
193            return mDisplayOn;
194        }
195
196        @Override
197        void registerDisplayListener(DisplayManager.DisplayListener listener, Handler handler) {
198            mDisplayListener = listener;
199        }
200
201        @Override
202        String getActiveNetworkScorer() {
203            return null;
204        }
205
206        @Override
207        public boolean isBoundWidgetPackage(AppWidgetManager appWidgetManager, String packageName,
208                int userId) {
209            return packageName != null && packageName.equals(mBoundWidgetPackage);
210        }
211
212        @Override
213        String getAppIdleSettings() {
214            return "screen_thresholds=0/0/0/" + HOUR_MS + ",elapsed_thresholds=0/"
215                    + WORKING_SET_THRESHOLD + "/"
216                    + FREQUENT_THRESHOLD + "/"
217                    + RARE_THRESHOLD + ","
218                    + "stable_charging_threshold=" + STABLE_CHARGING_THRESHOLD;
219        }
220
221        @Override
222        public boolean isDeviceIdleMode() {
223            return false;
224        }
225
226        // Internal methods
227
228        void setDisplayOn(boolean on) {
229            mDisplayOn = on;
230            if (mDisplayListener != null) {
231                mDisplayListener.onDisplayChanged(Display.DEFAULT_DISPLAY);
232            }
233        }
234    }
235
236    private void setupPm(PackageManager mockPm) throws PackageManager.NameNotFoundException {
237        List<PackageInfo> packages = new ArrayList<>();
238        PackageInfo pi = new PackageInfo();
239        pi.applicationInfo = new ApplicationInfo();
240        pi.applicationInfo.uid = UID_1;
241        pi.packageName = PACKAGE_1;
242        packages.add(pi);
243
244        PackageInfo pie = new PackageInfo();
245        pie.applicationInfo = new ApplicationInfo();
246        pie.applicationInfo.uid = UID_EXEMPTED_1;
247        pie.packageName = PACKAGE_EXEMPTED_1;
248        packages.add(pie);
249
250        doReturn(packages).when(mockPm).getInstalledPackagesAsUser(anyInt(), anyInt());
251        try {
252            doReturn(UID_1).when(mockPm).getPackageUidAsUser(eq(PACKAGE_1), anyInt(), anyInt());
253            doReturn(UID_EXEMPTED_1).when(mockPm).getPackageUidAsUser(eq(PACKAGE_EXEMPTED_1),
254                    anyInt(), anyInt());
255            doReturn(pi.applicationInfo).when(mockPm).getApplicationInfo(eq(pi.packageName),
256                    anyInt());
257            doReturn(pie.applicationInfo).when(mockPm).getApplicationInfo(eq(pie.packageName),
258                    anyInt());
259        } catch (PackageManager.NameNotFoundException nnfe) {}
260    }
261
262    private void setChargingState(AppStandbyController controller, boolean charging) {
263        mInjector.mIsCharging = charging;
264        if (controller != null) {
265            controller.setChargingState(charging);
266        }
267    }
268
269    private AppStandbyController setupController() throws Exception {
270        mInjector.mElapsedRealtime = 0;
271        setupPm(mInjector.getContext().getPackageManager());
272        AppStandbyController controller = new AppStandbyController(mInjector);
273        controller.initializeDefaultsForSystemApps(USER_ID);
274        controller.onBootPhase(SystemService.PHASE_SYSTEM_SERVICES_READY);
275        controller.onBootPhase(SystemService.PHASE_BOOT_COMPLETED);
276        mInjector.setDisplayOn(false);
277        mInjector.setDisplayOn(true);
278        setChargingState(controller, false);
279        controller.checkIdleStates(USER_ID);
280        assertEquals(STANDBY_BUCKET_EXEMPTED,
281                controller.getAppStandbyBucket(PACKAGE_EXEMPTED_1, USER_ID,
282                        mInjector.mElapsedRealtime, false));
283        assertNotEquals(STANDBY_BUCKET_EXEMPTED,
284                controller.getAppStandbyBucket(PACKAGE_1, USER_ID,
285                        mInjector.mElapsedRealtime, false));
286
287        return controller;
288    }
289
290    private long getCurrentTime() {
291        return TimeUnit.NANOSECONDS.toMillis(System.nanoTime());
292    }
293
294    @Before
295    public void setUp() throws Exception {
296        MyContextWrapper myContext = new MyContextWrapper(InstrumentationRegistry.getContext());
297        mInjector = new MyInjector(myContext, Looper.getMainLooper());
298        mController = setupController();
299        setChargingState(mController, false);
300    }
301
302    private class TestParoleListener extends UsageStatsManagerInternal.AppIdleStateChangeListener {
303        private boolean mOnParole = false;
304        private CountDownLatch mLatch;
305        private long mLastParoleChangeTime;
306
307        public boolean getParoleState() {
308            synchronized (this) {
309                return mOnParole;
310            }
311        }
312
313        public void rearmLatch() {
314            synchronized (this) {
315                mLatch = new CountDownLatch(1);
316            }
317        }
318
319        public void awaitOnLatch(long time) throws Exception {
320            mLatch.await(time, TimeUnit.MILLISECONDS);
321        }
322
323        public long getLastParoleChangeTime() {
324            synchronized (this) {
325                return mLastParoleChangeTime;
326            }
327        }
328
329        @Override
330        public void onAppIdleStateChanged(String packageName, int userId, boolean idle,
331                int bucket, int reason) {
332        }
333
334        @Override
335        public void onParoleStateChanged(boolean isParoleOn) {
336            synchronized (this) {
337                // Only record information if it is being looked for
338                if (mLatch.getCount() > 0) {
339                    mOnParole = isParoleOn;
340                    mLastParoleChangeTime = getCurrentTime();
341                    mLatch.countDown();
342                }
343            }
344        }
345    }
346
347    @Test
348    public void testCharging() throws Exception {
349        long startTime;
350        TestParoleListener paroleListener = new TestParoleListener();
351        long marginOfError = 200;
352
353        // Charging
354        paroleListener.rearmLatch();
355        mController.addListener(paroleListener);
356        startTime = getCurrentTime();
357        setChargingState(mController, true);
358        paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
359        assertTrue(paroleListener.mOnParole);
360        // Parole will only be granted after device has been charging for a sufficient amount of
361        // time.
362        assertEquals(STABLE_CHARGING_THRESHOLD,
363                paroleListener.getLastParoleChangeTime() - startTime,
364                marginOfError);
365
366        // Discharging
367        paroleListener.rearmLatch();
368        startTime = getCurrentTime();
369        setChargingState(mController, false);
370        mController.checkIdleStates(USER_ID);
371        paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
372        assertFalse(paroleListener.getParoleState());
373        // Parole should be revoked immediately
374        assertEquals(0,
375                paroleListener.getLastParoleChangeTime() - startTime,
376                marginOfError);
377
378        // Brief Charging
379        paroleListener.rearmLatch();
380        setChargingState(mController, true);
381        setChargingState(mController, false);
382        // Device stopped charging before the stable charging threshold.
383        // Parole should not be granted at the end of the threshold
384        paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
385        assertFalse(paroleListener.getParoleState());
386
387        // Charging Again
388        paroleListener.rearmLatch();
389        startTime = getCurrentTime();
390        setChargingState(mController, true);
391        paroleListener.awaitOnLatch(STABLE_CHARGING_THRESHOLD * 3 / 2);
392        assertTrue(paroleListener.getParoleState());
393        assertTrue(paroleListener.mOnParole);
394        assertEquals(STABLE_CHARGING_THRESHOLD,
395                paroleListener.getLastParoleChangeTime() - startTime,
396                marginOfError);
397    }
398
399    private void assertTimeout(AppStandbyController controller, long elapsedTime, int bucket) {
400        mInjector.mElapsedRealtime = elapsedTime;
401        controller.checkIdleStates(USER_ID);
402        assertEquals(bucket,
403                controller.getAppStandbyBucket(PACKAGE_1, USER_ID, mInjector.mElapsedRealtime,
404                        false));
405    }
406
407    private void reportEvent(AppStandbyController controller, int eventType,
408            long elapsedTime) {
409        // Back to ACTIVE on event
410        mInjector.mElapsedRealtime = elapsedTime;
411        UsageEvents.Event ev = new UsageEvents.Event();
412        ev.mPackage = PACKAGE_1;
413        ev.mEventType = eventType;
414        controller.reportEvent(ev, elapsedTime, USER_ID);
415    }
416
417    private int getStandbyBucket(AppStandbyController controller) {
418        return controller.getAppStandbyBucket(PACKAGE_1, USER_ID, mInjector.mElapsedRealtime,
419                true);
420    }
421
422    private void assertBucket(int bucket) {
423        assertEquals(bucket, getStandbyBucket(mController));
424    }
425
426    @Test
427    public void testBuckets() throws Exception {
428        assertTimeout(mController, 0, STANDBY_BUCKET_NEVER);
429
430        reportEvent(mController, USER_INTERACTION, 0);
431
432        // ACTIVE bucket
433        assertTimeout(mController, WORKING_SET_THRESHOLD - 1, STANDBY_BUCKET_ACTIVE);
434
435        // WORKING_SET bucket
436        assertTimeout(mController, WORKING_SET_THRESHOLD + 1, STANDBY_BUCKET_WORKING_SET);
437
438        // WORKING_SET bucket
439        assertTimeout(mController, FREQUENT_THRESHOLD - 1, STANDBY_BUCKET_WORKING_SET);
440
441        // FREQUENT bucket
442        assertTimeout(mController, FREQUENT_THRESHOLD + 1, STANDBY_BUCKET_FREQUENT);
443
444        // RARE bucket
445        assertTimeout(mController, RARE_THRESHOLD + 1, STANDBY_BUCKET_RARE);
446
447        reportEvent(mController, USER_INTERACTION, RARE_THRESHOLD + 1);
448
449        assertTimeout(mController, RARE_THRESHOLD + 1, STANDBY_BUCKET_ACTIVE);
450
451        // RARE bucket
452        assertTimeout(mController, RARE_THRESHOLD * 2 + 2, STANDBY_BUCKET_RARE);
453    }
454
455    @Test
456    public void testScreenTimeAndBuckets() throws Exception {
457        mInjector.setDisplayOn(false);
458
459        assertTimeout(mController, 0, STANDBY_BUCKET_NEVER);
460
461        reportEvent(mController, USER_INTERACTION, 0);
462
463        // ACTIVE bucket
464        assertTimeout(mController, WORKING_SET_THRESHOLD - 1, STANDBY_BUCKET_ACTIVE);
465
466        // WORKING_SET bucket
467        assertTimeout(mController, WORKING_SET_THRESHOLD + 1, STANDBY_BUCKET_WORKING_SET);
468
469        // RARE bucket, should fail because the screen wasn't ON.
470        mInjector.mElapsedRealtime = RARE_THRESHOLD + 1;
471        mController.checkIdleStates(USER_ID);
472        assertNotEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController));
473
474        mInjector.setDisplayOn(true);
475        assertTimeout(mController, RARE_THRESHOLD * 2 + 2, STANDBY_BUCKET_RARE);
476    }
477
478    @Test
479    public void testForcedIdle() throws Exception {
480        mController.forceIdleState(PACKAGE_1, USER_ID, true);
481        assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController));
482        assertTrue(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
483
484        mController.forceIdleState(PACKAGE_1, USER_ID, false);
485        assertEquals(STANDBY_BUCKET_ACTIVE, mController.getAppStandbyBucket(PACKAGE_1, USER_ID, 0,
486                true));
487        assertFalse(mController.isAppIdleFiltered(PACKAGE_1, UID_1, USER_ID, 0));
488    }
489
490    @Test
491    public void testNotificationEvent() throws Exception {
492        reportEvent(mController, USER_INTERACTION, 0);
493        assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController));
494        mInjector.mElapsedRealtime = 1;
495        reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime);
496        assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController));
497
498        mController.forceIdleState(PACKAGE_1, USER_ID, true);
499        reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime);
500        assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController));
501    }
502
503    @Test
504    public void testSlicePinnedEvent() throws Exception {
505        reportEvent(mController, USER_INTERACTION, 0);
506        assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController));
507        mInjector.mElapsedRealtime = 1;
508        reportEvent(mController, SLICE_PINNED, mInjector.mElapsedRealtime);
509        assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController));
510
511        mController.forceIdleState(PACKAGE_1, USER_ID, true);
512        reportEvent(mController, SLICE_PINNED, mInjector.mElapsedRealtime);
513        assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController));
514    }
515
516    @Test
517    public void testSlicePinnedPrivEvent() throws Exception {
518        mController.forceIdleState(PACKAGE_1, USER_ID, true);
519        reportEvent(mController, SLICE_PINNED_PRIV, mInjector.mElapsedRealtime);
520        assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController));
521    }
522
523    @Test
524    public void testPredictionTimedout() throws Exception {
525        // Set it to timeout or usage, so that prediction can override it
526        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
527                REASON_MAIN_TIMEOUT, HOUR_MS);
528        assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController));
529
530        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
531                REASON_MAIN_PREDICTED, HOUR_MS);
532        assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController));
533
534        // Fast forward 12 hours
535        mInjector.mElapsedRealtime += WORKING_SET_THRESHOLD;
536        mController.checkIdleStates(USER_ID);
537        // Should still be in predicted bucket, since prediction timeout is 1 day since prediction
538        assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController));
539        // Fast forward two more hours
540        mInjector.mElapsedRealtime += 2 * HOUR_MS;
541        mController.checkIdleStates(USER_ID);
542        // Should have now applied prediction timeout
543        assertEquals(STANDBY_BUCKET_WORKING_SET, getStandbyBucket(mController));
544
545        // Fast forward RARE bucket
546        mInjector.mElapsedRealtime += RARE_THRESHOLD;
547        mController.checkIdleStates(USER_ID);
548        // Should continue to apply prediction timeout
549        assertEquals(STANDBY_BUCKET_RARE, getStandbyBucket(mController));
550    }
551
552    @Test
553    public void testOverrides() throws Exception {
554        // Can force to NEVER
555        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
556                REASON_MAIN_FORCED, 1 * HOUR_MS);
557        assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController));
558
559        // Prediction can't override FORCED reason
560        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
561                REASON_MAIN_FORCED, 1 * HOUR_MS);
562        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
563                REASON_MAIN_PREDICTED, 1 * HOUR_MS);
564        assertEquals(STANDBY_BUCKET_FREQUENT, getStandbyBucket(mController));
565
566        // Prediction can't override NEVER
567        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
568                REASON_MAIN_DEFAULT, 2 * HOUR_MS);
569        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
570                REASON_MAIN_PREDICTED, 2 * HOUR_MS);
571        assertEquals(STANDBY_BUCKET_NEVER, getStandbyBucket(mController));
572
573        // Prediction can't set to NEVER
574        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
575                REASON_MAIN_USAGE, 2 * HOUR_MS);
576        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_NEVER,
577                REASON_MAIN_PREDICTED, 2 * HOUR_MS);
578        assertEquals(STANDBY_BUCKET_ACTIVE, getStandbyBucket(mController));
579    }
580
581    @Test
582    public void testTimeout() throws Exception {
583        reportEvent(mController, USER_INTERACTION, 0);
584        assertBucket(STANDBY_BUCKET_ACTIVE);
585
586        mInjector.mElapsedRealtime = 2000;
587        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
588                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
589        assertBucket(STANDBY_BUCKET_ACTIVE);
590
591        // bucketing works after timeout
592        mInjector.mElapsedRealtime = mController.mPredictionTimeoutMillis - 100;
593        mController.checkIdleStates(USER_ID);
594        // Use recent prediction
595        assertBucket(STANDBY_BUCKET_FREQUENT);
596
597        // Way past prediction timeout, use system thresholds
598        mInjector.mElapsedRealtime = RARE_THRESHOLD * 4;
599        mController.checkIdleStates(USER_ID);
600        assertBucket(STANDBY_BUCKET_RARE);
601    }
602
603    @Test
604    public void testCascadingTimeouts() throws Exception {
605        reportEvent(mController, USER_INTERACTION, 0);
606        assertBucket(STANDBY_BUCKET_ACTIVE);
607
608        reportEvent(mController, NOTIFICATION_SEEN, 1000);
609        assertBucket(STANDBY_BUCKET_ACTIVE);
610
611        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_WORKING_SET,
612                REASON_MAIN_PREDICTED, 1000);
613        assertBucket(STANDBY_BUCKET_ACTIVE);
614
615        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
616                REASON_MAIN_PREDICTED, 2000 + mController.mStrongUsageTimeoutMillis);
617        assertBucket(STANDBY_BUCKET_WORKING_SET);
618
619        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
620                REASON_MAIN_PREDICTED, 2000 + mController.mNotificationSeenTimeoutMillis);
621        assertBucket(STANDBY_BUCKET_FREQUENT);
622    }
623
624    @Test
625    public void testOverlappingTimeouts() throws Exception {
626        reportEvent(mController, USER_INTERACTION, 0);
627        assertBucket(STANDBY_BUCKET_ACTIVE);
628
629        reportEvent(mController, NOTIFICATION_SEEN, 1000);
630        assertBucket(STANDBY_BUCKET_ACTIVE);
631
632        // Overlapping USER_INTERACTION before previous one times out
633        reportEvent(mController, USER_INTERACTION, mController.mStrongUsageTimeoutMillis - 1000);
634        assertBucket(STANDBY_BUCKET_ACTIVE);
635
636        // Still in ACTIVE after first USER_INTERACTION times out
637        mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis + 1000;
638        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
639                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
640        assertBucket(STANDBY_BUCKET_ACTIVE);
641
642        // Both timed out, so NOTIFICATION_SEEN timeout should be effective
643        mInjector.mElapsedRealtime = mController.mStrongUsageTimeoutMillis * 2 + 2000;
644        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
645                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
646        assertBucket(STANDBY_BUCKET_WORKING_SET);
647
648        mInjector.mElapsedRealtime = mController.mNotificationSeenTimeoutMillis + 2000;
649        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_RARE,
650                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
651        assertBucket(STANDBY_BUCKET_RARE);
652    }
653
654    @Test
655    public void testSystemInteractionTimeout() throws Exception {
656        setChargingState(mController, false);
657
658        reportEvent(mController, USER_INTERACTION, 0);
659        // Fast forward to RARE
660        mInjector.mElapsedRealtime = RARE_THRESHOLD + 100;
661        mController.checkIdleStates(USER_ID);
662        assertBucket(STANDBY_BUCKET_RARE);
663
664        // Trigger a SYSTEM_INTERACTION and verify bucket
665        reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime);
666        assertBucket(STANDBY_BUCKET_ACTIVE);
667
668        // Verify it's still in ACTIVE close to end of timeout
669        mInjector.mElapsedRealtime += mController.mSystemInteractionTimeoutMillis - 100;
670        mController.checkIdleStates(USER_ID);
671        assertBucket(STANDBY_BUCKET_ACTIVE);
672
673        // Verify bucket moves to RARE after timeout
674        mInjector.mElapsedRealtime += 200;
675        mController.checkIdleStates(USER_ID);
676        assertBucket(STANDBY_BUCKET_RARE);
677    }
678
679    @Test
680    public void testPredictionNotOverridden() throws Exception {
681        reportEvent(mController, USER_INTERACTION, 0);
682        assertBucket(STANDBY_BUCKET_ACTIVE);
683
684        mInjector.mElapsedRealtime = WORKING_SET_THRESHOLD - 1000;
685        reportEvent(mController, NOTIFICATION_SEEN, mInjector.mElapsedRealtime);
686        assertBucket(STANDBY_BUCKET_ACTIVE);
687
688        // Falls back to WORKING_SET
689        mInjector.mElapsedRealtime += 5000;
690        mController.checkIdleStates(USER_ID);
691        assertBucket(STANDBY_BUCKET_WORKING_SET);
692
693        // Predict to ACTIVE
694        mInjector.mElapsedRealtime += 1000;
695        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_ACTIVE,
696                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
697        assertBucket(STANDBY_BUCKET_ACTIVE);
698
699        // CheckIdleStates should not change the prediction
700        mInjector.mElapsedRealtime += 1000;
701        mController.checkIdleStates(USER_ID);
702        assertBucket(STANDBY_BUCKET_ACTIVE);
703    }
704
705    @Test
706    public void testPredictionStrikesBack() throws Exception {
707        reportEvent(mController, USER_INTERACTION, 0);
708        assertBucket(STANDBY_BUCKET_ACTIVE);
709
710        // Predict to FREQUENT
711        mInjector.mElapsedRealtime = RARE_THRESHOLD;
712        mController.setAppStandbyBucket(PACKAGE_1, USER_ID, STANDBY_BUCKET_FREQUENT,
713                REASON_MAIN_PREDICTED, mInjector.mElapsedRealtime);
714        assertBucket(STANDBY_BUCKET_FREQUENT);
715
716        // Add a short timeout event
717        mInjector.mElapsedRealtime += 1000;
718        reportEvent(mController, SYSTEM_INTERACTION, mInjector.mElapsedRealtime);
719        assertBucket(STANDBY_BUCKET_ACTIVE);
720        mInjector.mElapsedRealtime += 1000;
721        mController.checkIdleStates(USER_ID);
722        assertBucket(STANDBY_BUCKET_ACTIVE);
723
724        // Verify it reverted to predicted
725        mInjector.mElapsedRealtime += WORKING_SET_THRESHOLD / 2;
726        mController.checkIdleStates(USER_ID);
727        assertBucket(STANDBY_BUCKET_FREQUENT);
728    }
729
730    @Test
731    public void testAddActiveDeviceAdmin() {
732        assertActiveAdmins(USER_ID, (String[]) null);
733        assertActiveAdmins(USER_ID2, (String[]) null);
734
735        mController.addActiveDeviceAdmin(ADMIN_PKG, USER_ID);
736        assertActiveAdmins(USER_ID, ADMIN_PKG);
737        assertActiveAdmins(USER_ID2, (String[]) null);
738
739        mController.addActiveDeviceAdmin(ADMIN_PKG, USER_ID);
740        assertActiveAdmins(USER_ID, ADMIN_PKG);
741        assertActiveAdmins(USER_ID2, (String[]) null);
742
743        mController.addActiveDeviceAdmin(ADMIN_PKG2, USER_ID2);
744        assertActiveAdmins(USER_ID, ADMIN_PKG);
745        assertActiveAdmins(USER_ID2, ADMIN_PKG2);
746    }
747
748    @Test
749    public void testSetActiveAdminApps() {
750        assertActiveAdmins(USER_ID, (String[]) null);
751        assertActiveAdmins(USER_ID2, (String[]) null);
752
753        setActiveAdmins(USER_ID, ADMIN_PKG, ADMIN_PKG2);
754        assertActiveAdmins(USER_ID, ADMIN_PKG, ADMIN_PKG2);
755        assertActiveAdmins(USER_ID2, (String[]) null);
756
757        mController.addActiveDeviceAdmin(ADMIN_PKG2, USER_ID2);
758        setActiveAdmins(USER_ID2, ADMIN_PKG);
759        assertActiveAdmins(USER_ID, ADMIN_PKG, ADMIN_PKG2);
760        assertActiveAdmins(USER_ID2, ADMIN_PKG);
761
762        mController.setActiveAdminApps(null, USER_ID);
763        assertActiveAdmins(USER_ID, (String[]) null);
764    }
765
766    @Test
767    public void isActiveDeviceAdmin() {
768        assertActiveAdmins(USER_ID, (String[]) null);
769        assertActiveAdmins(USER_ID2, (String[]) null);
770
771        mController.addActiveDeviceAdmin(ADMIN_PKG, USER_ID);
772        assertIsActiveAdmin(ADMIN_PKG, USER_ID);
773        assertIsNotActiveAdmin(ADMIN_PKG, USER_ID2);
774
775        mController.addActiveDeviceAdmin(ADMIN_PKG2, USER_ID2);
776        mController.addActiveDeviceAdmin(ADMIN_PKG, USER_ID2);
777        assertIsActiveAdmin(ADMIN_PKG, USER_ID);
778        assertIsNotActiveAdmin(ADMIN_PKG2, USER_ID);
779        assertIsActiveAdmin(ADMIN_PKG, USER_ID2);
780        assertIsActiveAdmin(ADMIN_PKG2, USER_ID2);
781
782        setActiveAdmins(USER_ID2, ADMIN_PKG2);
783        assertIsActiveAdmin(ADMIN_PKG2, USER_ID2);
784        assertIsNotActiveAdmin(ADMIN_PKG, USER_ID2);
785        assertIsActiveAdmin(ADMIN_PKG, USER_ID);
786        assertIsNotActiveAdmin(ADMIN_PKG2, USER_ID);
787    }
788
789    private String getAdminAppsStr(int userId) {
790        return getAdminAppsStr(userId, mController.getActiveAdminAppsForTest(userId));
791    }
792
793    private String getAdminAppsStr(int userId, Set<String> adminApps) {
794        return "admin apps for u" + userId + ": "
795                + (adminApps == null ? "null" : Arrays.toString(adminApps.toArray()));
796    }
797
798    private void assertIsActiveAdmin(String adminApp, int userId) {
799        assertTrue(adminApp + " should be an active admin; " + getAdminAppsStr(userId),
800                mController.isActiveDeviceAdmin(adminApp, userId));
801    }
802
803    private void assertIsNotActiveAdmin(String adminApp, int userId) {
804        assertFalse(adminApp + " shouldn't be an active admin; " + getAdminAppsStr(userId),
805                mController.isActiveDeviceAdmin(adminApp, userId));
806    }
807
808    private void assertActiveAdmins(int userId, String... admins) {
809        final Set<String> actualAdminApps = mController.getActiveAdminAppsForTest(userId);
810        if (admins == null) {
811            if (actualAdminApps != null && !actualAdminApps.isEmpty()) {
812                fail("Admin apps should be null; " + getAdminAppsStr(userId, actualAdminApps));
813            }
814            return;
815        }
816        assertEquals("No. of admin apps not equal; " + getAdminAppsStr(userId, actualAdminApps)
817                + "; expected=" + Arrays.toString(admins), admins.length, actualAdminApps.size());
818        final Set<String> adminAppsCopy = new ArraySet<>(actualAdminApps);
819        for (String admin : admins) {
820            adminAppsCopy.remove(admin);
821        }
822        assertTrue("Unexpected admin apps; " + getAdminAppsStr(userId, actualAdminApps)
823                + "; expected=" + Arrays.toString(admins), adminAppsCopy.isEmpty());
824    }
825
826    private void setActiveAdmins(int userId, String... admins) {
827        mController.setActiveAdminApps(new ArraySet<>(Arrays.asList(admins)), userId);
828    }
829}
830