Files
Lawnchair/tests/multivalentTests/src/com/android/launcher3/util/LauncherModelHelper.java
T
Sunny Goyal 993893895c Converting ModelTask to an interface instead of an abstract class
This allows extensibility and better use of lambdas

Bug: 338282246
Test: Presubmit
Flag: None

Change-Id: Ia41067f0068b3b631eeb4faf877dc77f8587e1f6
2024-05-01 15:43:26 -07:00

310 lines
12 KiB
Java

/*
* 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.launcher3.util;
import static android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.TestUtil.runOnExecutorSync;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.spy;
import android.content.ContentProvider;
import android.content.ContentResolver;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Color;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
import android.provider.Settings;
import android.test.mock.MockContentResolver;
import android.util.ArrayMap;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.uiautomator.UiDevice;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.model.BgDataModel;
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.testing.TestInformationProvider;
import com.android.launcher3.util.MainThreadInitializedObject.SandboxContext;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
/**
* Utility class to help manage Launcher Model and related objects for test.
*/
public class LauncherModelHelper {
public static final String TEST_PACKAGE = getInstrumentation().getContext().getPackageName();
public static final String TEST_ACTIVITY = "com.android.launcher3.tests.Activity2";
public static final String TEST_ACTIVITY2 = "com.android.launcher3.tests.Activity3";
public static final String TEST_ACTIVITY3 = "com.android.launcher3.tests.Activity4";
public static final String TEST_ACTIVITY4 = "com.android.launcher3.tests.Activity5";
public static final String TEST_ACTIVITY5 = "com.android.launcher3.tests.Activity6";
public static final String TEST_ACTIVITY6 = "com.android.launcher3.tests.Activity7";
public static final String TEST_ACTIVITY7 = "com.android.launcher3.tests.Activity8";
public static final String TEST_ACTIVITY8 = "com.android.launcher3.tests.Activity9";
public static final String TEST_ACTIVITY9 = "com.android.launcher3.tests.Activity10";
public static final String TEST_ACTIVITY10 = "com.android.launcher3.tests.Activity11";
public static final String TEST_ACTIVITY11 = "com.android.launcher3.tests.Activity12";
public static final String TEST_ACTIVITY12 = "com.android.launcher3.tests.Activity13";
public static final String TEST_ACTIVITY13 = "com.android.launcher3.tests.Activity14";
public static final String TEST_ACTIVITY14 = "com.android.launcher3.tests.Activity15";
// Authority for providing a test default-workspace-layout data.
private static final String TEST_PROVIDER_AUTHORITY =
LauncherModelHelper.class.getName().toLowerCase();
private static final int DEFAULT_BITMAP_SIZE = 10;
private static final int DEFAULT_GRID_SIZE = 4;
public final SandboxModelContext sandboxContext;
private final RunnableList mDestroyTask = new RunnableList();
private BgDataModel mDataModel;
public LauncherModelHelper() {
sandboxContext = new SandboxModelContext();
}
public void setupProvider(String authority, ContentProvider provider) {
sandboxContext.setupProvider(authority, provider);
}
public LauncherModel getModel() {
return LauncherAppState.getInstance(sandboxContext).getModel();
}
public synchronized BgDataModel getBgDataModel() {
if (mDataModel == null) {
getModel().enqueueModelUpdateTask((taskController, dataModel, apps) ->
mDataModel = dataModel);
runOnExecutorSync(Executors.MODEL_EXECUTOR, () -> { });
}
return mDataModel;
}
/**
* Creates a installer session for the provided package.
*/
public int createInstallerSession(String pkg) throws IOException {
SessionParams sp = new SessionParams(MODE_FULL_INSTALL);
sp.setAppPackageName(pkg);
Bitmap icon = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
icon.eraseColor(Color.RED);
sp.setAppIcon(icon);
sp.setAppLabel(pkg);
PackageInstaller pi = sandboxContext.getPackageManager().getPackageInstaller();
int sessionId = pi.createSession(sp);
mDestroyTask.add(() -> pi.abandonSession(sessionId));
return sessionId;
}
public void destroy() {
// When destroying the context, make sure that the model thread is blocked, so that no
// new jobs get posted while we are cleaning up
CountDownLatch l1 = new CountDownLatch(1);
CountDownLatch l2 = new CountDownLatch(1);
MODEL_EXECUTOR.execute(() -> {
l1.countDown();
waitOrThrow(l2);
});
waitOrThrow(l1);
sandboxContext.onDestroy();
l2.countDown();
mDestroyTask.executeAllAndDestroy();
}
private void waitOrThrow(CountDownLatch latch) {
try {
latch.await();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Sets up a mock provider to load the provided layout by default, next time the layout loads
*/
public LauncherModelHelper setupDefaultLayoutProvider(LauncherLayoutBuilder builder)
throws Exception {
InvariantDeviceProfile idp = InvariantDeviceProfile.INSTANCE.get(sandboxContext);
idp.numRows = idp.numColumns = idp.numDatabaseHotseatIcons = DEFAULT_GRID_SIZE;
idp.iconBitmapSize = DEFAULT_BITMAP_SIZE;
UiDevice.getInstance(getInstrumentation()).executeShellCommand(
"settings put secure launcher3.layout.provider " + TEST_PROVIDER_AUTHORITY);
ContentProvider cp = new TestInformationProvider() {
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
try {
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
AutoCloseOutputStream outputStream = new AutoCloseOutputStream(pipe[1]);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
builder.build(new OutputStreamWriter(bos));
outputStream.write(bos.toByteArray());
outputStream.flush();
outputStream.close();
return pipe[0];
} catch (Exception e) {
throw new FileNotFoundException(e.getMessage());
}
}
};
setupProvider(TEST_PROVIDER_AUTHORITY, cp);
mDestroyTask.add(() -> runOnExecutorSync(MODEL_EXECUTOR, () ->
UiDevice.getInstance(getInstrumentation()).executeShellCommand(
"settings delete secure launcher3.layout.provider")));
return this;
}
/**
* Loads the model in memory synchronously
*/
public void loadModelSync() throws ExecutionException, InterruptedException {
Callbacks mockCb = new Callbacks() { };
MAIN_EXECUTOR.submit(() -> getModel().addCallbacksAndLoad(mockCb)).get();
Executors.MODEL_EXECUTOR.submit(() -> { }).get();
MAIN_EXECUTOR.submit(() -> { }).get();
MAIN_EXECUTOR.submit(() -> getModel().removeCallbacks(mockCb)).get();
}
public static class SandboxModelContext extends SandboxContext {
private final MockContentResolver mMockResolver = new MockContentResolver();
private final ArrayMap<String, Object> mSpiedServices = new ArrayMap<>();
private final PackageManager mPm;
private final File mDbDir;
public SandboxModelContext() {
super(ApplicationProvider.getApplicationContext());
// System settings cache content provider. Ensure that they are statically initialized
Settings.Secure.getString(
ApplicationProvider.getApplicationContext().getContentResolver(), "test");
Settings.System.getString(
ApplicationProvider.getApplicationContext().getContentResolver(), "test");
Settings.Global.getString(
ApplicationProvider.getApplicationContext().getContentResolver(), "test");
mPm = spy(getBaseContext().getPackageManager());
mDbDir = new File(getCacheDir(), UUID.randomUUID().toString());
}
@Override
public <T extends SafeCloseable> T createObject(MainThreadInitializedObject<T> object) {
if (object == LauncherAppState.INSTANCE) {
return (T) new LauncherAppState(this, null /* iconCacheFileName */);
}
return super.createObject(object);
}
@Override
public File getDatabasePath(String name) {
if (!mDbDir.exists()) {
mDbDir.mkdirs();
}
return new File(mDbDir, name);
}
@Override
public ContentResolver getContentResolver() {
return mMockResolver;
}
@Override
public void onDestroy() {
if (deleteContents(mDbDir)) {
mDbDir.delete();
}
super.onDestroy();
}
@Override
public PackageManager getPackageManager() {
return mPm;
}
@Override
public Object getSystemService(String name) {
Object service = mSpiedServices.get(name);
return service != null ? service : super.getSystemService(name);
}
public <T> T spyService(Class<T> tClass) {
String name = getSystemServiceName(tClass);
Object service = mSpiedServices.get(name);
if (service != null) {
return (T) service;
}
T result = spy(getSystemService(tClass));
mSpiedServices.put(name, result);
return result;
}
public void setupProvider(String authority, ContentProvider provider) {
ProviderInfo providerInfo = new ProviderInfo();
providerInfo.authority = authority;
providerInfo.applicationInfo = getApplicationInfo();
provider.attachInfo(this, providerInfo);
mMockResolver.addProvider(providerInfo.authority, provider);
doReturn(providerInfo).when(mPm).resolveContentProvider(eq(authority), anyInt());
}
private static boolean deleteContents(File dir) {
File[] files = dir.listFiles();
boolean success = true;
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
success &= deleteContents(file);
}
if (!file.delete()) {
success = false;
}
}
}
return success;
}
}
}