diff options
author | Sunny Goyal <sunnygoyal@google.com> | 2017-02-02 13:52:53 -0800 |
---|---|---|
committer | Sunny Goyal <sunnygoyal@google.com> | 2017-02-16 13:50:14 -0800 |
commit | 43bf11d9c95f76c2dfeb625b23cb458df81252b3 (patch) | |
tree | ec2bbed88795b3c02621cd964be56f20d797c263 /src/com/android/launcher3/model/ModelWriter.java | |
parent | 5a2edd65f2ca3d83b1672835831e2f5b2f16ab82 (diff) | |
download | android_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.java | 374 |
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); + } + } + } + } +} |