• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.launcher3.model;
18 
19 import android.content.ContentProviderOperation;
20 import android.content.ContentResolver;
21 import android.content.ContentValues;
22 import android.content.Context;
23 import android.net.Uri;
24 import android.util.Log;
25 
26 import com.android.launcher3.FolderInfo;
27 import com.android.launcher3.ItemInfo;
28 import com.android.launcher3.LauncherAppState;
29 import com.android.launcher3.LauncherModel;
30 import com.android.launcher3.LauncherProvider;
31 import com.android.launcher3.LauncherSettings;
32 import com.android.launcher3.LauncherSettings.Favorites;
33 import com.android.launcher3.LauncherSettings.Settings;
34 import com.android.launcher3.ShortcutInfo;
35 import com.android.launcher3.util.ContentWriter;
36 import com.android.launcher3.util.ItemInfoMatcher;
37 import com.android.launcher3.util.LooperExecuter;
38 
39 import java.util.ArrayList;
40 import java.util.Arrays;
41 import java.util.concurrent.Executor;
42 
43 /**
44  * Class for handling model updates.
45  */
46 public class ModelWriter {
47 
48     private static final String TAG = "ModelWriter";
49 
50     private final Context mContext;
51     private final BgDataModel mBgDataModel;
52     private final Executor mWorkerExecutor;
53     private final boolean mHasVerticalHotseat;
54 
ModelWriter(Context context, BgDataModel dataModel, boolean hasVerticalHotseat)55     public ModelWriter(Context context, BgDataModel dataModel, boolean hasVerticalHotseat) {
56         mContext = context;
57         mBgDataModel = dataModel;
58         mWorkerExecutor = new LooperExecuter(LauncherModel.getWorkerLooper());
59         mHasVerticalHotseat = hasVerticalHotseat;
60     }
61 
updateItemInfoProps( ItemInfo item, long container, long screenId, int cellX, int cellY)62     private void updateItemInfoProps(
63             ItemInfo item, long container, long screenId, int cellX, int cellY) {
64         item.container = container;
65         item.cellX = cellX;
66         item.cellY = cellY;
67         // We store hotseat items in canonical form which is this orientation invariant position
68         // in the hotseat
69         if (container == Favorites.CONTAINER_HOTSEAT) {
70             item.screenId = mHasVerticalHotseat
71                     ? LauncherAppState.getIDP(mContext).numHotseatIcons - cellY - 1 : cellX;
72         } else {
73             item.screenId = screenId;
74         }
75     }
76 
77     /**
78      * Adds an item to the DB if it was not created previously, or move it to a new
79      * <container, screen, cellX, cellY>
80      */
addOrMoveItemInDatabase(ItemInfo item, long container, long screenId, int cellX, int cellY)81     public void addOrMoveItemInDatabase(ItemInfo item,
82             long container, long screenId, int cellX, int cellY) {
83         if (item.container == ItemInfo.NO_ID) {
84             // From all apps
85             addItemToDatabase(item, container, screenId, cellX, cellY);
86         } else {
87             // From somewhere else
88             moveItemInDatabase(item, container, screenId, cellX, cellY);
89         }
90     }
91 
checkItemInfoLocked(long itemId, ItemInfo item, StackTraceElement[] stackTrace)92     private void checkItemInfoLocked(long itemId, ItemInfo item, StackTraceElement[] stackTrace) {
93         ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
94         if (modelItem != null && item != modelItem) {
95             // check all the data is consistent
96             if (modelItem instanceof ShortcutInfo && item instanceof ShortcutInfo) {
97                 ShortcutInfo modelShortcut = (ShortcutInfo) modelItem;
98                 ShortcutInfo shortcut = (ShortcutInfo) item;
99                 if (modelShortcut.title.toString().equals(shortcut.title.toString()) &&
100                         modelShortcut.intent.filterEquals(shortcut.intent) &&
101                         modelShortcut.id == shortcut.id &&
102                         modelShortcut.itemType == shortcut.itemType &&
103                         modelShortcut.container == shortcut.container &&
104                         modelShortcut.screenId == shortcut.screenId &&
105                         modelShortcut.cellX == shortcut.cellX &&
106                         modelShortcut.cellY == shortcut.cellY &&
107                         modelShortcut.spanX == shortcut.spanX &&
108                         modelShortcut.spanY == shortcut.spanY) {
109                     // For all intents and purposes, this is the same object
110                     return;
111                 }
112             }
113 
114             // the modelItem needs to match up perfectly with item if our model is
115             // to be consistent with the database-- for now, just require
116             // modelItem == item or the equality check above
117             String msg = "item: " + ((item != null) ? item.toString() : "null") +
118                     "modelItem: " +
119                     ((modelItem != null) ? modelItem.toString() : "null") +
120                     "Error: ItemInfo passed to checkItemInfo doesn't match original";
121             RuntimeException e = new RuntimeException(msg);
122             if (stackTrace != null) {
123                 e.setStackTrace(stackTrace);
124             }
125             throw e;
126         }
127     }
128 
129     /**
130      * Move an item in the DB to a new <container, screen, cellX, cellY>
131      */
moveItemInDatabase(final ItemInfo item, long container, long screenId, int cellX, int cellY)132     public void moveItemInDatabase(final ItemInfo item,
133             long container, long screenId, int cellX, int cellY) {
134         updateItemInfoProps(item, container, screenId, cellX, cellY);
135 
136         final ContentWriter writer = new ContentWriter(mContext)
137                 .put(Favorites.CONTAINER, item.container)
138                 .put(Favorites.CELLX, item.cellX)
139                 .put(Favorites.CELLY, item.cellY)
140                 .put(Favorites.RANK, item.rank)
141                 .put(Favorites.SCREEN, item.screenId);
142 
143         mWorkerExecutor.execute(new UpdateItemRunnable(item, writer));
144     }
145 
146     /**
147      * Move items in the DB to a new <container, screen, cellX, cellY>. We assume that the
148      * cellX, cellY have already been updated on the ItemInfos.
149      */
moveItemsInDatabase(final ArrayList<ItemInfo> items, long container, int screen)150     public void moveItemsInDatabase(final ArrayList<ItemInfo> items, long container, int screen) {
151         ArrayList<ContentValues> contentValues = new ArrayList<>();
152         int count = items.size();
153 
154         for (int i = 0; i < count; i++) {
155             ItemInfo item = items.get(i);
156             updateItemInfoProps(item, container, screen, item.cellX, item.cellY);
157 
158             final ContentValues values = new ContentValues();
159             values.put(Favorites.CONTAINER, item.container);
160             values.put(Favorites.CELLX, item.cellX);
161             values.put(Favorites.CELLY, item.cellY);
162             values.put(Favorites.RANK, item.rank);
163             values.put(Favorites.SCREEN, item.screenId);
164 
165             contentValues.add(values);
166         }
167         mWorkerExecutor.execute(new UpdateItemsRunnable(items, contentValues));
168     }
169 
170     /**
171      * Move and/or resize item in the DB to a new <container, screen, cellX, cellY, spanX, spanY>
172      */
modifyItemInDatabase(final ItemInfo item, long container, long screenId, int cellX, int cellY, int spanX, int spanY)173     public void modifyItemInDatabase(final ItemInfo item,
174             long container, long screenId, int cellX, int cellY, int spanX, int spanY) {
175         updateItemInfoProps(item, container, screenId, cellX, cellY);
176         item.spanX = spanX;
177         item.spanY = spanY;
178 
179         final ContentWriter writer = new ContentWriter(mContext)
180                 .put(Favorites.CONTAINER, item.container)
181                 .put(Favorites.CELLX, item.cellX)
182                 .put(Favorites.CELLY, item.cellY)
183                 .put(Favorites.RANK, item.rank)
184                 .put(Favorites.SPANX, item.spanX)
185                 .put(Favorites.SPANY, item.spanY)
186                 .put(Favorites.SCREEN, item.screenId);
187 
188         mWorkerExecutor.execute(new UpdateItemRunnable(item, writer));
189     }
190 
191     /**
192      * Update an item to the database in a specified container.
193      */
updateItemInDatabase(ItemInfo item)194     public void updateItemInDatabase(ItemInfo item) {
195         ContentWriter writer = new ContentWriter(mContext);
196         item.onAddToDatabase(writer);
197         mWorkerExecutor.execute(new UpdateItemRunnable(item, writer));
198     }
199 
200     /**
201      * Add an item to the database in a specified container. Sets the container, screen, cellX and
202      * cellY fields of the item. Also assigns an ID to the item.
203      */
addItemToDatabase(final ItemInfo item, long container, long screenId, int cellX, int cellY)204     public void addItemToDatabase(final ItemInfo item,
205             long container, long screenId, int cellX, int cellY) {
206         updateItemInfoProps(item, container, screenId, cellX, cellY);
207 
208         final ContentWriter writer = new ContentWriter(mContext);
209         final ContentResolver cr = mContext.getContentResolver();
210         item.onAddToDatabase(writer);
211 
212         item.id = Settings.call(cr, Settings.METHOD_NEW_ITEM_ID).getLong(Settings.EXTRA_VALUE);
213         writer.put(Favorites._ID, item.id);
214 
215         final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
216         mWorkerExecutor.execute(new Runnable() {
217             public void run() {
218                 cr.insert(Favorites.CONTENT_URI, writer.getValues(mContext));
219 
220                 synchronized (mBgDataModel) {
221                     checkItemInfoLocked(item.id, item, stackTrace);
222                     mBgDataModel.addItem(mContext, item, true);
223                 }
224             }
225         });
226     }
227 
228     /**
229      * Removes the specified item from the database
230      */
deleteItemFromDatabase(ItemInfo item)231     public void deleteItemFromDatabase(ItemInfo item) {
232         deleteItemsFromDatabase(Arrays.asList(item));
233     }
234 
235     /**
236      * Removes all the items from the database matching {@param matcher}.
237      */
deleteItemsFromDatabase(ItemInfoMatcher matcher)238     public void deleteItemsFromDatabase(ItemInfoMatcher matcher) {
239         deleteItemsFromDatabase(matcher.filterItemInfos(mBgDataModel.itemsIdMap));
240     }
241 
242     /**
243      * Removes the specified items from the database
244      */
deleteItemsFromDatabase(final Iterable<? extends ItemInfo> items)245     public void deleteItemsFromDatabase(final Iterable<? extends ItemInfo> items) {
246         mWorkerExecutor.execute(new Runnable() {
247             public void run() {
248                 for (ItemInfo item : items) {
249                     final Uri uri = Favorites.getContentUri(item.id);
250                     mContext.getContentResolver().delete(uri, null, null);
251 
252                     mBgDataModel.removeItem(mContext, item);
253                 }
254             }
255         });
256     }
257 
258     /**
259      * Remove the specified folder and all its contents from the database.
260      */
deleteFolderAndContentsFromDatabase(final FolderInfo info)261     public void deleteFolderAndContentsFromDatabase(final FolderInfo info) {
262         mWorkerExecutor.execute(new Runnable() {
263             public void run() {
264                 ContentResolver cr = mContext.getContentResolver();
265                 cr.delete(LauncherSettings.Favorites.CONTENT_URI,
266                         LauncherSettings.Favorites.CONTAINER + "=" + info.id, null);
267                 mBgDataModel.removeItem(mContext, info.contents);
268                 info.contents.clear();
269 
270                 cr.delete(LauncherSettings.Favorites.getContentUri(info.id), null, null);
271                 mBgDataModel.removeItem(mContext, info);
272             }
273         });
274     }
275 
276     private class UpdateItemRunnable extends UpdateItemBaseRunnable {
277         private final ItemInfo mItem;
278         private final ContentWriter mWriter;
279         private final long mItemId;
280 
UpdateItemRunnable(ItemInfo item, ContentWriter writer)281         UpdateItemRunnable(ItemInfo item, ContentWriter writer) {
282             mItem = item;
283             mWriter = writer;
284             mItemId = item.id;
285         }
286 
287         @Override
run()288         public void run() {
289             Uri uri = Favorites.getContentUri(mItemId);
290             mContext.getContentResolver().update(uri, mWriter.getValues(mContext), null, null);
291             updateItemArrays(mItem, mItemId);
292         }
293     }
294 
295     private class UpdateItemsRunnable extends UpdateItemBaseRunnable {
296         private final ArrayList<ContentValues> mValues;
297         private final ArrayList<ItemInfo> mItems;
298 
UpdateItemsRunnable(ArrayList<ItemInfo> items, ArrayList<ContentValues> values)299         UpdateItemsRunnable(ArrayList<ItemInfo> items, ArrayList<ContentValues> values) {
300             mValues = values;
301             mItems = items;
302         }
303 
304         @Override
run()305         public void run() {
306             ArrayList<ContentProviderOperation> ops = new ArrayList<>();
307             int count = mItems.size();
308             for (int i = 0; i < count; i++) {
309                 ItemInfo item = mItems.get(i);
310                 final long itemId = item.id;
311                 final Uri uri = Favorites.getContentUri(itemId);
312                 ContentValues values = mValues.get(i);
313 
314                 ops.add(ContentProviderOperation.newUpdate(uri).withValues(values).build());
315                 updateItemArrays(item, itemId);
316             }
317             try {
318                 mContext.getContentResolver().applyBatch(LauncherProvider.AUTHORITY, ops);
319             } catch (Exception e) {
320                 e.printStackTrace();
321             }
322         }
323     }
324 
325     private abstract class UpdateItemBaseRunnable implements Runnable {
326         private final StackTraceElement[] mStackTrace;
327 
UpdateItemBaseRunnable()328         UpdateItemBaseRunnable() {
329             mStackTrace = new Throwable().getStackTrace();
330         }
331 
updateItemArrays(ItemInfo item, long itemId)332         protected void updateItemArrays(ItemInfo item, long itemId) {
333             // Lock on mBgLock *after* the db operation
334             synchronized (mBgDataModel) {
335                 checkItemInfoLocked(itemId, item, mStackTrace);
336 
337                 if (item.container != Favorites.CONTAINER_DESKTOP &&
338                         item.container != Favorites.CONTAINER_HOTSEAT) {
339                     // Item is in a folder, make sure this folder exists
340                     if (!mBgDataModel.folders.containsKey(item.container)) {
341                         // An items container is being set to a that of an item which is not in
342                         // the list of Folders.
343                         String msg = "item: " + item + " container being set to: " +
344                                 item.container + ", not in the list of folders";
345                         Log.e(TAG, msg);
346                     }
347                 }
348 
349                 // Items are added/removed from the corresponding FolderInfo elsewhere, such
350                 // as in Workspace.onDrop. Here, we just add/remove them from the list of items
351                 // that are on the desktop, as appropriate
352                 ItemInfo modelItem = mBgDataModel.itemsIdMap.get(itemId);
353                 if (modelItem != null &&
354                         (modelItem.container == Favorites.CONTAINER_DESKTOP ||
355                                 modelItem.container == Favorites.CONTAINER_HOTSEAT)) {
356                     switch (modelItem.itemType) {
357                         case Favorites.ITEM_TYPE_APPLICATION:
358                         case Favorites.ITEM_TYPE_SHORTCUT:
359                         case Favorites.ITEM_TYPE_DEEP_SHORTCUT:
360                         case Favorites.ITEM_TYPE_FOLDER:
361                             if (!mBgDataModel.workspaceItems.contains(modelItem)) {
362                                 mBgDataModel.workspaceItems.add(modelItem);
363                             }
364                             break;
365                         default:
366                             break;
367                     }
368                 } else {
369                     mBgDataModel.workspaceItems.remove(modelItem);
370                 }
371             }
372         }
373     }
374 }
375