summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/model/ModelWriter.java
diff options
context:
space:
mode:
authorSunny Goyal <sunnygoyal@google.com>2017-02-02 13:52:53 -0800
committerSunny Goyal <sunnygoyal@google.com>2017-02-16 13:50:14 -0800
commit43bf11d9c95f76c2dfeb625b23cb458df81252b3 (patch)
treeec2bbed88795b3c02621cd964be56f20d797c263 /src/com/android/launcher3/model/ModelWriter.java
parent5a2edd65f2ca3d83b1672835831e2f5b2f16ab82 (diff)
downloadandroid_packages_apps_Trebuchet-43bf11d9c95f76c2dfeb625b23cb458df81252b3.tar.gz
android_packages_apps_Trebuchet-43bf11d9c95f76c2dfeb625b23cb458df81252b3.tar.bz2
android_packages_apps_Trebuchet-43bf11d9c95f76c2dfeb625b23cb458df81252b3.zip
Separating methods for updating the model to a sepatate class.
Removing static access to model update methods, to allow for better access control and testing Change-Id: I9afe004dbf1b2fe50df422fd28bceea9230a4704
Diffstat (limited to 'src/com/android/launcher3/model/ModelWriter.java')
-rw-r--r--src/com/android/launcher3/model/ModelWriter.java374
1 files changed, 374 insertions, 0 deletions
diff --git a/src/com/android/launcher3/model/ModelWriter.java b/src/com/android/launcher3/model/ModelWriter.java
new file mode 100644
index 000000000..4931dca14
--- /dev/null
+++ b/src/com/android/launcher3/model/ModelWriter.java
@@ -0,0 +1,374 @@
+/*
+ * Copyright (C) 2017 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.model;
+
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+
+import com.android.launcher3.FolderInfo;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.LauncherProvider;
+import com.android.launcher3.LauncherSettings;
+import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.LauncherSettings.Settings;
+import com.android.launcher3.ShortcutInfo;
+import com.android.launcher3.util.ContentWriter;
+import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.LooperExecuter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.concurrent.Executor;
+
+/**
+ * Class for handling model updates.
+ */
+public class ModelWriter {
+
+ private static final String TAG = "ModelWriter";
+
+ private final Context mContext;
+ private final BgDataModel mBgDataModel;
+ private final Executor mWorkerExecutor;
+ private final boolean mHasVerticalHotseat;
+
+ public ModelWriter(Context context, BgDataModel dataModel, boolean hasVerticalHotseat) {
+ mContext = context;
+ mBgDataModel = dataModel;
+ mWorkerExecutor = new LooperExecuter(LauncherModel.getWorkerLooper());
+ mHasVerticalHotseat = hasVerticalHotseat;
+ }
+
+ private void updateItemInfoProps(
+ ItemInfo item, long container, long screenId, int cellX, int cellY) {
+ item.container = container;
+ item.cellX = cellX;
+ item.cellY = cellY;
+ // We store hotseat items in canonical form which is this orientation invariant position
+ // in the hotseat
+ if (container == Favorites.CONTAINER_HOTSEAT) {
+ item.screenId = mHasVerticalHotseat
+ ? LauncherAppState.getIDP(mContext).numHotseatIcons - cellY - 1 : cellX;
+ } else {
+ item.screenId = screenId;
+ }
+ }
+
+ /**
+ * Adds an item to the DB if it was not created previously, or move it to a new
+ * <container, screen, cellX, cellY>
+ */
+ public void addOrMoveItemInDatabase(ItemInfo item,
+ long container, long screenId, int cellX, int cellY) {
+ if (item.container == ItemInfo.NO_ID) {
+ // From all apps
+ addItemToDatabase(item, container, screenId, cellX, cellY);
+ } else {
+ // From somewhere else
+ moveItemInDatabase(item, container, screenId, cellX, cellY);
+ }
+ }
+
+ private void checkItemInfoLocked(long itemId, ItemInfo item, StackTraceElement[] stackTrace) {
+ ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
+ if (modelItem != null && item != modelItem) {
+ // check all the data is consistent
+ if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
+ ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
+ ShortcutInfo shortcut = (ShortcutInfo) item;
+ if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
+ modelShortcut.intent.filterEquals(shortcut.intent) &&
+ modelShortcut.id == shortcut.id &&
+ modelShortcut.itemType == shortcut.itemType &&
+ modelShortcut.container == shortcut.container &&
+ modelShortcut.screenId == shortcut.screenId &&
+ modelShortcut.cellX == shortcut.cellX &&
+ modelShortcut.cellY == shortcut.cellY &&
+ modelShortcut.spanX == shortcut.spanX &&
+ modelShortcut.spanY == shortcut.spanY) {
+ // For all intents and purposes, this is the same object
+ return;
+ }
+ }
+
+ // the modelItem needs to match up perfectly with item if our model is
+ // to be consistent with the database-- for now, just require
+ // modelItem == item or the equality check above
+ String msg = "item: " + ((item != null) ? item.toString() : "null") +
+ "modelItem: " +
+ ((modelItem != null) ? modelItem.toString() : "null") +
+ "Error: ItemInfo passed to checkItemInfo doesn't match original";
+ RuntimeException e = new RuntimeException(msg);
+ if (stackTrace != null) {
+ e.setStackTrace(stackTrace);
+ }
+ throw e;
+ }
+ }
+
+ /**
+ * Move an item in the DB to a new <container, screen, cellX, cellY>
+ */
+ public void moveItemInDatabase(final ItemInfo item,
+ long container, long screenId, int cellX, int cellY) {
+ updateItemInfoProps(item, container, screenId, cellX, cellY);
+
+ final ContentWriter writer = new ContentWriter(mContext)
+ .put(Favorites.CONTAINER, item.container)
+ .put(Favorites.CELLX, item.cellX)
+ .put(Favorites.CELLY, item.cellY)
+ .put(Favorites.RANK, item.rank)
+ .put(Favorites.SCREEN, item.screenId);
+
+ mWorkerExecutor.execute(new UpdateItemRunnable(item, writer));
+ }
+
+ /**
+ * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
+ * cellX, cellY have already been updated on the ItemInfos.
+ */
+ public void moveItemsInDatabase(final ArrayList<ItemInfo> items, long container, int screen) {
+ ArrayList<ContentValues> contentValues = new ArrayList<>();
+ int count = items.size();
+
+ for (int i = 0; i < count; i++) {
+ ItemInfo item = items.get(i);
+ updateItemInfoProps(item, container, screen, item.cellX, item.cellY);
+
+ final ContentValues values = new ContentValues();
+ values.put(Favorites.CONTAINER, item.container);
+ values.put(Favorites.CELLX, item.cellX);
+ values.put(Favorites.CELLY, item.cellY);
+ values.put(Favorites.RANK, item.rank);
+ values.put(Favorites.SCREEN, item.screenId);
+
+ contentValues.add(values);
+ }
+ mWorkerExecutor.execute(new UpdateItemsRunnable(items, contentValues));
+ }
+
+ /**
+ * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
+ */
+ public void modifyItemInDatabase(final ItemInfo item,
+ long container, long screenId, int cellX, int cellY, int spanX, int spanY) {
+ updateItemInfoProps(item, container, screenId, cellX, cellY);
+ item.spanX = spanX;
+ item.spanY = spanY;
+
+ final ContentWriter writer = new ContentWriter(mContext)
+ .put(Favorites.CONTAINER, item.container)
+ .put(Favorites.CELLX, item.cellX)
+ .put(Favorites.CELLY, item.cellY)
+ .put(Favorites.RANK, item.rank)
+ .put(Favorites.SPANX, item.spanX)
+ .put(Favorites.SPANY, item.spanY)
+ .put(Favorites.SCREEN, item.screenId);
+
+ mWorkerExecutor.execute(new UpdateItemRunnable(item, writer));
+ }
+
+ /**
+ * Update an item to the database in a specified container.
+ */
+ public void updateItemInDatabase(ItemInfo item) {
+ ContentWriter writer = new ContentWriter(mContext);
+ item.onAddToDatabase(writer);
+ mWorkerExecutor.execute(new UpdateItemRunnable(item, writer));
+ }
+
+ /**
+ * Add an item to the database in a specified container. Sets the container, screen, cellX and
+ * cellY fields of the item. Also assigns an ID to the item.
+ */
+ public void addItemToDatabase(final ItemInfo item,
+ long container, long screenId, int cellX, int cellY) {
+ updateItemInfoProps(item, container, screenId, cellX, cellY);
+
+ final ContentWriter writer = new ContentWriter(mContext);
+ final ContentResolver cr = mContext.getContentResolver();
+ item.onAddToDatabase(writer);
+
+ item.id = Settings.call(cr, Settings.METHOD_NEW_ITEM_ID).getLong(Settings.EXTRA_VALUE);
+ writer.put(Favorites._ID, item.id);
+
+ final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
+ mWorkerExecutor.execute(new Runnable() {
+ public void run() {
+ cr.insert(Favorites.CONTENT_URI, writer.getValues(mContext));
+
+ synchronized (mBgDataModel) {
+ checkItemInfoLocked(item.id, item, stackTrace);
+ mBgDataModel.addItem(mContext, item, true);
+ }
+ }
+ });
+ }
+
+ /**
+ * Removes the specified item from the database
+ */
+ public void deleteItemFromDatabase(ItemInfo item) {
+ deleteItemsFromDatabase(Arrays.asList(item));
+ }
+
+ /**
+ * Removes all the items from the database matching {@param matcher}.
+ */
+ public void deleteItemsFromDatabase(ItemInfoMatcher matcher) {
+ deleteItemsFromDatabase(matcher.filterItemInfos(mBgDataModel.itemsIdMap));
+ }
+
+ /**
+ * Removes the specified items from the database
+ */
+ public void deleteItemsFromDatabase(final Iterable<? extends ItemInfo> items) {
+ mWorkerExecutor.execute(new Runnable() {
+ public void run() {
+ for (ItemInfo item : items) {
+ final Uri uri = Favorites.getContentUri(item.id);
+ mContext.getContentResolver().delete(uri, null, null);
+
+ mBgDataModel.removeItem(mContext, item);
+ }
+ }
+ });
+ }
+
+ /**
+ * Remove the specified folder and all its contents from the database.
+ */
+ public void deleteFolderAndContentsFromDatabase(final FolderInfo info) {
+ mWorkerExecutor.execute(new Runnable() {
+ public void run() {
+ ContentResolver cr = mContext.getContentResolver();
+ cr.delete(LauncherSettings.Favorites.CONTENT_URI,
+ LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
+ mBgDataModel.removeItem(mContext, info.contents);
+ info.contents.clear();
+
+ cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
+ mBgDataModel.removeItem(mContext, info);
+ }
+ });
+ }
+
+ private class UpdateItemRunnable extends UpdateItemBaseRunnable {
+ private final ItemInfo mItem;
+ private final ContentWriter mWriter;
+ private final long mItemId;
+
+ UpdateItemRunnable(ItemInfo item, ContentWriter writer) {
+ mItem = item;
+ mWriter = writer;
+ mItemId = item.id;
+ }
+
+ @Override
+ public void run() {
+ Uri uri = Favorites.getContentUri(mItemId);
+ mContext.getContentResolver().update(uri, mWriter.getValues(mContext), null, null);
+ updateItemArrays(mItem, mItemId);
+ }
+ }
+
+ private class UpdateItemsRunnable extends UpdateItemBaseRunnable {
+ private final ArrayList<ContentValues> mValues;
+ private final ArrayList<ItemInfo> mItems;
+
+ UpdateItemsRunnable(ArrayList<ItemInfo> items, ArrayList<ContentValues> values) {
+ mValues = values;
+ mItems = items;
+ }
+
+ @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 long 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);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ private abstract class UpdateItemBaseRunnable implements Runnable {
+ private final StackTraceElement[] mStackTrace;
+
+ UpdateItemBaseRunnable() {
+ mStackTrace = new Throwable().getStackTrace();
+ }
+
+ protected void updateItemArrays(ItemInfo item, long itemId) {
+ // Lock on mBgLock *after* the db operation
+ synchronized (mBgDataModel) {
+ checkItemInfoLocked(itemId, item, mStackTrace);
+
+ if (item.container != Favorites.CONTAINER_DESKTOP &&
+ item.container != Favorites.CONTAINER_HOTSEAT) {
+ // Item is in a folder, make sure this folder exists
+ if (!mBgDataModel.folders.containsKey(item.container)) {
+ // An items container is being set to a that of an item which is not in
+ // the list of Folders.
+ String msg = "item: " + item + " container being set to: " +
+ item.container + ", not in the list of folders";
+ Log.e(TAG, msg);
+ }
+ }
+
+ // Items are added/removed from the corresponding FolderInfo elsewhere, such
+ // as in Workspace.onDrop. Here, we just add/remove them from the list of items
+ // that are on the desktop, as appropriate
+ ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
+ if (modelItem != null &&
+ (modelItem.container == Favorites.CONTAINER_DESKTOP ||
+ modelItem.container == Favorites.CONTAINER_HOTSEAT)) {
+ switch (modelItem.itemType) {
+ case Favorites.ITEM_TYPE_APPLICATION:
+ case Favorites.ITEM_TYPE_SHORTCUT:
+ case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
+ case Favorites.ITEM_TYPE_FOLDER:
+ if (!mBgDataModel.workspaceItems.contains(modelItem)) {
+ mBgDataModel.workspaceItems.add(modelItem);
+ }
+ break;
+ default:
+ break;
+ }
+ } else {
+ mBgDataModel.workspaceItems.remove(modelItem);
+ }
+ }
+ }
+ }
+}