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