diff options
author | Kevin Han <kevhan@google.com> | 2021-01-28 14:13:10 -0800 |
---|---|---|
committer | Kevin Han <kevhan@google.com> | 2021-02-03 18:17:25 -0800 |
commit | dba83c49dd7566061d905412e4e6f71103c457af (patch) | |
tree | 187514005aed37b8d30ffaabdbb46ff5cbf4529b /services/tests | |
parent | 6669dec038ffe7a4a328687d73562bfe7600cf0d (diff) |
Add persistence for app hibernation states
Persist hibernation states to disk. This CL persists the user-level
and global hibernation states to disk.
Bug: 175829330
Test: atest AppHibernationServiceTest
Test: atest HibernationStateDiskStoreTest
Change-Id: If58d648d720bed1693b9346c4d0e85074daec931
Diffstat (limited to 'services/tests')
2 files changed, 262 insertions, 2 deletions
diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java index f2e09984728f..1328b91d03f9 100644 --- a/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/apphibernation/AppHibernationServiceTest.java @@ -16,12 +16,15 @@ package com.android.server.apphibernation; +import static android.content.pm.PackageManager.MATCH_ANY_USER; + import static org.junit.Assert.assertTrue; import static org.mockito.AdditionalAnswers.returnsArgAt; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.intThat; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; @@ -47,6 +50,7 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; +import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import java.util.ArrayList; @@ -75,11 +79,15 @@ public final class AppHibernationServiceTest { private IActivityManager mIActivityManager; @Mock private UserManager mUserManager; + @Mock + private HibernationStateDiskStore<UserLevelState> mHibernationStateDiskStore; @Captor private ArgumentCaptor<BroadcastReceiver> mReceiverCaptor; @Before public void setUp() throws RemoteException { + // Share class loader to allow access to package-private classes + System.setProperty("dexmaker.share_classloader", "true"); MockitoAnnotations.initMocks(this); doReturn(mContext).when(mContext).createContextAsUser(any(), anyInt()); @@ -93,6 +101,12 @@ public final class AppHibernationServiceTest { doAnswer(returnsArgAt(2)).when(mIActivityManager).handleIncomingUser(anyInt(), anyInt(), anyInt(), anyBoolean(), anyBoolean(), any(), any()); + List<PackageInfo> packages = new ArrayList<>(); + packages.add(makePackageInfo(PACKAGE_NAME_1)); + doReturn(new ParceledListSlice<>(packages)).when(mIPackageManager).getInstalledPackages( + intThat(arg -> (arg & MATCH_ANY_USER) != 0), anyInt()); + mAppHibernationService.onBootPhase(SystemService.PHASE_BOOT_COMPLETED); + UserInfo userInfo = addUser(USER_ID_1); mAppHibernationService.onUserUnlocking(new SystemService.TargetUser(userInfo)); doReturn(true).when(mUserManager).isUserUnlockingOrUnlocked(USER_ID_1); @@ -151,7 +165,7 @@ public final class AppHibernationServiceTest { } @Test - public void testSetHibernatingGlobally_packageIsHibernatingGlobally() { + public void testSetHibernatingGlobally_packageIsHibernatingGlobally() throws RemoteException { // WHEN we hibernate a package mAppHibernationService.setHibernatingGlobally(PACKAGE_NAME_1, true); @@ -177,7 +191,7 @@ public final class AppHibernationServiceTest { userPackages.add(makePackageInfo(pkgName)); } doReturn(new ParceledListSlice<>(userPackages)).when(mIPackageManager) - .getInstalledPackages(anyInt(), eq(userId)); + .getInstalledPackages(intThat(arg -> (arg & MATCH_ANY_USER) == 0), eq(userId)); return userInfo; } @@ -213,5 +227,15 @@ public final class AppHibernationServiceTest { public UserManager getUserManager() { return mUserManager; } + + @Override + public HibernationStateDiskStore<GlobalLevelState> getGlobalLevelDiskStore() { + return Mockito.mock(HibernationStateDiskStore.class); + } + + @Override + public HibernationStateDiskStore<UserLevelState> getUserLevelDiskStore(int userId) { + return Mockito.mock(HibernationStateDiskStore.class); + } } } diff --git a/services/tests/servicestests/src/com/android/server/apphibernation/HibernationStateDiskStoreTest.java b/services/tests/servicestests/src/com/android/server/apphibernation/HibernationStateDiskStoreTest.java new file mode 100644 index 000000000000..59f3c35f2137 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/apphibernation/HibernationStateDiskStoreTest.java @@ -0,0 +1,236 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.apphibernation; + +import static org.junit.Assert.assertEquals; + +import android.os.FileUtils; +import android.util.proto.ProtoInputStream; +import android.util.proto.ProtoOutputStream; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.test.InstrumentationRegistry; +import androidx.test.filters.SmallTest; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + + +@SmallTest +public class HibernationStateDiskStoreTest { + private static final String STATES_FILE_NAME = "states"; + private final MockScheduledExecutorService mMockScheduledExecutorService = + new MockScheduledExecutorService(); + + private File mFile; + private HibernationStateDiskStore<String> mHibernationStateDiskStore; + + + @Before + public void setUp() { + mFile = new File(InstrumentationRegistry.getContext().getCacheDir(), "test"); + mHibernationStateDiskStore = new HibernationStateDiskStore<>(mFile, + new MockProtoReadWriter(), mMockScheduledExecutorService, STATES_FILE_NAME); + } + + @After + public void tearDown() { + FileUtils.deleteContentsAndDir(mFile); + } + + @Test + public void testScheduleWriteHibernationStates_writesDataThatCanBeRead() { + // GIVEN some data to be written + List<String> toWrite = new ArrayList<>(Arrays.asList("A", "B")); + + // WHEN the data is written + mHibernationStateDiskStore.scheduleWriteHibernationStates(toWrite); + mMockScheduledExecutorService.executeScheduledTask(); + + // THEN the read data is equal to what was written + List<String> storedStrings = mHibernationStateDiskStore.readHibernationStates(); + for (int i = 0; i < toWrite.size(); i++) { + assertEquals(toWrite.get(i), storedStrings.get(i)); + } + } + + @Test + public void testScheduleWriteHibernationStates_laterWritesOverwritePrevious() { + // GIVEN store has some data it is scheduled to write + mHibernationStateDiskStore.scheduleWriteHibernationStates( + new ArrayList<>(Arrays.asList("C", "D"))); + + // WHEN a write is scheduled with new data + List<String> toWrite = new ArrayList<>(Arrays.asList("A", "B")); + mHibernationStateDiskStore.scheduleWriteHibernationStates(toWrite); + mMockScheduledExecutorService.executeScheduledTask(); + + // THEN the written data is the last scheduled data + List<String> storedStrings = mHibernationStateDiskStore.readHibernationStates(); + for (int i = 0; i < toWrite.size(); i++) { + assertEquals(toWrite.get(i), storedStrings.get(i)); + } + } + + /** + * Mock proto read / writer that just writes and reads a list of String data. + */ + private final class MockProtoReadWriter implements ProtoReadWriter<List<String>> { + private static final long FIELD_ID = 1; + + @Override + public void writeToProto(@NonNull ProtoOutputStream stream, + @NonNull List<String> data) { + for (int i = 0, size = data.size(); i < size; i++) { + stream.write(FIELD_ID, data.get(i)); + } + } + + @Nullable + @Override + public List<String> readFromProto(@NonNull ProtoInputStream stream) + throws IOException { + ArrayList<String> list = new ArrayList<>(); + while (stream.nextField() != ProtoInputStream.NO_MORE_FIELDS) { + list.add(stream.readString(FIELD_ID)); + } + return list; + } + } + + /** + * Mock scheduled executor service that has minimum implementation and can synchronously + * execute scheduled tasks. + */ + private final class MockScheduledExecutorService implements ScheduledExecutorService { + + Runnable mScheduledRunnable = null; + + @Override + public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) { + mScheduledRunnable = command; + return Mockito.mock(ScheduledFuture.class); + } + + @Override + public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, + long period, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, + long delay, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public void shutdown() { + throw new UnsupportedOperationException(); + } + + @Override + public List<Runnable> shutdownNow() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isShutdown() { + return false; + } + + @Override + public boolean isTerminated() { + return false; + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> Future<T> submit(Callable<T> task) { + throw new UnsupportedOperationException(); + } + + @Override + public <T> Future<T> submit(Runnable task, T result) { + throw new UnsupportedOperationException(); + } + + @Override + public Future<?> submit(Runnable task) { + throw new UnsupportedOperationException(); + } + + @Override + public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) + throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, + TimeUnit unit) throws InterruptedException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> T invokeAny(Collection<? extends Callable<T>> tasks) + throws InterruptedException, ExecutionException { + throw new UnsupportedOperationException(); + } + + @Override + public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { + throw new UnsupportedOperationException(); + } + + @Override + public void execute(Runnable command) { + throw new UnsupportedOperationException(); + } + + void executeScheduledTask() { + mScheduledRunnable.run(); + } + } +} |