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 
17 package com.android.internal.os;
18 
19 import static org.junit.Assert.assertNotNull;
20 import static org.junit.Assert.assertTrue;
21 import static org.junit.Assume.assumeTrue;
22 
23 import android.app.ActivityManager;
24 import android.app.IActivityManager;
25 import android.app.IStopUserCallback;
26 import android.content.Context;
27 import android.content.pm.UserInfo;
28 import android.os.RemoteException;
29 import android.os.UserHandle;
30 import android.os.UserManager;
31 import android.support.test.uiautomator.UiDevice;
32 import android.util.ArraySet;
33 
34 import androidx.test.InstrumentationRegistry;
35 import androidx.test.filters.LargeTest;
36 import androidx.test.runner.AndroidJUnit4;
37 
38 import org.junit.After;
39 import org.junit.Before;
40 import org.junit.BeforeClass;
41 import org.junit.Test;
42 import org.junit.runner.RunWith;
43 
44 import java.util.concurrent.CountDownLatch;
45 import java.util.concurrent.TimeUnit;
46 
47 @LargeTest
48 @RunWith(AndroidJUnit4.class)
49 public class BatteryStatsUserLifecycleTests {
50 
51     private static final long POLL_INTERVAL_MS = 500;
52     private static final long USER_REMOVE_TIMEOUT_MS = 5_000;
53     private static final long STOP_USER_TIMEOUT_MS = 10_000;
54     private static final long BATTERYSTATS_POLLING_TIMEOUT_MS = 5_000;
55 
56     private static final String CPU_DATA_TAG = "cpu";
57     private static final String CPU_FREQ_DATA_TAG = "ctf";
58 
59     private int mTestUserId = UserHandle.USER_NULL;
60     private Context mContext;
61     private UserManager mUm;
62     private IActivityManager mIam;
63 
64     @BeforeClass
setUpOnce()65     public static void setUpOnce() {
66         assumeTrue(UserManager.getMaxSupportedUsers() > 1);
67     }
68 
69     @Before
setUp()70     public void setUp() throws Exception {
71         mContext = InstrumentationRegistry.getTargetContext();
72         mUm = UserManager.get(mContext);
73         mIam = ActivityManager.getService();
74         final UserInfo user = mUm.createUser("Test_user_" + System.currentTimeMillis() / 1000, 0);
75         assertNotNull("Unable to create test user", user);
76         mTestUserId = user.id;
77         batteryOnScreenOff();
78     }
79 
80     @Test
testNoCpuDataForRemovedUser()81     public void testNoCpuDataForRemovedUser() throws Exception {
82         mIam.startUserInBackground(mTestUserId);
83         waitUntilTrue("No uids for started user " + mTestUserId,
84                 () -> getNumberOfUidsInBatteryStats() > 0, BATTERYSTATS_POLLING_TIMEOUT_MS);
85 
86         CountDownLatch stopUserLatch = new CountDownLatch(1);
87         mIam.stopUser(mTestUserId, true, new IStopUserCallback.Stub() {
88             @Override
89             public void userStopped(int userId) throws RemoteException {
90                 stopUserLatch.countDown();
91             }
92 
93             @Override
94             public void userStopAborted(int userId) throws RemoteException {
95             }
96         });
97         assertTrue("User " + mTestUserId + " could not be stopped",
98                 stopUserLatch.await(STOP_USER_TIMEOUT_MS, TimeUnit.MILLISECONDS));
99 
100         mUm.removeUser(mTestUserId);
101         waitUntilTrue("Unable to remove user " + mTestUserId, () -> {
102             for (UserInfo user : mUm.getUsers()) {
103                 if (user.id == mTestUserId) {
104                     return false;
105                 }
106             }
107             return true;
108         }, USER_REMOVE_TIMEOUT_MS);
109         waitUntilTrue("Uids still found for removed user " + mTestUserId,
110                 () -> getNumberOfUidsInBatteryStats() == 0, BATTERYSTATS_POLLING_TIMEOUT_MS);
111     }
112 
113     @After
tearDown()114     public void tearDown() throws Exception {
115         batteryOffScreenOn();
116         if (mTestUserId != UserHandle.USER_NULL) {
117             mUm.removeUser(mTestUserId);
118         }
119     }
120 
getNumberOfUidsInBatteryStats()121     private int getNumberOfUidsInBatteryStats() throws Exception {
122         ArraySet<Integer> uids = new ArraySet<>();
123         final String dumpsys = executeShellCommand("dumpsys batterystats --checkin");
124         for (String line : dumpsys.split("\n")) {
125             final String[] parts = line.trim().split(",");
126             if (parts.length < 5 ||
127                     (!parts[3].equals(CPU_DATA_TAG) && !parts[3].equals(CPU_FREQ_DATA_TAG))) {
128                 continue;
129             }
130             try {
131                 final int uid = Integer.parseInt(parts[1]);
132                 if (UserHandle.getUserId(uid) == mTestUserId) {
133                     uids.add(uid);
134                 }
135             } catch (NumberFormatException nexc) {
136                 // ignore
137             }
138         }
139         return uids.size();
140     }
141 
batteryOnScreenOff()142     protected void batteryOnScreenOff() throws Exception {
143         executeShellCommand("dumpsys battery unplug");
144         executeShellCommand("dumpsys batterystats enable pretend-screen-off");
145     }
146 
batteryOffScreenOn()147     protected void batteryOffScreenOn() throws Exception {
148         executeShellCommand("dumpsys battery reset");
149         executeShellCommand("dumpsys batterystats disable pretend-screen-off");
150     }
151 
executeShellCommand(String cmd)152     private String executeShellCommand(String cmd) throws Exception {
153         return UiDevice.getInstance(
154                 InstrumentationRegistry.getInstrumentation()).executeShellCommand(cmd);
155     }
156 
waitUntilTrue(String message, Condition condition, long timeout)157     private void waitUntilTrue(String message, Condition condition, long timeout) throws Exception {
158         final long deadLine = System.currentTimeMillis() + timeout;
159         while (System.currentTimeMillis() <= deadLine && !condition.isTrue()) {
160             Thread.sleep(POLL_INTERVAL_MS);
161         }
162         assertTrue(message, condition.isTrue());
163     }
164 
165     private interface Condition {
isTrue()166         boolean isTrue() throws Exception;
167     }
168 }
169