blob: fe23c14a0b954e53eae0354fda8d3b6439cffe40 [file] [log] [blame]
/*
* Copyright (C) 2019 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.wallpaper;
import static android.app.WallpaperManager.COMMAND_REAPPLY;
import static android.app.WallpaperManager.FLAG_SYSTEM;
import static android.os.FileObserver.CLOSE_WRITE;
import static android.os.UserHandle.USER_SYSTEM;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
import static com.android.server.wallpaper.WallpaperManagerService.WALLPAPER;
import static com.android.server.wallpaper.WallpaperManagerService.WALLPAPER_CROP;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeThat;
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.Mockito.reset;
import static org.mockito.Mockito.verify;
import android.app.AppGlobals;
import android.app.AppOpsManager;
import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ParceledListSlice;
import android.content.pm.ServiceInfo;
import android.graphics.Color;
import android.hardware.display.DisplayManager;
import android.os.RemoteException;
import android.os.SystemClock;
import android.platform.test.annotations.Presubmit;
import android.service.wallpaper.IWallpaperConnection;
import android.service.wallpaper.IWallpaperEngine;
import android.service.wallpaper.WallpaperService;
import android.testing.TestableContext;
import android.util.Log;
import android.util.SparseArray;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
import android.view.Display;
import androidx.test.filters.FlakyTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
import com.android.dx.mockito.inline.extended.StaticMockitoSession;
import com.android.internal.R;
import com.android.server.LocalServices;
import com.android.server.wallpaper.WallpaperManagerService.WallpaperData;
import com.android.server.wm.WindowManagerInternal;
import org.hamcrest.CoreMatchers;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.quality.Strictness;
import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* Tests for the {@link WallpaperManagerService} class.
*
* Build/Install/Run:
* atest FrameworksMockingServicesTests:WallpaperManagerServiceTests
*/
@Presubmit
@FlakyTest(bugId = 129797242)
@RunWith(AndroidJUnit4.class)
public class WallpaperManagerServiceTests {
private static final int DISPLAY_SIZE_DIMENSION = 100;
private static StaticMockitoSession sMockitoSession;
@ClassRule
public static final TestableContext sContext = new TestableContext(
InstrumentationRegistry.getInstrumentation().getTargetContext(), null);
private static ComponentName sImageWallpaperComponentName;
private static ComponentName sDefaultWallpaperComponent;
private IPackageManager mIpm = AppGlobals.getPackageManager();
@Mock
private DisplayManager mDisplayManager;
@Rule
public final TemporaryFolder mFolder = new TemporaryFolder();
private final SparseArray<File> mTempDirs = new SparseArray<>();
private WallpaperManagerService mService;
private static IWallpaperConnection.Stub sWallpaperService;
@BeforeClass
public static void setUpClass() {
sMockitoSession = mockitoSession()
.strictness(Strictness.LENIENT)
.spyStatic(LocalServices.class)
.spyStatic(WallpaperManager.class)
.startMocking();
final WindowManagerInternal dmi = mock(WindowManagerInternal.class);
LocalServices.addService(WindowManagerInternal.class, dmi);
sContext.addMockSystemService(Context.APP_OPS_SERVICE, mock(AppOpsManager.class));
spyOn(sContext);
sContext.getTestablePermissions().setPermission(
android.Manifest.permission.SET_WALLPAPER_COMPONENT,
PackageManager.PERMISSION_GRANTED);
sContext.getTestablePermissions().setPermission(
android.Manifest.permission.SET_WALLPAPER,
PackageManager.PERMISSION_GRANTED);
doNothing().when(sContext).sendBroadcastAsUser(any(), any());
//Wallpaper components
sWallpaperService = mock(IWallpaperConnection.Stub.class);
sImageWallpaperComponentName = ComponentName.unflattenFromString(
sContext.getResources().getString(R.string.image_wallpaper_component));
// Mock default wallpaper as image wallpaper if there is no pre-defined default wallpaper.
sDefaultWallpaperComponent = WallpaperManager.getDefaultWallpaperComponent(sContext);
if (sDefaultWallpaperComponent == null) {
sDefaultWallpaperComponent = sImageWallpaperComponentName;
doReturn(sImageWallpaperComponentName).when(() ->
WallpaperManager.getDefaultWallpaperComponent(any()));
} else {
sContext.addMockService(sDefaultWallpaperComponent, sWallpaperService);
}
sContext.addMockService(sImageWallpaperComponentName, sWallpaperService);
}
@AfterClass
public static void tearDownClass() {
if (sMockitoSession != null) {
sMockitoSession.finishMocking();
sMockitoSession = null;
}
LocalServices.removeServiceForTest(WindowManagerInternal.class);
sImageWallpaperComponentName = null;
sDefaultWallpaperComponent = null;
reset(sContext);
}
@Before
public void setUp() {
MockitoAnnotations.initMocks(this);
sContext.addMockSystemService(DisplayManager.class, mDisplayManager);
final Display mockDisplay = mock(Display.class);
doReturn(DISPLAY_SIZE_DIMENSION).when(mockDisplay).getMaximumSizeDimension();
doReturn(mockDisplay).when(mDisplayManager).getDisplay(anyInt());
final Display[] displays = new Display[]{mockDisplay};
doReturn(displays).when(mDisplayManager).getDisplays();
spyOn(mIpm);
mService = new TestWallpaperManagerService(sContext);
spyOn(mService);
mService.systemReady();
}
@After
public void tearDown() {
LocalServices.removeServiceForTest(WallpaperManagerInternal.class);
mTempDirs.clear();
reset(mIpm);
mService = null;
}
protected class TestWallpaperManagerService extends WallpaperManagerService {
private static final String TAG = "TestWallpaperManagerService";
TestWallpaperManagerService(Context context) {
super(context);
}
@Override
File getWallpaperDir(int userId) {
File tempDir = mTempDirs.get(userId);
if (tempDir == null) {
try {
tempDir = mFolder.newFolder(String.valueOf(userId));
mTempDirs.append(userId, tempDir);
} catch (IOException e) {
Log.e(TAG, "getWallpaperDir failed at userId= " + userId);
}
}
return tempDir;
}
// Always return true for test
@Override
public boolean isWallpaperSupported(String callingPackage) {
return true;
}
// Always return true for test
@Override
public boolean isSetWallpaperAllowed(String callingPackage) {
return true;
}
}
/**
* Tests that internal basic data should be correct after boot up.
*/
@Test
public void testDataCorrectAfterBoot() {
mService.switchUser(USER_SYSTEM, null);
final WallpaperData fallbackData = mService.mFallbackWallpaper;
assertEquals("Fallback wallpaper component should be ImageWallpaper.",
sImageWallpaperComponentName, fallbackData.wallpaperComponent);
verifyLastWallpaperData(USER_SYSTEM, sDefaultWallpaperComponent);
verifyDisplayData();
}
/**
* Tests setWallpaperComponent and clearWallpaper should work as expected.
*/
@Test
public void testSetThenClearComponent() {
// Skip if there is no pre-defined default wallpaper component.
assumeThat(sDefaultWallpaperComponent,
not(CoreMatchers.equalTo(sImageWallpaperComponentName)));
final int testUserId = USER_SYSTEM;
mService.switchUser(testUserId, null);
verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent);
verifyCurrentSystemData(testUserId);
mService.setWallpaperComponent(sImageWallpaperComponentName);
verifyLastWallpaperData(testUserId, sImageWallpaperComponentName);
verifyCurrentSystemData(testUserId);
mService.clearWallpaper(null, FLAG_SYSTEM, testUserId);
verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent);
verifyCurrentSystemData(testUserId);
}
/**
* Tests that when setWallpaperComponent is called with the currently set component, a command
* is issued to the wallpaper.
*/
@Test
public void testSetCurrentComponent() throws Exception {
final int testUserId = USER_SYSTEM;
mService.switchUser(testUserId, null);
verifyLastWallpaperData(testUserId, sDefaultWallpaperComponent);
verifyCurrentSystemData(testUserId);
spyOn(mService.mLastWallpaper.connection);
doReturn(true).when(mService.mLastWallpaper.connection).isUsableDisplay(any());
mService.mLastWallpaper.connection.attachEngine(mock(IWallpaperEngine.class),
DEFAULT_DISPLAY);
WallpaperManagerService.WallpaperConnection.DisplayConnector connector =
mService.mLastWallpaper.connection.getDisplayConnectorOrCreate(DEFAULT_DISPLAY);
mService.setWallpaperComponent(sDefaultWallpaperComponent);
verify(connector.mEngine).dispatchWallpaperCommand(
eq(COMMAND_REAPPLY), anyInt(), anyInt(), anyInt(), any());
}
/**
* Tests internal data should be correct and no crash after switch user continuously.
*/
@Test
public void testSwitchMultipleUsers() throws Exception {
final int lastUserId = 5;
final ServiceInfo pi = mIpm.getServiceInfo(sDefaultWallpaperComponent,
PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, 0);
doReturn(pi).when(mIpm).getServiceInfo(any(), anyInt(), anyInt());
final Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
final ParceledListSlice ris =
mIpm.queryIntentServices(intent,
intent.resolveTypeIfNeeded(sContext.getContentResolver()),
PackageManager.GET_META_DATA, 0);
doReturn(ris).when(mIpm).queryIntentServices(any(), any(), anyInt(), anyInt());
doReturn(PackageManager.PERMISSION_GRANTED).when(mIpm).checkPermission(
eq(android.Manifest.permission.AMBIENT_WALLPAPER), any(), anyInt());
for (int userId = 0; userId <= lastUserId; userId++) {
mService.switchUser(userId, null);
verifyLastWallpaperData(userId, sDefaultWallpaperComponent);
verifyCurrentSystemData(userId);
}
verifyNoConnectionBeforeLastUser(lastUserId);
}
/**
* Tests internal data should be correct and no crash after switch user + unlock user
* continuously.
* Simulating that the selected WallpaperService is not built-in. After switching users, the
* service should not be bound, but bound to the image wallpaper. After receiving the user
* unlock callback and can find the selected service for the user, the selected service should
* be bound.
*/
@Test
public void testSwitchThenUnlockMultipleUsers() throws Exception {
final int lastUserId = 5;
final ServiceInfo pi = mIpm.getServiceInfo(sDefaultWallpaperComponent,
PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, 0);
doReturn(pi).when(mIpm).getServiceInfo(any(), anyInt(), anyInt());
final Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
final ParceledListSlice ris =
mIpm.queryIntentServices(intent,
intent.resolveTypeIfNeeded(sContext.getContentResolver()),
PackageManager.GET_META_DATA, 0);
doReturn(PackageManager.PERMISSION_GRANTED).when(mIpm).checkPermission(
eq(android.Manifest.permission.AMBIENT_WALLPAPER), any(), anyInt());
for (int userId = 1; userId <= lastUserId; userId++) {
mService.switchUser(userId, null);
verifyLastWallpaperData(userId, sImageWallpaperComponentName);
// Simulate user unlocked
doReturn(ris).when(mIpm).queryIntentServices(any(), any(), anyInt(), eq(userId));
mService.onUnlockUser(userId);
verifyLastWallpaperData(userId, sDefaultWallpaperComponent);
verifyCurrentSystemData(userId);
}
verifyNoConnectionBeforeLastUser(lastUserId);
verifyDisplayData();
}
@Test
public void testXmlSerializationRoundtrip() {
WallpaperData systemWallpaperData = mService.getCurrentWallpaperData(FLAG_SYSTEM, 0);
try {
TypedXmlSerializer serializer = Xml.newBinarySerializer();
serializer.setOutput(new ByteArrayOutputStream(), StandardCharsets.UTF_8.name());
serializer.startDocument(StandardCharsets.UTF_8.name(), true);
mService.writeWallpaperAttributes(serializer, "wp", systemWallpaperData);
} catch (IOException e) {
fail("exception occurred while writing system wallpaper attributes");
}
WallpaperData shouldMatchSystem = new WallpaperData(systemWallpaperData.userId,
systemWallpaperData.wallpaperFile.getParentFile(),
systemWallpaperData.wallpaperFile.getAbsolutePath(),
systemWallpaperData.cropFile.getAbsolutePath());
try {
TypedXmlPullParser parser = Xml.newBinaryPullParser();
mService.parseWallpaperAttributes(parser, shouldMatchSystem, true);
} catch (XmlPullParserException e) {
fail("exception occurred while parsing wallpaper");
}
assertEquals(systemWallpaperData.primaryColors, shouldMatchSystem.primaryColors);
}
@Test
public void testWallpaperManagerCallbackInRightOrder() throws RemoteException {
WallpaperData wallpaper = new WallpaperData(
USER_SYSTEM, mService.getWallpaperDir(USER_SYSTEM), WALLPAPER, WALLPAPER_CROP);
wallpaper.primaryColors = new WallpaperColors(Color.valueOf(Color.RED),
Color.valueOf(Color.BLUE), null);
spyOn(wallpaper);
doReturn(wallpaper).when(mService).getWallpaperSafeLocked(wallpaper.userId, FLAG_SYSTEM);
doNothing().when(mService).switchWallpaper(any(), any());
doReturn(true).when(mService)
.bindWallpaperComponentLocked(any(), anyBoolean(), anyBoolean(), any(), any());
doNothing().when(mService).saveSettingsLocked(wallpaper.userId);
doNothing().when(mService).generateCrop(wallpaper);
// timestamps of {ACTION_WALLPAPER_CHANGED, onWallpaperColorsChanged}
final long[] timestamps = new long[2];
doAnswer(invocation -> timestamps[0] = SystemClock.elapsedRealtime())
.when(sContext).sendBroadcastAsUser(any(), any());
doAnswer(invocation -> timestamps[1] = SystemClock.elapsedRealtime())
.when(mService).notifyWallpaperColorsChanged(wallpaper, FLAG_SYSTEM);
assertNull(wallpaper.wallpaperObserver);
mService.switchUser(wallpaper.userId, null);
assertNotNull(wallpaper.wallpaperObserver);
// We will call onEvent directly, so stop watching the file.
wallpaper.wallpaperObserver.stopWatching();
spyOn(wallpaper.wallpaperObserver);
doReturn(wallpaper).when(wallpaper.wallpaperObserver).dataForEvent(true, false);
wallpaper.wallpaperObserver.onEvent(CLOSE_WRITE, WALLPAPER);
// ACTION_WALLPAPER_CHANGED should be invoked before onWallpaperColorsChanged.
assertTrue(timestamps[1] > timestamps[0]);
}
// Verify that after continue switch user from userId 0 to lastUserId, the wallpaper data for
// non-current user must not bind to wallpaper service.
private void verifyNoConnectionBeforeLastUser(int lastUserId) {
for (int i = 0; i < lastUserId; i++) {
final WallpaperData userData = mService.getCurrentWallpaperData(FLAG_SYSTEM, i);
assertNull("No user data connection left", userData.connection);
}
}
private void verifyLastWallpaperData(int lastUserId, ComponentName expectedComponent) {
final WallpaperData lastData = mService.mLastWallpaper;
assertNotNull("Last wallpaper must not be null", lastData);
assertEquals("Last wallpaper component must be equals.", expectedComponent,
lastData.wallpaperComponent);
assertEquals("The user id in last wallpaper should be the last switched user",
lastUserId, lastData.userId);
assertNotNull("Must exist user data connection on last wallpaper data",
lastData.connection);
}
private void verifyCurrentSystemData(int userId) {
final WallpaperData lastData = mService.mLastWallpaper;
final WallpaperData wallpaper = mService.getCurrentWallpaperData(FLAG_SYSTEM, userId);
assertEquals("Last wallpaper should be equals to current system wallpaper",
lastData, wallpaper);
}
private void verifyDisplayData() {
mService.forEachDisplayData(data -> {
assertTrue("Display width must larger than maximum screen size",
data.mWidth >= DISPLAY_SIZE_DIMENSION);
assertTrue("Display height must larger than maximum screen size",
data.mHeight >= DISPLAY_SIZE_DIMENSION);
});
}
}