Merge "Removing all usage of LauncherProvider" into udc-qpr-dev am: cda1842e7a am: 67d7f3789c
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/apps/Launcher3/+/23305873 Change-Id: I6bc91c260f20b0274c20d135bce1324c056b801a Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -31,7 +31,7 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.LauncherModel;
|
||||
import com.android.launcher3.ShortcutAndWidgetContainer;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
|
||||
@@ -189,10 +189,11 @@ public class DebugTestInformationHandler extends TestInformationHandler {
|
||||
case TestProtocol.REQUEST_CLEAR_DATA: {
|
||||
final long identity = Binder.clearCallingIdentity();
|
||||
try {
|
||||
LauncherSettings.Settings.call(mContext.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
|
||||
MAIN_EXECUTOR.submit(() ->
|
||||
LauncherAppState.getInstance(mContext).getModel().forceReload());
|
||||
MODEL_EXECUTOR.execute(() -> {
|
||||
LauncherModel model = LauncherAppState.getInstance(mContext).getModel();
|
||||
model.getModelDbController().createEmptyDB();
|
||||
MAIN_EXECUTOR.execute(model::forceReload);
|
||||
});
|
||||
return response;
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identity);
|
||||
|
||||
@@ -95,9 +95,7 @@ public class HotseatEduController {
|
||||
}
|
||||
}
|
||||
if (pageId == -1) {
|
||||
pageId = LauncherSettings.Settings.call(mLauncher.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
|
||||
.getInt(LauncherSettings.Settings.EXTRA_VALUE);
|
||||
pageId = mLauncher.getModel().getModelDbController().getNewScreenId();
|
||||
mNewScreens = IntArray.wrap(pageId);
|
||||
}
|
||||
boolean isPortrait = !mLauncher.getDeviceProfile().isVerticalBarLayout();
|
||||
|
||||
@@ -22,9 +22,10 @@ import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
import android.content.Context;
|
||||
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.LauncherModel;
|
||||
import com.android.launcher3.model.GridBackupTable;
|
||||
import com.android.launcher3.provider.LauncherDbUtils;
|
||||
import com.android.launcher3.model.ModelDbController;
|
||||
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
|
||||
|
||||
/**
|
||||
* A helper class to manage migration revert restoration for hybrid hotseat
|
||||
@@ -36,16 +37,13 @@ public class HotseatRestoreHelper {
|
||||
*/
|
||||
public static void createBackup(Context context) {
|
||||
MODEL_EXECUTOR.execute(() -> {
|
||||
try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction)
|
||||
LauncherSettings.Settings.call(
|
||||
context.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
|
||||
.getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
|
||||
ModelDbController dbController = LauncherAppState.getInstance(context)
|
||||
.getModel().getModelDbController();
|
||||
try (SQLiteTransaction transaction = dbController.newTransaction()) {
|
||||
GridBackupTable backupTable = new GridBackupTable(context, transaction.getDb());
|
||||
backupTable.createCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE);
|
||||
transaction.commit();
|
||||
LauncherSettings.Settings.call(context.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_REFRESH_HOTSEAT_RESTORE_TABLE);
|
||||
dbController.refreshHotseatRestoreTable();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -55,18 +53,15 @@ public class HotseatRestoreHelper {
|
||||
*/
|
||||
public static void restoreBackup(Context context) {
|
||||
MODEL_EXECUTOR.execute(() -> {
|
||||
try (LauncherDbUtils.SQLiteTransaction transaction = (LauncherDbUtils.SQLiteTransaction)
|
||||
LauncherSettings.Settings.call(
|
||||
context.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_NEW_TRANSACTION)
|
||||
.getBinder(LauncherSettings.Settings.EXTRA_VALUE)) {
|
||||
LauncherModel model = LauncherAppState.getInstance(context).getModel();
|
||||
try (SQLiteTransaction transaction = model.getModelDbController().newTransaction()) {
|
||||
if (!tableExists(transaction.getDb(), HYBRID_HOTSEAT_BACKUP_TABLE)) {
|
||||
return;
|
||||
}
|
||||
GridBackupTable backupTable = new GridBackupTable(context, transaction.getDb());
|
||||
backupTable.restoreFromCustomBackupTable(HYBRID_HOTSEAT_BACKUP_TABLE, true);
|
||||
transaction.commit();
|
||||
LauncherAppState.getInstance(context).getModel().forceReload();
|
||||
model.forceReload();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ package com.android.quickstep;
|
||||
|
||||
import static androidx.test.InstrumentationRegistry.getContext;
|
||||
import static androidx.test.InstrumentationRegistry.getInstrumentation;
|
||||
import static androidx.test.InstrumentationRegistry.getTargetContext;
|
||||
|
||||
import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
|
||||
import static com.android.launcher3.testcomponent.TestCommandReceiver.EXTRA_VALUE;
|
||||
import static com.android.launcher3.testcomponent.TestCommandReceiver.SET_LIST_VIEW_SERVICE_BINDER;
|
||||
import static com.android.launcher3.util.WidgetUtils.createWidgetInfo;
|
||||
@@ -32,7 +32,6 @@ import static org.mockito.Mockito.doAnswer;
|
||||
import static org.mockito.Mockito.spy;
|
||||
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
@@ -42,14 +41,16 @@ import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.filters.Suppress;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
import androidx.test.uiautomator.By;
|
||||
import androidx.test.uiautomator.UiDevice;
|
||||
import androidx.test.uiautomator.Until;
|
||||
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherModel;
|
||||
import com.android.launcher3.celllayout.FavoriteItemsTransaction;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.tapl.LaunchedAppState;
|
||||
import com.android.launcher3.testcomponent.ListViewService;
|
||||
@@ -57,6 +58,7 @@ import com.android.launcher3.testcomponent.ListViewService.SimpleViewsFactory;
|
||||
import com.android.launcher3.testcomponent.TestCommandReceiver;
|
||||
import com.android.launcher3.ui.TaplTestsLauncher3;
|
||||
import com.android.launcher3.ui.TestViewHelpers;
|
||||
import com.android.launcher3.util.Executors;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
|
||||
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
|
||||
|
||||
@@ -67,6 +69,7 @@ import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
/**
|
||||
@@ -84,9 +87,9 @@ import java.util.function.IntConsumer;
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ViewInflationDuringSwipeUp extends AbstractQuickStepTest {
|
||||
|
||||
private ContentResolver mResolver;
|
||||
private SparseArray<ViewConfiguration> mConfigMap;
|
||||
private InitTracker mInitTracker;
|
||||
private LauncherModel mModel;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
@@ -101,8 +104,8 @@ public class ViewInflationDuringSwipeUp extends AbstractQuickStepTest {
|
||||
|
||||
TaplTestsLauncher3.initialize(this);
|
||||
|
||||
mResolver = mTargetContext.getContentResolver();
|
||||
LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
|
||||
mModel = LauncherAppState.getInstance(mTargetContext).getModel();
|
||||
Executors.MODEL_EXECUTOR.submit(mModel.getModelDbController()::createEmptyDB).get();
|
||||
|
||||
// Get static configuration map
|
||||
Field field = ViewConfiguration.class.getDeclaredField("sConfigurations");
|
||||
@@ -182,26 +185,30 @@ public class ViewInflationDuringSwipeUp extends AbstractQuickStepTest {
|
||||
private void executeSwipeUpTestWithWidget(IntConsumer widgetIdCreationCallback,
|
||||
IntConsumer updateBeforeSwipeUp, String finalWidgetText) {
|
||||
try {
|
||||
// Clear all existing data
|
||||
LauncherSettings.Settings.call(mResolver,
|
||||
LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
|
||||
LauncherSettings.Settings.call(mResolver,
|
||||
LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
|
||||
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(false);
|
||||
|
||||
// Make sure the widget is big enough to show a list of items
|
||||
info.minSpanX = 2;
|
||||
info.minSpanY = 2;
|
||||
info.spanX = 2;
|
||||
info.spanY = 2;
|
||||
LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), true);
|
||||
AtomicInteger widgetId = new AtomicInteger();
|
||||
new FavoriteItemsTransaction(mTargetContext)
|
||||
.addItem(() -> {
|
||||
LauncherAppWidgetInfo item = createWidgetInfo(info, mTargetContext, true);
|
||||
item.screenId = FIRST_SCREEN_ID;
|
||||
widgetId.set(item.appWidgetId);
|
||||
return item;
|
||||
})
|
||||
.commitAndLoadHome(mLauncher);
|
||||
|
||||
|
||||
|
||||
addItemToScreen(item);
|
||||
assertTrue("Widget is not present",
|
||||
mLauncher.goHome().tryGetWidget(info.label, DEFAULT_UI_TIMEOUT) != null);
|
||||
int widgetId = item.appWidgetId;
|
||||
|
||||
// Verify widget id
|
||||
widgetIdCreationCallback.accept(widgetId);
|
||||
widgetIdCreationCallback.accept(widgetId.get());
|
||||
|
||||
// Go to overview once so that all views are initialized and cached
|
||||
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
|
||||
@@ -214,7 +221,7 @@ public class ViewInflationDuringSwipeUp extends AbstractQuickStepTest {
|
||||
LaunchedAppState launchedAppState = mLauncher.getLaunchedAppState();
|
||||
|
||||
// Update widget
|
||||
updateBeforeSwipeUp.accept(widgetId);
|
||||
updateBeforeSwipeUp.accept(widgetId.get());
|
||||
|
||||
launchedAppState.switchToOverview();
|
||||
assertEquals("Views inflated during swipe up", 0, mInitTracker.viewInitCount);
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package com.android.launcher3;
|
||||
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
|
||||
import static com.android.launcher3.provider.LauncherDbUtils.itemIdMatch;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentValues;
|
||||
@@ -30,7 +32,6 @@ import android.content.res.Resources;
|
||||
import android.content.res.Resources.NotFoundException;
|
||||
import android.content.res.XmlResourceParser;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.os.Process;
|
||||
import android.text.TextUtils;
|
||||
@@ -44,7 +45,6 @@ import androidx.annotation.StringRes;
|
||||
import androidx.annotation.WorkerThread;
|
||||
import androidx.annotation.XmlRes;
|
||||
|
||||
import com.android.launcher3.LauncherProvider.SqlArguments;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
@@ -619,9 +619,7 @@ public class AutoInstallsLayout {
|
||||
// failed to add, and less than 2 were actually added
|
||||
if (folderItems.size() < 2) {
|
||||
// Delete the folder
|
||||
Uri uri = Favorites.getContentUri(folderId);
|
||||
SqlArguments args = new SqlArguments(uri, null, null);
|
||||
mDb.delete(args.table, args.where, args.args);
|
||||
mDb.delete(TABLE_NAME, itemIdMatch(folderId), null);
|
||||
addedId = -1;
|
||||
|
||||
// If we have a single item, promote it to where the folder
|
||||
@@ -634,7 +632,7 @@ public class AutoInstallsLayout {
|
||||
copyInteger(myValues, childValues, Favorites.CELLY);
|
||||
|
||||
addedId = folderItems.get(0);
|
||||
mDb.update(Favorites.TABLE_NAME, childValues,
|
||||
mDb.update(TABLE_NAME, childValues,
|
||||
Favorites._ID + "=" + addedId, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +143,8 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
|
||||
@NonNull
|
||||
private final ModelDelegate mModelDelegate;
|
||||
|
||||
private int mLastLoadId = -1;
|
||||
|
||||
// Runnable to check if the shortcuts permission has changed.
|
||||
@NonNull
|
||||
private final Runnable mDataValidationCheck = new Runnable() {
|
||||
@@ -553,6 +555,7 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
|
||||
if (mLoaderTask != task) {
|
||||
throw new CancellationException("Loader already stopped");
|
||||
}
|
||||
mLastLoadId++;
|
||||
mTask = task;
|
||||
mIsLoaderTaskRunning = true;
|
||||
mModelLoaded = false;
|
||||
@@ -722,4 +725,12 @@ public class LauncherModel extends LauncherApps.Callback implements InstallSessi
|
||||
return mCallbacksList.toArray(new Callbacks[mCallbacksList.size()]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the ID for the last model load. If the load ID doesn't match for a transaction, the
|
||||
* transaction should be ignored.
|
||||
*/
|
||||
public int getLastLoadId() {
|
||||
return mLastLoadId;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,21 +16,18 @@
|
||||
|
||||
package com.android.launcher3;
|
||||
|
||||
import android.annotation.TargetApi;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentProviderResult;
|
||||
import android.content.ContentUris;
|
||||
import android.content.ContentValues;
|
||||
import android.content.OperationApplicationException;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteQueryBuilder;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Process;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
@@ -38,12 +35,11 @@ import android.util.Log;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.model.ModelDbController;
|
||||
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
|
||||
import com.android.launcher3.widget.LauncherWidgetHolder;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.function.ToIntFunction;
|
||||
|
||||
public class LauncherProvider extends ContentProvider {
|
||||
private static final String TAG = "LauncherProvider";
|
||||
@@ -74,10 +70,6 @@ public class LauncherProvider extends ContentProvider {
|
||||
return true;
|
||||
}
|
||||
|
||||
public ModelDbController getModelDbController() {
|
||||
return LauncherAppState.getInstance(getContext()).getModel().getModelDbController();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
SqlArguments args = new SqlArguments(uri, null, null);
|
||||
@@ -91,180 +83,91 @@ public class LauncherProvider extends ContentProvider {
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection,
|
||||
String[] selectionArgs, String sortOrder) {
|
||||
|
||||
SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
|
||||
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
|
||||
qb.setTables(args.table);
|
||||
|
||||
Cursor result = getModelDbController().query(
|
||||
args.table, projection, args.where, args.args, sortOrder);
|
||||
result.setNotificationUri(getContext().getContentResolver(), uri);
|
||||
return result;
|
||||
}
|
||||
|
||||
private void reloadLauncherIfExternal() {
|
||||
if (Binder.getCallingPid() != Process.myPid()) {
|
||||
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
|
||||
if (app != null) {
|
||||
app.getModel().forceReload();
|
||||
}
|
||||
}
|
||||
Cursor[] result = new Cursor[1];
|
||||
executeControllerTask(controller -> {
|
||||
result[0] = controller.query(args.table, projection, args.where, args.args, sortOrder);
|
||||
return 0;
|
||||
});
|
||||
return result[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues initialValues) {
|
||||
// In very limited cases, we support system|signature permission apps to modify the db.
|
||||
if (Binder.getCallingPid() != Process.myPid()) {
|
||||
if (!initializeExternalAdd(initialValues)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
int rowId = executeControllerTask(controller -> {
|
||||
// 1. Ensure that externally added items have a valid item id
|
||||
int id = controller.generateNewItemId();
|
||||
values.put(LauncherSettings.Favorites._ID, id);
|
||||
|
||||
SqlArguments args = new SqlArguments(uri);
|
||||
int rowId = getModelDbController().insert(args.table, initialValues);
|
||||
if (rowId < 0) return null;
|
||||
// 2. In the case of an app widget, and if no app widget id is specified, we
|
||||
// attempt allocate and bind the widget.
|
||||
Integer itemType = values.getAsInteger(Favorites.ITEM_TYPE);
|
||||
if (itemType != null
|
||||
&& itemType.intValue() == Favorites.ITEM_TYPE_APPWIDGET
|
||||
&& !values.containsKey(Favorites.APPWIDGET_ID)) {
|
||||
|
||||
uri = ContentUris.withAppendedId(uri, rowId);
|
||||
reloadLauncherIfExternal();
|
||||
return uri;
|
||||
}
|
||||
ComponentName cn = ComponentName.unflattenFromString(
|
||||
values.getAsString(Favorites.APPWIDGET_PROVIDER));
|
||||
if (cn == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private boolean initializeExternalAdd(ContentValues values) {
|
||||
// 1. Ensure that externally added items have a valid item id
|
||||
int id = getModelDbController().generateNewItemId();
|
||||
values.put(LauncherSettings.Favorites._ID, id);
|
||||
|
||||
// 2. In the case of an app widget, and if no app widget id is specified, we
|
||||
// attempt allocate and bind the widget.
|
||||
Integer itemType = values.getAsInteger(LauncherSettings.Favorites.ITEM_TYPE);
|
||||
if (itemType != null &&
|
||||
itemType.intValue() == LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET &&
|
||||
!values.containsKey(LauncherSettings.Favorites.APPWIDGET_ID)) {
|
||||
|
||||
final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(getContext());
|
||||
ComponentName cn = ComponentName.unflattenFromString(
|
||||
values.getAsString(Favorites.APPWIDGET_PROVIDER));
|
||||
|
||||
if (cn != null) {
|
||||
LauncherWidgetHolder widgetHolder = LauncherWidgetHolder.newInstance(getContext());
|
||||
try {
|
||||
int appWidgetId = widgetHolder.allocateAppWidgetId();
|
||||
values.put(LauncherSettings.Favorites.APPWIDGET_ID, appWidgetId);
|
||||
if (!appWidgetManager.bindAppWidgetIdIfAllowed(appWidgetId,cn)) {
|
||||
if (!AppWidgetManager.getInstance(getContext())
|
||||
.bindAppWidgetIdIfAllowed(appWidgetId, cn)) {
|
||||
widgetHolder.deleteAppWidgetId(appWidgetId);
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
} catch (RuntimeException e) {
|
||||
Log.e(TAG, "Failed to initialize external widget", e);
|
||||
return false;
|
||||
return 0;
|
||||
} finally {
|
||||
// Necessary to destroy the holder to free up possible activity context
|
||||
widgetHolder.destroy();
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
SqlArguments args = new SqlArguments(uri);
|
||||
return controller.insert(args.table, values);
|
||||
});
|
||||
|
||||
@Override
|
||||
public int bulkInsert(Uri uri, ContentValues[] values) {
|
||||
SqlArguments args = new SqlArguments(uri);
|
||||
getModelDbController().bulkInsert(args.table, values);
|
||||
reloadLauncherIfExternal();
|
||||
return values.length;
|
||||
}
|
||||
|
||||
@TargetApi(Build.VERSION_CODES.M)
|
||||
@Override
|
||||
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)
|
||||
throws OperationApplicationException {
|
||||
try (SQLiteTransaction t = getModelDbController().newTransaction()) {
|
||||
final int numOperations = operations.size();
|
||||
final ContentProviderResult[] results = new ContentProviderResult[numOperations];
|
||||
for (int i = 0; i < numOperations; i++) {
|
||||
ContentProviderOperation op = operations.get(i);
|
||||
results[i] = op.apply(this, results, i);
|
||||
}
|
||||
t.commit();
|
||||
reloadLauncherIfExternal();
|
||||
return results;
|
||||
}
|
||||
return rowId < 0 ? null : ContentUris.withAppendedId(uri, rowId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
|
||||
int count = getModelDbController().delete(args.table, args.where, args.args);
|
||||
if (count > 0) {
|
||||
reloadLauncherIfExternal();
|
||||
}
|
||||
return count;
|
||||
return executeControllerTask(c -> c.delete(args.table, args.where, args.args));
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
|
||||
int count = getModelDbController().update(args.table, values, args.where, args.args);
|
||||
reloadLauncherIfExternal();
|
||||
return count;
|
||||
return executeControllerTask(c -> c.update(args.table, values, args.where, args.args));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Bundle call(String method, final String arg, final Bundle extras) {
|
||||
if (Binder.getCallingUid() != Process.myUid()) {
|
||||
return null;
|
||||
private int executeControllerTask(ToIntFunction<ModelDbController> task) {
|
||||
if (Binder.getCallingPid() == Process.myPid()) {
|
||||
throw new IllegalArgumentException("Same process should call model directly");
|
||||
}
|
||||
|
||||
switch (method) {
|
||||
case LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG: {
|
||||
getModelDbController().clearEmptyDbFlag();
|
||||
return null;
|
||||
}
|
||||
case LauncherSettings.Settings.METHOD_DELETE_EMPTY_FOLDERS: {
|
||||
Bundle result = new Bundle();
|
||||
result.putIntArray(LauncherSettings.Settings.EXTRA_VALUE,
|
||||
getModelDbController().deleteEmptyFolders().toArray());
|
||||
return result;
|
||||
}
|
||||
case LauncherSettings.Settings.METHOD_NEW_ITEM_ID: {
|
||||
Bundle result = new Bundle();
|
||||
result.putInt(LauncherSettings.Settings.EXTRA_VALUE,
|
||||
getModelDbController().generateNewItemId());
|
||||
return result;
|
||||
}
|
||||
case LauncherSettings.Settings.METHOD_NEW_SCREEN_ID: {
|
||||
Bundle result = new Bundle();
|
||||
result.putInt(LauncherSettings.Settings.EXTRA_VALUE,
|
||||
getModelDbController().getNewScreenId());
|
||||
return result;
|
||||
}
|
||||
case LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB: {
|
||||
getModelDbController().createEmptyDB();
|
||||
return null;
|
||||
}
|
||||
case LauncherSettings.Settings.METHOD_LOAD_DEFAULT_FAVORITES: {
|
||||
getModelDbController().loadDefaultFavoritesIfNecessary();
|
||||
return null;
|
||||
}
|
||||
case LauncherSettings.Settings.METHOD_REMOVE_GHOST_WIDGETS: {
|
||||
getModelDbController().removeGhostWidgets();
|
||||
return null;
|
||||
}
|
||||
case LauncherSettings.Settings.METHOD_NEW_TRANSACTION: {
|
||||
Bundle result = new Bundle();
|
||||
result.putBinder(LauncherSettings.Settings.EXTRA_VALUE,
|
||||
getModelDbController().newTransaction());
|
||||
return result;
|
||||
}
|
||||
case LauncherSettings.Settings.METHOD_REFRESH_HOTSEAT_RESTORE_TABLE: {
|
||||
getModelDbController().refreshHotseatRestoreTable();
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return MODEL_EXECUTOR.submit(() -> {
|
||||
LauncherModel model = LauncherAppState.getInstance(getContext()).getModel();
|
||||
int count = task.applyAsInt(model.getModelDbController());
|
||||
if (count > 0) {
|
||||
MAIN_EXECUTOR.submit(model::forceReload);
|
||||
}
|
||||
return count;
|
||||
}).get();
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static class SqlArguments {
|
||||
|
||||
@@ -16,10 +16,7 @@
|
||||
|
||||
package com.android.launcher3;
|
||||
|
||||
import android.content.ContentResolver;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.provider.BaseColumns;
|
||||
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
@@ -154,24 +151,6 @@ public class LauncherSettings {
|
||||
*/
|
||||
public static final String TMP_TABLE = "favorites_tmp";
|
||||
|
||||
/**
|
||||
* The content:// style URL for "favorites" table
|
||||
*/
|
||||
public static final Uri CONTENT_URI = Uri.parse("content://"
|
||||
+ LauncherProvider.AUTHORITY + "/" + TABLE_NAME);
|
||||
|
||||
/**
|
||||
* The content:// style URL for a given row, identified by its id.
|
||||
*
|
||||
* @param id The row id.
|
||||
*
|
||||
* @return The unique content URL for the specified row.
|
||||
*/
|
||||
public static Uri getContentUri(int id) {
|
||||
return Uri.parse("content://" + LauncherProvider.AUTHORITY
|
||||
+ "/" + TABLE_NAME + "/" + id);
|
||||
}
|
||||
|
||||
/**
|
||||
* The container holding the favorite
|
||||
* <P>Type: INTEGER</P>
|
||||
@@ -339,42 +318,8 @@ public class LauncherSettings {
|
||||
* Launcher settings
|
||||
*/
|
||||
public static final class Settings {
|
||||
|
||||
public static final Uri CONTENT_URI = Uri.parse("content://" +
|
||||
LauncherProvider.AUTHORITY + "/settings");
|
||||
|
||||
public static final String METHOD_CLEAR_EMPTY_DB_FLAG = "clear_empty_db_flag";
|
||||
|
||||
public static final String METHOD_DELETE_EMPTY_FOLDERS = "delete_empty_folders";
|
||||
|
||||
public static final String METHOD_NEW_ITEM_ID = "generate_new_item_id";
|
||||
public static final String METHOD_NEW_SCREEN_ID = "generate_new_screen_id";
|
||||
|
||||
public static final String METHOD_CREATE_EMPTY_DB = "create_empty_db";
|
||||
|
||||
public static final String METHOD_LOAD_DEFAULT_FAVORITES = "load_default_favorites";
|
||||
|
||||
public static final String METHOD_REMOVE_GHOST_WIDGETS = "remove_ghost_widgets";
|
||||
|
||||
public static final String METHOD_NEW_TRANSACTION = "new_db_transaction";
|
||||
|
||||
public static final String METHOD_REFRESH_HOTSEAT_RESTORE_TABLE = "restore_hotseat_table";
|
||||
|
||||
public static final String EXTRA_VALUE = "value";
|
||||
|
||||
public static final String EXTRA_DB_NAME = "db_name";
|
||||
|
||||
public static final String LAYOUT_DIGEST_KEY = "launcher3.layout.provider.blob";
|
||||
public static final String LAYOUT_DIGEST_LABEL = "launcher-layout";
|
||||
public static final String LAYOUT_DIGEST_TAG = "ignore";
|
||||
|
||||
public static Bundle call(ContentResolver cr, String method) {
|
||||
return call(cr, method, null /* arg */);
|
||||
}
|
||||
|
||||
public static Bundle call(ContentResolver cr, String method, String arg) {
|
||||
return cr.call(CONTENT_URI, method, arg, null);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -897,9 +897,8 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
|
||||
mWorkspaceScreens.remove(emptyScreenId);
|
||||
mScreenOrder.removeValue(emptyScreenId);
|
||||
|
||||
int newScreenId = LauncherSettings.Settings.call(getContext().getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
|
||||
.getInt(LauncherSettings.Settings.EXTRA_VALUE);
|
||||
int newScreenId = LauncherAppState.getInstance(getContext())
|
||||
.getModel().getModelDbController().getNewScreenId();
|
||||
// Launcher database isn't aware of empty pages that are already bound, so we need to
|
||||
// skip those IDs manually.
|
||||
while (mWorkspaceScreens.containsKey(newScreenId)) {
|
||||
|
||||
@@ -106,6 +106,7 @@ public abstract class BaseLauncherBinder {
|
||||
synchronized (mBgDataModel) {
|
||||
if (incrementBindId) {
|
||||
mBgDataModel.lastBindId++;
|
||||
mBgDataModel.lastLoadId = mApp.getModel().getLastLoadId();
|
||||
}
|
||||
mMyBindingId = mBgDataModel.lastBindId;
|
||||
return new DisjointWorkspaceBinder(workspacePages);
|
||||
@@ -126,6 +127,7 @@ public abstract class BaseLauncherBinder {
|
||||
mBgDataModel.extraItems.forEach(extraItems::add);
|
||||
if (incrementBindId) {
|
||||
mBgDataModel.lastBindId++;
|
||||
mBgDataModel.lastLoadId = mApp.getModel().getLastLoadId();
|
||||
}
|
||||
mMyBindingId = mBgDataModel.lastBindId;
|
||||
workspaceItemCount = mBgDataModel.itemsIdMap.size();
|
||||
|
||||
@@ -125,6 +125,11 @@ public class BgDataModel {
|
||||
*/
|
||||
public int lastBindId = 0;
|
||||
|
||||
/**
|
||||
* Load id for which the callbacks were successfully bound
|
||||
*/
|
||||
public int lastLoadId = -1;
|
||||
|
||||
/**
|
||||
* Clears all the data
|
||||
*/
|
||||
|
||||
@@ -33,7 +33,6 @@ import static com.android.launcher3.util.PackageManagerHelper.isSystemApp;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
@@ -61,7 +60,6 @@ import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherModel;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.LauncherSettings.Settings;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.folder.Folder;
|
||||
@@ -361,16 +359,15 @@ public class LoaderTask implements Runnable {
|
||||
String selection,
|
||||
@Nullable LoaderMemoryLogger memoryLogger) {
|
||||
final Context context = mApp.getContext();
|
||||
final ContentResolver contentResolver = context.getContentResolver();
|
||||
final PackageManagerHelper pmHelper = new PackageManagerHelper(context);
|
||||
final boolean isSafeMode = pmHelper.isSafeMode();
|
||||
final boolean isSdCardReady = Utilities.isBootCompleted();
|
||||
final WidgetManagerHelper widgetHelper = new WidgetManagerHelper(context);
|
||||
|
||||
mApp.getModel().getModelDbController().tryMigrateDB();
|
||||
ModelDbController dbController = mApp.getModel().getModelDbController();
|
||||
dbController.tryMigrateDB();
|
||||
Log.d(TAG, "loadWorkspace: loading default favorites");
|
||||
mApp.getModel().getModelDbController().loadDefaultFavoritesIfNecessary();
|
||||
Settings.call(contentResolver, Settings.METHOD_LOAD_DEFAULT_FAVORITES);
|
||||
dbController.loadDefaultFavoritesIfNecessary();
|
||||
|
||||
synchronized (mBgDataModel) {
|
||||
mBgDataModel.clear();
|
||||
@@ -384,12 +381,11 @@ public class LoaderTask implements Runnable {
|
||||
mFirstScreenBroadcast = new FirstScreenBroadcast(installingPkgs);
|
||||
|
||||
mShortcutKeyToPinnedShortcuts = new HashMap<>();
|
||||
ModelDbController dbController = mApp.getModel().getModelDbController();
|
||||
final LoaderCursor c = new LoaderCursor(
|
||||
dbController.query(TABLE_NAME, null, selection, null, null),
|
||||
mApp, mUserManagerState);
|
||||
final Bundle extras = c.getExtras();
|
||||
mDbName = extras == null ? null : extras.getString(Settings.EXTRA_DB_NAME);
|
||||
mDbName = extras == null ? null : extras.getString(ModelDbController.EXTRA_DB_NAME);
|
||||
try {
|
||||
final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>();
|
||||
|
||||
@@ -906,9 +902,7 @@ public class LoaderTask implements Runnable {
|
||||
private void sanitizeFolders(boolean itemsDeleted) {
|
||||
if (itemsDeleted) {
|
||||
// Remove any empty folder
|
||||
int[] deletedFolderIds = Settings.call(mApp.getContext().getContentResolver(),
|
||||
Settings.METHOD_DELETE_EMPTY_FOLDERS)
|
||||
.getIntArray(Settings.EXTRA_VALUE);
|
||||
IntArray deletedFolderIds = mApp.getModel().getModelDbController().deleteEmptyFolders();
|
||||
synchronized (mBgDataModel) {
|
||||
for (int folderId : deletedFolderIds) {
|
||||
mBgDataModel.workspaceItems.remove(mBgDataModel.folders.get(folderId));
|
||||
@@ -921,11 +915,9 @@ public class LoaderTask implements Runnable {
|
||||
|
||||
private void sanitizeWidgetsShortcutsAndPackages() {
|
||||
Context context = mApp.getContext();
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
|
||||
// Remove any ghost widgets
|
||||
Settings.call(contentResolver,
|
||||
Settings.METHOD_REMOVE_GHOST_WIDGETS);
|
||||
mApp.getModel().getModelDbController().removeGhostWidgets();
|
||||
|
||||
// Update pinned state of model shortcuts
|
||||
mBgDataModel.updateShortcutPinnedState(context);
|
||||
|
||||
@@ -37,7 +37,6 @@ import android.database.Cursor;
|
||||
import android.database.SQLException;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Bundle;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.os.Process;
|
||||
@@ -85,6 +84,7 @@ public class ModelDbController {
|
||||
private static final String TAG = "LauncherProvider";
|
||||
|
||||
private static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
|
||||
public static final String EXTRA_DB_NAME = "db_name";
|
||||
|
||||
protected DatabaseHelper mOpenHelper;
|
||||
|
||||
@@ -140,7 +140,7 @@ public class ModelDbController {
|
||||
table, projection, selection, selectionArgs, null, null, sortOrder);
|
||||
|
||||
final Bundle extra = new Bundle();
|
||||
extra.putString(LauncherSettings.Settings.EXTRA_DB_NAME, mOpenHelper.getDatabaseName());
|
||||
extra.putString(EXTRA_DB_NAME, mOpenHelper.getDatabaseName());
|
||||
result.setExtras(extra);
|
||||
return result;
|
||||
}
|
||||
@@ -161,28 +161,6 @@ public class ModelDbController {
|
||||
return rowId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to insert but for adding multiple values in a transaction.
|
||||
*/
|
||||
@WorkerThread
|
||||
public int bulkInsert(String table, ContentValues[] values) {
|
||||
createDbIfNotExists();
|
||||
|
||||
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
|
||||
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
|
||||
int numValues = values.length;
|
||||
for (int i = 0; i < numValues; i++) {
|
||||
addModifiedTime(values[i]);
|
||||
if (mOpenHelper.dbInsertAndCheck(db, table, values[i]) < 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
onAddOrDeleteOp(db);
|
||||
t.commit();
|
||||
}
|
||||
return values.length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Refer {@link SQLiteDatabase#delete(String, String, String[])}
|
||||
*/
|
||||
@@ -191,10 +169,6 @@ public class ModelDbController {
|
||||
createDbIfNotExists();
|
||||
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
|
||||
|
||||
if (Binder.getCallingPid() != Process.myPid()
|
||||
&& Favorites.TABLE_NAME.equalsIgnoreCase(table)) {
|
||||
mOpenHelper.removeGhostWidgets(mOpenHelper.getWritableDatabase());
|
||||
}
|
||||
int count = db.delete(table, selection, selectionArgs);
|
||||
if (count > 0) {
|
||||
onAddOrDeleteOp(db);
|
||||
@@ -250,6 +224,7 @@ public class ModelDbController {
|
||||
public void createEmptyDB() {
|
||||
createDbIfNotExists();
|
||||
mOpenHelper.createEmptyDB(mOpenHelper.getWritableDatabase());
|
||||
LauncherPrefs.get(mContext).putSync(getEmptyDbCreatedKey().to(true));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -304,6 +279,10 @@ public class ModelDbController {
|
||||
*/
|
||||
private boolean migrateGridIfNeeded() {
|
||||
createDbIfNotExists();
|
||||
if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey())) {
|
||||
// If we have already create a new DB, ignore migration
|
||||
return false;
|
||||
}
|
||||
InvariantDeviceProfile idp = LauncherAppState.getIDP(mContext);
|
||||
if (!GridSizeMigrationUtil.needsToMigrate(mContext, idp)) {
|
||||
return true;
|
||||
@@ -374,7 +353,7 @@ public class ModelDbController {
|
||||
}
|
||||
|
||||
private void clearFlagEmptyDbCreated() {
|
||||
LauncherPrefs.get(mContext).removeSync(getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()));
|
||||
LauncherPrefs.get(mContext).removeSync(getEmptyDbCreatedKey());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -388,7 +367,7 @@ public class ModelDbController {
|
||||
public synchronized void loadDefaultFavoritesIfNecessary() {
|
||||
createDbIfNotExists();
|
||||
|
||||
if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey(mOpenHelper.getDatabaseName()))) {
|
||||
if (LauncherPrefs.get(mContext).get(getEmptyDbCreatedKey())) {
|
||||
Log.d(TAG, "loading default workspace");
|
||||
|
||||
LauncherWidgetHolder widgetHolder = mOpenHelper.newLauncherWidgetHolder();
|
||||
@@ -507,6 +486,10 @@ public class ModelDbController {
|
||||
mOpenHelper, mContext.getResources(), defaultLayout);
|
||||
}
|
||||
|
||||
private ConstantItem<Boolean> getEmptyDbCreatedKey() {
|
||||
return getEmptyDbCreatedKey(mOpenHelper.getDatabaseName());
|
||||
}
|
||||
|
||||
/**
|
||||
* Re-composite given key in respect to database. If the current db is
|
||||
* {@link LauncherFiles#LAUNCHER_DB}, return the key as-is. Otherwise append the db name to
|
||||
|
||||
@@ -16,13 +16,12 @@
|
||||
|
||||
package com.android.launcher3.model;
|
||||
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
|
||||
import static com.android.launcher3.provider.LauncherDbUtils.itemIdMatch;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -32,9 +31,7 @@ import androidx.annotation.Nullable;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherModel;
|
||||
import com.android.launcher3.LauncherModel.CallbackTask;
|
||||
import com.android.launcher3.LauncherProvider;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.LauncherSettings.Settings;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.celllayout.CellPosMapper;
|
||||
import com.android.launcher3.celllayout.CellPosMapper.CellPos;
|
||||
@@ -45,6 +42,7 @@ import com.android.launcher3.model.data.FolderInfo;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
|
||||
import com.android.launcher3.util.ContentWriter;
|
||||
import com.android.launcher3.util.Executors;
|
||||
import com.android.launcher3.util.ItemInfoMatcher;
|
||||
@@ -80,7 +78,7 @@ public class ModelWriter {
|
||||
private final boolean mVerifyChanges;
|
||||
|
||||
// Keep track of delete operations that occur when an Undo option is present; we may not commit.
|
||||
private final List<Runnable> mDeleteRunnables = new ArrayList<>();
|
||||
private final List<ModelTask> mDeleteRunnables = new ArrayList<>();
|
||||
private boolean mPreparingToUndo;
|
||||
private final CellPosMapper mCellPosMapper;
|
||||
|
||||
@@ -217,8 +215,7 @@ public class ModelWriter {
|
||||
item.spanX = spanX;
|
||||
item.spanY = spanY;
|
||||
notifyItemModified(item);
|
||||
|
||||
MODEL_EXECUTOR.execute(new UpdateItemRunnable(item, () ->
|
||||
new UpdateItemRunnable(item, () ->
|
||||
new ContentWriter(mContext)
|
||||
.put(Favorites.CONTAINER, item.container)
|
||||
.put(Favorites.CELLX, item.cellX)
|
||||
@@ -226,7 +223,8 @@ public class ModelWriter {
|
||||
.put(Favorites.RANK, item.rank)
|
||||
.put(Favorites.SPANX, item.spanX)
|
||||
.put(Favorites.SPANY, item.spanY)
|
||||
.put(Favorites.SCREEN, item.screenId)));
|
||||
.put(Favorites.SCREEN, item.screenId))
|
||||
.executeOnModelThread();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -234,11 +232,11 @@ public class ModelWriter {
|
||||
*/
|
||||
public void updateItemInDatabase(ItemInfo item) {
|
||||
notifyItemModified(item);
|
||||
MODEL_EXECUTOR.execute(new UpdateItemRunnable(item, () -> {
|
||||
new UpdateItemRunnable(item, () -> {
|
||||
ContentWriter writer = new ContentWriter(mContext);
|
||||
item.onAddToDatabase(writer);
|
||||
return writer;
|
||||
}));
|
||||
}).executeOnModelThread();
|
||||
}
|
||||
|
||||
private void notifyItemModified(ItemInfo item) {
|
||||
@@ -253,13 +251,12 @@ public class ModelWriter {
|
||||
int container, int screenId, int cellX, int cellY) {
|
||||
updateItemInfoProps(item, container, screenId, cellX, cellY);
|
||||
|
||||
final ContentResolver cr = mContext.getContentResolver();
|
||||
item.id = Settings.call(cr, Settings.METHOD_NEW_ITEM_ID).getInt(Settings.EXTRA_VALUE);
|
||||
item.id = mModel.getModelDbController().generateNewItemId();
|
||||
notifyOtherCallbacks(c -> c.bindItems(Collections.singletonList(item), false));
|
||||
|
||||
ModelVerifier verifier = new ModelVerifier();
|
||||
final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
|
||||
MODEL_EXECUTOR.execute(() -> {
|
||||
newModelTask(() -> {
|
||||
// Write the item on background thread, as some properties might have been updated in
|
||||
// the background.
|
||||
final ContentWriter writer = new ContentWriter(mContext);
|
||||
@@ -272,7 +269,7 @@ public class ModelWriter {
|
||||
mBgDataModel.addItem(mContext, item, true);
|
||||
verifier.verifyModel();
|
||||
}
|
||||
});
|
||||
}).executeOnModelThread();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -303,15 +300,13 @@ public class ModelWriter {
|
||||
Collectors.joining(","))
|
||||
+ ". Reason: [" + (TextUtils.isEmpty(reason) ? "unknown" : reason) + "]");
|
||||
notifyDelete(items);
|
||||
enqueueDeleteRunnable(() -> {
|
||||
enqueueDeleteRunnable(newModelTask(() -> {
|
||||
for (ItemInfo item : items) {
|
||||
final Uri uri = Favorites.getContentUri(item.id);
|
||||
mContext.getContentResolver().delete(uri, null, null);
|
||||
|
||||
mModel.getModelDbController().delete(TABLE_NAME, itemIdMatch(item.id), null);
|
||||
mBgDataModel.removeItem(mContext, item);
|
||||
verifier.verifyModel();
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -321,7 +316,7 @@ public class ModelWriter {
|
||||
ModelVerifier verifier = new ModelVerifier();
|
||||
notifyDelete(Collections.singleton(info));
|
||||
|
||||
enqueueDeleteRunnable(() -> {
|
||||
enqueueDeleteRunnable(newModelTask(() -> {
|
||||
mModel.getModelDbController().delete(Favorites.TABLE_NAME,
|
||||
Favorites.CONTAINER + "=" + info.id, null);
|
||||
mBgDataModel.removeItem(mContext, info.contents);
|
||||
@@ -331,7 +326,7 @@ public class ModelWriter {
|
||||
Favorites._ID + "=" + info.id, null);
|
||||
mBgDataModel.removeItem(mContext, info);
|
||||
verifier.verifyModel();
|
||||
});
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -343,7 +338,7 @@ public class ModelWriter {
|
||||
if (holder != null && !info.isCustomWidget() && info.isWidgetIdAllocated()) {
|
||||
// Deleting an app widget ID is a void call but writes to disk before returning
|
||||
// to the caller...
|
||||
enqueueDeleteRunnable(() -> holder.deleteAppWidgetId(info.appWidgetId));
|
||||
enqueueDeleteRunnable(newModelTask(() -> holder.deleteAppWidgetId(info.appWidgetId)));
|
||||
}
|
||||
deleteItemFromDatabase(info, reason);
|
||||
}
|
||||
@@ -373,19 +368,17 @@ public class ModelWriter {
|
||||
* {@link #commitDelete()} is called (or abandoned if {@link #abortDelete} is called).
|
||||
* Otherwise, we run the Runnable immediately.
|
||||
*/
|
||||
private void enqueueDeleteRunnable(Runnable r) {
|
||||
private void enqueueDeleteRunnable(ModelTask r) {
|
||||
if (mPreparingToUndo) {
|
||||
mDeleteRunnables.add(r);
|
||||
} else {
|
||||
MODEL_EXECUTOR.execute(r);
|
||||
r.executeOnModelThread();
|
||||
}
|
||||
}
|
||||
|
||||
public void commitDelete() {
|
||||
mPreparingToUndo = false;
|
||||
for (Runnable runnable : mDeleteRunnables) {
|
||||
MODEL_EXECUTOR.execute(runnable);
|
||||
}
|
||||
mDeleteRunnables.forEach(ModelTask::executeOnModelThread);
|
||||
mDeleteRunnables.clear();
|
||||
}
|
||||
|
||||
@@ -426,10 +419,9 @@ public class ModelWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Uri uri = Favorites.getContentUri(mItemId);
|
||||
mContext.getContentResolver().update(uri, mWriter.get().getValues(mContext),
|
||||
null, null);
|
||||
public void runImpl() {
|
||||
mModel.getModelDbController().update(
|
||||
TABLE_NAME, mWriter.get().getValues(mContext), itemIdMatch(mItemId), null);
|
||||
updateItemArrays(mItem, mItemId);
|
||||
}
|
||||
}
|
||||
@@ -444,27 +436,24 @@ public class ModelWriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
ArrayList<ContentProviderOperation> ops = new ArrayList<>();
|
||||
int count = mItems.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
ItemInfo item = mItems.get(i);
|
||||
final int itemId = item.id;
|
||||
final Uri uri = Favorites.getContentUri(itemId);
|
||||
ContentValues values = mValues.get(i);
|
||||
|
||||
ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
|
||||
updateItemArrays(item, itemId);
|
||||
}
|
||||
try {
|
||||
mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, ops);
|
||||
public void runImpl() {
|
||||
try (SQLiteTransaction t = mModel.getModelDbController().newTransaction()) {
|
||||
int count = mItems.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
ItemInfo item = mItems.get(i);
|
||||
final int itemId = item.id;
|
||||
mModel.getModelDbController().update(
|
||||
TABLE_NAME, mValues.get(i), itemIdMatch(itemId), null);
|
||||
updateItemArrays(item, itemId);
|
||||
}
|
||||
t.commit();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class UpdateItemBaseRunnable implements Runnable {
|
||||
private abstract class UpdateItemBaseRunnable extends ModelTask {
|
||||
private final StackTraceElement[] mStackTrace;
|
||||
private final ModelVerifier mVerifier = new ModelVerifier();
|
||||
|
||||
@@ -515,6 +504,35 @@ public class ModelWriter {
|
||||
}
|
||||
}
|
||||
|
||||
private abstract class ModelTask implements Runnable {
|
||||
|
||||
private final int mLoadId = mBgDataModel.lastLoadId;
|
||||
|
||||
@Override
|
||||
public final void run() {
|
||||
if (mLoadId != mModel.getLastLoadId()) {
|
||||
Log.d(TAG, "Model changed before the task could execute");
|
||||
return;
|
||||
}
|
||||
runImpl();
|
||||
}
|
||||
|
||||
public final void executeOnModelThread() {
|
||||
MODEL_EXECUTOR.execute(this);
|
||||
}
|
||||
|
||||
public abstract void runImpl();
|
||||
}
|
||||
|
||||
private ModelTask newModelTask(Runnable r) {
|
||||
return new ModelTask() {
|
||||
@Override
|
||||
public void runImpl() {
|
||||
r.run();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility class to verify model updates are propagated properly to the callback.
|
||||
*/
|
||||
|
||||
@@ -82,9 +82,7 @@ public class WorkspaceItemSpaceFinder {
|
||||
|
||||
if (!found) {
|
||||
// Still no position found. Add a new screen to the end.
|
||||
screenId = LauncherSettings.Settings.call(app.getContext().getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
|
||||
.getInt(LauncherSettings.Settings.EXTRA_VALUE);
|
||||
screenId = app.getModel().getModelDbController().getNewScreenId();
|
||||
|
||||
// Save the screen id for binding in the workspace
|
||||
workspaceScreens.add(screenId);
|
||||
|
||||
@@ -28,7 +28,6 @@ import android.database.sqlite.SQLiteDatabase;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.drawable.Icon;
|
||||
import android.os.Binder;
|
||||
import android.os.PersistableBundle;
|
||||
import android.os.Process;
|
||||
import android.os.UserManager;
|
||||
@@ -50,6 +49,13 @@ import com.android.launcher3.util.IntSet;
|
||||
*/
|
||||
public class LauncherDbUtils {
|
||||
|
||||
/**
|
||||
* Returns a string which can be used as a where clause for DB query to match the given itemId
|
||||
*/
|
||||
public static String itemIdMatch(int itemId) {
|
||||
return "_id=" + itemId;
|
||||
}
|
||||
|
||||
public static IntArray queryIntArray(boolean distinct, SQLiteDatabase db, String tableName,
|
||||
String columnName, String selection, String groupBy, String orderBy) {
|
||||
IntArray out = new IntArray();
|
||||
@@ -166,7 +172,7 @@ public class LauncherDbUtils {
|
||||
/**
|
||||
* Utility class to simplify managing sqlite transactions
|
||||
*/
|
||||
public static class SQLiteTransaction extends Binder implements AutoCloseable {
|
||||
public static class SQLiteTransaction implements AutoCloseable {
|
||||
private final SQLiteDatabase mDb;
|
||||
|
||||
public SQLiteTransaction(SQLiteDatabase db) {
|
||||
|
||||
@@ -15,24 +15,28 @@
|
||||
*/
|
||||
package com.android.launcher3.celllayout;
|
||||
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
|
||||
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 android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
|
||||
import androidx.test.uiautomator.UiDevice;
|
||||
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherModel;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.model.BgDataModel.Callbacks;
|
||||
import com.android.launcher3.model.ModelDbController;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.provider.LauncherDbUtils.SQLiteTransaction;
|
||||
import com.android.launcher3.tapl.LauncherInstrumentation;
|
||||
import com.android.launcher3.util.ContentWriter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class FavoriteItemsTransaction {
|
||||
@@ -67,15 +71,16 @@ public class FavoriteItemsTransaction {
|
||||
controller.clearEmptyDbFlag();
|
||||
|
||||
// Add new data
|
||||
List<ContentValues> values = new ArrayList<>();
|
||||
int count = mItemsToSubmit.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
ContentWriter writer = new ContentWriter(mContext);
|
||||
mItemsToSubmit.get(i).get().onAddToDatabase(writer);
|
||||
writer.put(LauncherSettings.Favorites._ID, i);
|
||||
values.add(writer.getValues(mContext));
|
||||
try (SQLiteTransaction transaction = controller.newTransaction()) {
|
||||
int count = mItemsToSubmit.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
ContentWriter writer = new ContentWriter(mContext);
|
||||
mItemsToSubmit.get(i).get().onAddToDatabase(writer);
|
||||
writer.put(LauncherSettings.Favorites._ID, i);
|
||||
controller.insert(TABLE_NAME, writer.getValues(mContext));
|
||||
}
|
||||
transaction.commit();
|
||||
}
|
||||
controller.bulkInsert(TABLE_NAME, values.toArray(new ContentValues[0]));
|
||||
});
|
||||
|
||||
// Reload model
|
||||
@@ -91,4 +96,15 @@ public class FavoriteItemsTransaction {
|
||||
runOnExecutorSync(MAIN_EXECUTOR, () -> { });
|
||||
runOnExecutorSync(MAIN_EXECUTOR, () -> model.removeCallbacks(mockCb));
|
||||
}
|
||||
|
||||
/**
|
||||
* Commits the transaction and waits for home load
|
||||
*/
|
||||
public void commitAndLoadHome(LauncherInstrumentation inst) {
|
||||
commit();
|
||||
|
||||
// Launch the home activity
|
||||
UiDevice.getInstance(getInstrumentation()).pressHome();
|
||||
inst.waitForLauncherInitialized();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,7 +61,6 @@ public class ReorderWidgets extends AbstractLauncherUiTest {
|
||||
public void setup() throws Throwable {
|
||||
mWorkspaceBuilder = new TestWorkspaceBuilder(mTargetContext);
|
||||
TaplTestsLauncher3.initialize(this);
|
||||
clearHomescreen();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -111,10 +110,10 @@ public class ReorderWidgets extends AbstractLauncherUiTest {
|
||||
new FavoriteItemsTransaction(mTargetContext);
|
||||
transaction = buildWorkspaceFromBoards(testCase.mStart, transaction);
|
||||
transaction.commit();
|
||||
mLauncher.waitForLauncherInitialized();
|
||||
// resetLoaderState triggers the launcher to start loading the workspace which allows
|
||||
// waitForLauncherCondition to wait for that condition, otherwise the condition would
|
||||
// always be true and it wouldn't wait for the changes to be applied.
|
||||
resetLoaderState();
|
||||
waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading());
|
||||
Widget widget = mLauncher.getWorkspace().getWidgetAtCell(mainWidgetCellPos.getCellX(),
|
||||
mainWidgetCellPos.getCellY());
|
||||
|
||||
@@ -78,12 +78,6 @@ public class TestWorkspaceBuilder {
|
||||
return transaction;
|
||||
}
|
||||
|
||||
private int getID() {
|
||||
return LauncherSettings.Settings.call(
|
||||
mResolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
|
||||
.getInt(LauncherSettings.Settings.EXTRA_VALUE);
|
||||
}
|
||||
|
||||
private AppInfo getApp() {
|
||||
return new AppInfo(APP_COMPONENT_NAME, "test icon", mMyUser,
|
||||
AppInfo.makeLaunchIntent(APP_COMPONENT_NAME));
|
||||
@@ -127,7 +121,6 @@ public class TestWorkspaceBuilder {
|
||||
return () -> {
|
||||
LauncherAppWidgetProviderInfo info = findWidgetProvider(false);
|
||||
LauncherAppWidgetInfo item = createWidgetInfo(info, getApplicationContext(), true);
|
||||
item.id = getID();
|
||||
item.cellX = widgetRect.getCellX();
|
||||
item.cellY = widgetRect.getCellY();
|
||||
item.spanX = widgetRect.getSpanX();
|
||||
@@ -139,7 +132,6 @@ public class TestWorkspaceBuilder {
|
||||
|
||||
private ItemInfo createIconInCell(CellLayoutBoard.IconPoint iconPoint, int screenId) {
|
||||
WorkspaceItemInfo item = new WorkspaceItemInfo(getApp());
|
||||
item.id = getID();
|
||||
item.screenId = screenId;
|
||||
item.cellX = iconPoint.getCoord().x;
|
||||
item.cellY = iconPoint.getCoord().y;
|
||||
@@ -150,7 +142,6 @@ public class TestWorkspaceBuilder {
|
||||
|
||||
private ItemInfo getHotseatValues(int x) {
|
||||
WorkspaceItemInfo item = new WorkspaceItemInfo(getApp());
|
||||
item.id = getID();
|
||||
item.cellX = x;
|
||||
item.cellY = 0;
|
||||
item.minSpanY = item.minSpanX = item.spanX = item.spanY = 1;
|
||||
|
||||
@@ -19,7 +19,6 @@ import static androidx.test.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.TestUtil.getOnUiThread;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
@@ -40,6 +39,7 @@ import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.os.UserManager;
|
||||
import android.system.OsConstants;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
@@ -49,11 +49,8 @@ import androidx.test.uiautomator.UiDevice;
|
||||
import androidx.test.uiautomator.Until;
|
||||
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.statemanager.StateManager;
|
||||
import com.android.launcher3.tapl.HomeAllApps;
|
||||
import com.android.launcher3.tapl.HomeAppIcon;
|
||||
@@ -66,7 +63,6 @@ import com.android.launcher3.util.LooperExecutor;
|
||||
import com.android.launcher3.util.SimpleBroadcastReceiver;
|
||||
import com.android.launcher3.util.TestUtil;
|
||||
import com.android.launcher3.util.Wait;
|
||||
import com.android.launcher3.util.WidgetUtils;
|
||||
import com.android.launcher3.util.rule.FailureWatcher;
|
||||
import com.android.launcher3.util.rule.LauncherActivityRule;
|
||||
import com.android.launcher3.util.rule.SamplerRule;
|
||||
@@ -83,8 +79,10 @@ import org.junit.rules.RuleChain;
|
||||
import org.junit.rules.TestRule;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
@@ -298,37 +296,19 @@ public abstract class AbstractLauncherUiTest {
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all icons from homescreen and hotseat.
|
||||
* Runs the callback on the UI thread and returns the result.
|
||||
*/
|
||||
public void clearHomescreen() {
|
||||
LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
|
||||
LauncherSettings.Settings.call(mTargetContext.getContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
|
||||
resetLoaderState();
|
||||
}
|
||||
|
||||
protected void resetLoaderState() {
|
||||
protected <T> T getOnUiThread(final Callable<T> callback) {
|
||||
try {
|
||||
mMainThreadExecutor.execute(
|
||||
() -> LauncherAppState.getInstance(
|
||||
mTargetContext).getModel().forceReload());
|
||||
} catch (Throwable t) {
|
||||
throw new IllegalArgumentException(t);
|
||||
return mMainThreadExecutor.submit(callback).get(DEFAULT_UI_TIMEOUT,
|
||||
TimeUnit.MILLISECONDS);
|
||||
} catch (TimeoutException e) {
|
||||
Log.e(TAG, "Timeout in getOnUiThread, sending SIGABRT", e);
|
||||
Process.sendSignal(Process.myPid(), OsConstants.SIGABRT);
|
||||
throw new RuntimeException(e);
|
||||
} catch (Throwable e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
mLauncher.waitForLauncherInitialized();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds {@param item} on the homescreen on the 0th screen
|
||||
*/
|
||||
public void addItemToScreen(ItemInfo item) {
|
||||
WidgetUtils.addItemToScreen(item, mTargetContext);
|
||||
resetLoaderState();
|
||||
|
||||
// Launch the home activity
|
||||
mDevice.pressHome();
|
||||
mLauncher.waitForLauncherInitialized();
|
||||
}
|
||||
|
||||
protected <T> T getFromLauncher(Function<Launcher, T> f) {
|
||||
|
||||
@@ -30,6 +30,7 @@ import android.view.View;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.launcher3.celllayout.FavoriteItemsTransaction;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.testcomponent.WidgetConfigActivity;
|
||||
@@ -85,8 +86,7 @@ public class AddConfigWidgetTest extends AbstractLauncherUiTest {
|
||||
* @param acceptConfig accept the config activity
|
||||
*/
|
||||
private void runTest(boolean acceptConfig) throws Throwable {
|
||||
clearHomescreen();
|
||||
mDevice.pressHome();
|
||||
new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
|
||||
|
||||
// Drag widget to homescreen
|
||||
WidgetConfigStartupMonitor monitor = new WidgetConfigStartupMonitor();
|
||||
|
||||
@@ -23,9 +23,10 @@ import static org.junit.Assert.assertTrue;
|
||||
import android.platform.test.annotations.IwTest;
|
||||
import android.platform.test.annotations.PlatinumTest;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.launcher3.celllayout.FavoriteItemsTransaction;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.tapl.Widget;
|
||||
import com.android.launcher3.tapl.WidgetResizeFrame;
|
||||
@@ -55,8 +56,7 @@ public class AddWidgetTest extends AbstractLauncherUiTest {
|
||||
@Test
|
||||
@PortraitLandscape
|
||||
public void testDragIcon() throws Throwable {
|
||||
clearHomescreen();
|
||||
mDevice.pressHome();
|
||||
new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
|
||||
|
||||
waitForLauncherCondition("Workspace didn't finish loading", l -> !l.isWorkspaceLoading());
|
||||
|
||||
@@ -93,8 +93,8 @@ public class AddWidgetTest extends AbstractLauncherUiTest {
|
||||
@Test
|
||||
@PortraitLandscape
|
||||
public void testDragCustomShortcut() throws Throwable {
|
||||
clearHomescreen();
|
||||
mDevice.pressHome();
|
||||
new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
|
||||
|
||||
mLauncher.getWorkspace().openAllWidgets()
|
||||
.getWidget("com.android.launcher3.testcomponent.CustomShortcutConfigActivity")
|
||||
.dragToWorkspace(false, true);
|
||||
|
||||
@@ -15,9 +15,13 @@
|
||||
*/
|
||||
package com.android.launcher3.ui.widget;
|
||||
|
||||
import static androidx.test.InstrumentationRegistry.getTargetContext;
|
||||
|
||||
import static com.android.launcher3.util.TestUtil.getOnUiThread;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
|
||||
import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
|
||||
import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
|
||||
import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
|
||||
import static com.android.launcher3.model.data.LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
|
||||
import static com.android.launcher3.provider.LauncherDbUtils.itemIdMatch;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
import static com.android.launcher3.util.WidgetUtils.createWidgetInfo;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -27,7 +31,6 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.content.pm.PackageInstaller.SessionParams;
|
||||
import android.content.pm.PackageManager;
|
||||
@@ -35,11 +38,14 @@ import android.database.Cursor;
|
||||
import android.os.Bundle;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherModel;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.celllayout.FavoriteItemsTransaction;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.pm.InstallSessionHelper;
|
||||
import com.android.launcher3.tapl.Widget;
|
||||
@@ -58,6 +64,7 @@ import org.junit.runner.RunWith;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Tests for bind widget flow.
|
||||
@@ -71,24 +78,18 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
|
||||
@Rule
|
||||
public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
|
||||
|
||||
private ContentResolver mResolver;
|
||||
|
||||
// Objects created during test, which should be cleaned up in the end.
|
||||
private Cursor mCursor;
|
||||
// App install session id.
|
||||
private int mSessionId = -1;
|
||||
|
||||
private LauncherModel mModel;
|
||||
|
||||
@Override
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
mResolver = mTargetContext.getContentResolver();
|
||||
|
||||
// Clear all existing data
|
||||
LauncherSettings.Settings.call(mResolver, LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
|
||||
LauncherSettings.Settings.call(mResolver,
|
||||
LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
|
||||
mModel = LauncherAppState.getInstance(mTargetContext).getModel();
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -104,34 +105,24 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
|
||||
|
||||
@Test
|
||||
public void testBindNormalWidget_withConfig() {
|
||||
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(true);
|
||||
LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), true);
|
||||
|
||||
addItemToScreen(item);
|
||||
LauncherAppWidgetProviderInfo info = addWidgetToScreen(true, true, i -> { });
|
||||
verifyWidgetPresent(info);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBindNormalWidget_withoutConfig() {
|
||||
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(false);
|
||||
LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), true);
|
||||
|
||||
addItemToScreen(item);
|
||||
LauncherAppWidgetProviderInfo info = addWidgetToScreen(false, true, i -> { });
|
||||
verifyWidgetPresent(info);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnboundWidget_removed() {
|
||||
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(false);
|
||||
LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), false);
|
||||
item.appWidgetId = -33;
|
||||
|
||||
addItemToScreen(item);
|
||||
LauncherAppWidgetProviderInfo info = addWidgetToScreen(false, false,
|
||||
item -> item.appWidgetId = -33);
|
||||
|
||||
final Workspace workspace = mLauncher.getWorkspace();
|
||||
// Item deleted from db
|
||||
mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
|
||||
null, null, null, null, null);
|
||||
mCursor = queryItem();
|
||||
assertEquals(0, mCursor.getCount());
|
||||
|
||||
// The view does not exist
|
||||
@@ -141,36 +132,26 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
|
||||
@Test
|
||||
public void testPendingWidget_autoRestored() {
|
||||
// A non-restored widget with no config screen gets restored automatically.
|
||||
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(false);
|
||||
|
||||
// Do not bind the widget
|
||||
LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), false);
|
||||
item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
|
||||
|
||||
addItemToScreen(item);
|
||||
LauncherAppWidgetProviderInfo info = addWidgetToScreen(false, false,
|
||||
item -> item.restoreStatus = FLAG_ID_NOT_VALID);
|
||||
verifyWidgetPresent(info);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPendingWidget_withConfigScreen() {
|
||||
// A non-restored widget with config screen get bound and shows a 'Click to setup' UI.
|
||||
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(true);
|
||||
|
||||
// Do not bind the widget
|
||||
LauncherAppWidgetInfo item = createWidgetInfo(info, getTargetContext(), false);
|
||||
item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID;
|
||||
|
||||
addItemToScreen(item);
|
||||
LauncherAppWidgetProviderInfo info = addWidgetToScreen(true, false,
|
||||
item -> item.restoreStatus = FLAG_ID_NOT_VALID);
|
||||
verifyPendingWidgetPresent();
|
||||
|
||||
// Item deleted from db
|
||||
mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
|
||||
null, null, null, null, null);
|
||||
mCursor = queryItem();
|
||||
mCursor.moveToNext();
|
||||
|
||||
// Widget has a valid Id now.
|
||||
assertEquals(0, mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
|
||||
& LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
|
||||
& FLAG_ID_NOT_VALID);
|
||||
assertNotNull(AppWidgetManager.getInstance(mTargetContext)
|
||||
.getAppWidgetInfo(mCursor.getInt(mCursor.getColumnIndex(
|
||||
LauncherSettings.Favorites.APPWIDGET_ID))));
|
||||
@@ -186,7 +167,6 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
|
||||
appWidgetManager.updateAppWidgetOptions(appWidgetId, b);
|
||||
appWidgetManager.updateAppWidget(appWidgetId, remoteViews);
|
||||
|
||||
|
||||
// verify changes are reflected
|
||||
waitForLauncherCondition("App widget options did not update",
|
||||
l -> appWidgetManager.getAppWidgetOptions(appWidgetId).getBoolean(
|
||||
@@ -198,17 +178,12 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
|
||||
|
||||
@Test
|
||||
public void testPendingWidget_notRestored_removed() {
|
||||
LauncherAppWidgetInfo item = getInvalidWidgetInfo();
|
||||
item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
|
||||
| LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
|
||||
|
||||
addItemToScreen(item);
|
||||
addPendingItemToScreen(getInvalidWidgetInfo(), FLAG_ID_NOT_VALID | FLAG_PROVIDER_NOT_READY);
|
||||
|
||||
assertTrue("Pending widget exists",
|
||||
mLauncher.getWorkspace().tryGetPendingWidget(0) == null);
|
||||
// Item deleted from db
|
||||
mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
|
||||
null, null, null, null, null);
|
||||
mCursor = queryItem();
|
||||
assertEquals(0, mCursor.getCount());
|
||||
}
|
||||
|
||||
@@ -216,32 +191,25 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
|
||||
public void testPendingWidget_notRestored_brokenInstall() {
|
||||
// A widget which is was being installed once, even if its not being
|
||||
// installed at the moment is not removed.
|
||||
LauncherAppWidgetInfo item = getInvalidWidgetInfo();
|
||||
item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
|
||||
| LauncherAppWidgetInfo.FLAG_RESTORE_STARTED
|
||||
| LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
|
||||
|
||||
addItemToScreen(item);
|
||||
addPendingItemToScreen(getInvalidWidgetInfo(),
|
||||
FLAG_ID_NOT_VALID | FLAG_RESTORE_STARTED | FLAG_PROVIDER_NOT_READY);
|
||||
verifyPendingWidgetPresent();
|
||||
|
||||
// Verify item still exists in db
|
||||
mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
|
||||
null, null, null, null, null);
|
||||
mCursor = queryItem();
|
||||
assertEquals(1, mCursor.getCount());
|
||||
|
||||
// Widget still has an invalid id.
|
||||
mCursor.moveToNext();
|
||||
assertEquals(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID,
|
||||
assertEquals(FLAG_ID_NOT_VALID,
|
||||
mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
|
||||
& LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
|
||||
& FLAG_ID_NOT_VALID);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPendingWidget_notRestored_activeInstall() throws Exception {
|
||||
// A widget which is being installed is not removed
|
||||
LauncherAppWidgetInfo item = getInvalidWidgetInfo();
|
||||
item.restoreStatus = LauncherAppWidgetInfo.FLAG_ID_NOT_VALID
|
||||
| LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY;
|
||||
|
||||
// Create an active installer session
|
||||
SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
|
||||
@@ -249,19 +217,18 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
|
||||
PackageInstaller installer = mTargetContext.getPackageManager().getPackageInstaller();
|
||||
mSessionId = installer.createSession(params);
|
||||
|
||||
addItemToScreen(item);
|
||||
addPendingItemToScreen(item, FLAG_ID_NOT_VALID | FLAG_PROVIDER_NOT_READY);
|
||||
verifyPendingWidgetPresent();
|
||||
|
||||
// Verify item still exists in db
|
||||
mCursor = mResolver.query(LauncherSettings.Favorites.getContentUri(item.id),
|
||||
null, null, null, null, null);
|
||||
mCursor = queryItem();
|
||||
assertEquals(1, mCursor.getCount());
|
||||
|
||||
// Widget still has an invalid id.
|
||||
mCursor.moveToNext();
|
||||
assertEquals(LauncherAppWidgetInfo.FLAG_ID_NOT_VALID,
|
||||
assertEquals(FLAG_ID_NOT_VALID,
|
||||
mCursor.getInt(mCursor.getColumnIndex(LauncherSettings.Favorites.RESTORED))
|
||||
& LauncherAppWidgetInfo.FLAG_ID_NOT_VALID);
|
||||
& FLAG_ID_NOT_VALID);
|
||||
}
|
||||
|
||||
private void verifyWidgetPresent(LauncherAppWidgetProviderInfo info) {
|
||||
@@ -276,6 +243,28 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
|
||||
widget != null);
|
||||
}
|
||||
|
||||
private void addPendingItemToScreen(LauncherAppWidgetInfo item, int restoreStatus) {
|
||||
item.restoreStatus = restoreStatus;
|
||||
item.screenId = FIRST_SCREEN_ID;
|
||||
new FavoriteItemsTransaction(mTargetContext)
|
||||
.addItem(() -> item)
|
||||
.commitAndLoadHome(mLauncher);
|
||||
}
|
||||
|
||||
private LauncherAppWidgetProviderInfo addWidgetToScreen(boolean hasConfigureScreen,
|
||||
boolean bindWidget, Consumer<LauncherAppWidgetInfo> itemOverride) {
|
||||
LauncherAppWidgetProviderInfo info = TestViewHelpers.findWidgetProvider(hasConfigureScreen);
|
||||
new FavoriteItemsTransaction(mTargetContext)
|
||||
.addItem(() -> {
|
||||
LauncherAppWidgetInfo item = createWidgetInfo(info, mTargetContext, bindWidget);
|
||||
item.screenId = FIRST_SCREEN_ID;
|
||||
itemOverride.accept(item);
|
||||
return item;
|
||||
})
|
||||
.commitAndLoadHome(mLauncher);
|
||||
return info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a LauncherAppWidgetInfo with package name which is not present on the device
|
||||
*/
|
||||
@@ -313,4 +302,14 @@ public class BindWidgetTest extends AbstractLauncherUiTest {
|
||||
item.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
|
||||
return item;
|
||||
}
|
||||
|
||||
private Cursor queryItem() {
|
||||
try {
|
||||
return MODEL_EXECUTOR.submit(() ->
|
||||
mModel.getModelDbController().query(
|
||||
TABLE_NAME, null, itemIdMatch(0), null, null)).get();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@ import androidx.test.filters.LargeTest;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.celllayout.FavoriteItemsTransaction;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
@@ -134,8 +135,7 @@ public class RequestPinItemTest extends AbstractLauncherUiTest {
|
||||
|
||||
private void runTest(String activityMethod, boolean isWidget, ItemOperator itemMatcher,
|
||||
Intent... commandIntents) throws Throwable {
|
||||
clearHomescreen();
|
||||
mDevice.pressHome();
|
||||
new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
|
||||
|
||||
// Open Pin item activity
|
||||
BlockingBroadcastReceiver openMonitor = new BlockingBroadcastReceiver(
|
||||
|
||||
@@ -53,7 +53,6 @@ import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherModel;
|
||||
import com.android.launcher3.LauncherModel.ModelUpdateTask;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.LauncherProvider;
|
||||
import com.android.launcher3.model.AllAppsList;
|
||||
import com.android.launcher3.model.BgDataModel;
|
||||
import com.android.launcher3.model.BgDataModel.Callbacks;
|
||||
@@ -241,12 +240,6 @@ public class LauncherModelHelper {
|
||||
|
||||
mPm = spy(getBaseContext().getPackageManager());
|
||||
mDbDir = new File(getCacheDir(), UUID.randomUUID().toString());
|
||||
setupProvider(LauncherProvider.AUTHORITY, new LauncherProvider() {
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,18 +18,14 @@ package com.android.launcher3.util;
|
||||
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
|
||||
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
|
||||
|
||||
import static com.android.launcher3.WorkspaceLayoutManager.FIRST_SCREEN_ID;
|
||||
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Process;
|
||||
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
|
||||
import com.android.launcher3.widget.LauncherWidgetHolder;
|
||||
@@ -87,33 +83,6 @@ public class WidgetUtils {
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds {@param item} on the homescreen on the 0th screen
|
||||
*/
|
||||
public static void addItemToScreen(ItemInfo item, Context targetContext) {
|
||||
ContentResolver resolver = targetContext.getContentResolver();
|
||||
int screenId = FIRST_SCREEN_ID;
|
||||
// Update the screen id counter for the provider.
|
||||
LauncherSettings.Settings.call(resolver,
|
||||
LauncherSettings.Settings.METHOD_NEW_SCREEN_ID);
|
||||
|
||||
if (screenId > FIRST_SCREEN_ID) {
|
||||
screenId = FIRST_SCREEN_ID;
|
||||
}
|
||||
|
||||
// Insert the item
|
||||
ContentWriter writer = new ContentWriter(targetContext);
|
||||
item.id = LauncherSettings.Settings.call(
|
||||
resolver, LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
|
||||
.getInt(LauncherSettings.Settings.EXTRA_VALUE);
|
||||
item.screenId = screenId;
|
||||
item.onAddToDatabase(writer);
|
||||
writer.put(LauncherSettings.Favorites._ID, item.id);
|
||||
resolver.insert(LauncherSettings.Favorites.CONTENT_URI,
|
||||
writer.getValues(targetContext));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Creates a {@link AppWidgetProviderInfo} for the provided component name
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user