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