diff options
author | Sunny Goyal <sunnygoyal@google.com> | 2017-01-05 21:50:27 -0800 |
---|---|---|
committer | Sunny Goyal <sunnygoyal@google.com> | 2017-01-10 13:53:50 -0800 |
commit | aaf86fe9a0406720cca7cfe204f28e2d2de085eb (patch) | |
tree | cc3c034548692ac963545d9eb11dc5742099ff74 | |
parent | e09bacc1ef50aa7c4fe551a7360d7f5613927ee9 (diff) | |
download | android_packages_apps_Trebuchet-aaf86fe9a0406720cca7cfe204f28e2d2de085eb.tar.gz android_packages_apps_Trebuchet-aaf86fe9a0406720cca7cfe204f28e2d2de085eb.tar.bz2 android_packages_apps_Trebuchet-aaf86fe9a0406720cca7cfe204f28e2d2de085eb.zip |
Refactoring some loadWorkspace logic in a separate class
Bug: 34112546
Change-Id: I8a43ed1646056aa1957ac3d6ea82018691df6386
-rw-r--r-- | src/com/android/launcher3/AppWidgetsRestoredReceiver.java | 13 | ||||
-rw-r--r-- | src/com/android/launcher3/InstallShortcutReceiver.java | 42 | ||||
-rw-r--r-- | src/com/android/launcher3/Launcher.java | 5 | ||||
-rw-r--r-- | src/com/android/launcher3/LauncherModel.java | 558 | ||||
-rw-r--r-- | src/com/android/launcher3/graphics/LauncherIcons.java | 12 | ||||
-rw-r--r-- | src/com/android/launcher3/model/LoaderCursor.java | 445 | ||||
-rw-r--r-- | src/com/android/launcher3/util/ContentWriter.java | 28 | ||||
-rw-r--r-- | src/com/android/launcher3/util/CursorIconInfo.java | 86 | ||||
-rw-r--r-- | src/com/android/launcher3/util/PackageManagerHelper.java | 10 | ||||
-rw-r--r-- | tests/src/com/android/launcher3/model/LoaderCursorTest.java | 234 |
10 files changed, 852 insertions, 581 deletions
diff --git a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java index c5b31046c..c6f1c48b9 100644 --- a/src/com/android/launcher3/AppWidgetsRestoredReceiver.java +++ b/src/com/android/launcher3/AppWidgetsRestoredReceiver.java @@ -5,13 +5,13 @@ import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.BroadcastReceiver; import android.content.ContentResolver; -import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.util.Log; import com.android.launcher3.LauncherSettings.Favorites; +import com.android.launcher3.util.ContentWriter; public class AppWidgetsRestoredReceiver extends BroadcastReceiver { @@ -50,14 +50,13 @@ public class AppWidgetsRestoredReceiver extends BroadcastReceiver { state = LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY; } - ContentValues values = new ContentValues(); - values.put(LauncherSettings.Favorites.APPWIDGET_ID, newWidgetIds[i]); - values.put(LauncherSettings.Favorites.RESTORED, state); - String[] widgetIdParams = new String[] { Integer.toString(oldWidgetIds[i]) }; + int result = new ContentWriter(context, new ContentWriter.CommitParams( + "appWidgetId=? and (restored & 1) = 1", widgetIdParams)) + .put(LauncherSettings.Favorites.APPWIDGET_ID, newWidgetIds[i]) + .put(LauncherSettings.Favorites.RESTORED, state) + .commit(); - int result = cr.update(Favorites.CONTENT_URI, values, - "appWidgetId=? and (restored & 1) = 1", widgetIdParams); if (result == 0) { Cursor cursor = cr.query(Favorites.CONTENT_URI, new String[] {Favorites.APPWIDGET_ID}, diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java index df2deb8ca..ecbfe3823 100644 --- a/src/com/android/launcher3/InstallShortcutReceiver.java +++ b/src/com/android/launcher3/InstallShortcutReceiver.java @@ -26,6 +26,7 @@ import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.os.Parcelable; import android.os.Process; import android.os.UserHandle; import android.text.TextUtils; @@ -35,6 +36,7 @@ import android.util.Log; import com.android.launcher3.compat.LauncherActivityInfoCompat; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.shortcuts.ShortcutKey; @@ -454,7 +456,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver { widgetInfo.spanY = Math.min(info.spanY, idp.numRows); return widgetInfo; } else { - return LauncherAppState.getInstance().getModel().infoFromShortcutIntent(mContext, data); + return createShortcutInfo(data, LauncherAppState.getInstance()); } } @@ -598,4 +600,42 @@ public class InstallShortcutReceiver extends BroadcastReceiver { return installQueue; } } + + private static ShortcutInfo createShortcutInfo(Intent data, LauncherAppState app) { + Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); + String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); + Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON); + + if (intent == null) { + // If the intent is null, we can't construct a valid ShortcutInfo, so we return null + Log.e(TAG, "Can't construct ShorcutInfo with null intent"); + return null; + } + + final ShortcutInfo info = new ShortcutInfo(); + + // Only support intents for current user for now. Intents sent from other + // users wouldn't get here without intent forwarding anyway. + info.user = Process.myUserHandle(); + + if (bitmap instanceof Bitmap) { + info.iconBitmap = LauncherIcons.createIconBitmap((Bitmap) bitmap, app.getContext()); + } else { + Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); + if (extra instanceof Intent.ShortcutIconResource) { + info.iconResource = (Intent.ShortcutIconResource) extra; + info.iconBitmap = LauncherIcons.createIconBitmap(info.iconResource, app.getContext()); + } + } + if (info.iconBitmap == null) { + info.iconBitmap = app.getIconCache().getDefaultIcon(info.user); + } + + info.title = Utilities.trim(name); + info.contentDescription = UserManagerCompat.getInstance(app.getContext()) + .getBadgedLabelForUser(info.title, info.user); + info.intent = intent; + return info; + } + } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index 8e28912db..d6c8cfb95 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -2337,13 +2337,14 @@ public class Launcher extends Activity showBrokenAppInstallDialog(packageName, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { - startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info); + startActivitySafely( + v, PackageManagerHelper.getMarketIntent(packageName), info); } }); } else { // Download has started. final String packageName = info.providerName.getPackageName(); - startActivitySafely(v, LauncherModel.getMarketIntent(packageName), info); + startActivitySafely(v, PackageManagerHelper.getMarketIntent(packageName), info); } } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index daa910fc8..616b2c7dd 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -24,21 +24,16 @@ import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; import android.content.Intent; -import android.content.Intent.ShortcutIconResource; import android.content.IntentFilter; import android.content.pm.PackageManager; -import android.database.Cursor; -import android.graphics.Bitmap; import android.net.Uri; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; -import android.os.Parcelable; import android.os.Process; import android.os.SystemClock; import android.os.Trace; import android.os.UserHandle; -import android.provider.BaseColumns; import android.text.TextUtils; import android.util.Log; import android.util.LongSparseArray; @@ -50,18 +45,17 @@ import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.PackageInstallerCompat; import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.config.ProviderConfig; import com.android.launcher3.dynamicui.ExtractionUtils; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIcon; -import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.logging.FileLog; import com.android.launcher3.model.AddWorkspaceItemsTask; import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.CacheDataUpdatedTask; import com.android.launcher3.model.ExtendedModelTask; import com.android.launcher3.model.GridSizeMigrationTask; +import com.android.launcher3.model.LoaderCursor; import com.android.launcher3.model.PackageInstallStateChangedTask; import com.android.launcher3.model.PackageItemInfo; import com.android.launcher3.model.PackageUpdatedTask; @@ -77,10 +71,7 @@ import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.ContentWriter; -import com.android.launcher3.util.CursorIconInfo; -import com.android.launcher3.util.GridOccupancy; import com.android.launcher3.util.ItemInfoMatcher; -import com.android.launcher3.util.LongArrayMap; import com.android.launcher3.util.ManagedProfileHeuristic; import com.android.launcher3.util.MultiHashMap; import com.android.launcher3.util.PackageManagerHelper; @@ -91,7 +82,6 @@ import com.android.launcher3.util.ViewOnDrawExecutor; import java.lang.ref.WeakReference; import java.net.URISyntaxException; -import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -1096,96 +1086,6 @@ public class LauncherModel extends BroadcastReceiver } } - // check & update map of what's occupied; used to discard overlapping/invalid items - private boolean checkItemPlacement(LongArrayMap<GridOccupancy> occupied, ItemInfo item, - ArrayList<Long> workspaceScreens) { - LauncherAppState app = LauncherAppState.getInstance(); - InvariantDeviceProfile profile = app.getInvariantDeviceProfile(); - - long containerIndex = item.screenId; - if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - // Return early if we detect that an item is under the hotseat button - if (!FeatureFlags.NO_ALL_APPS_ICON && - profile.isAllAppsButtonRank((int) item.screenId)) { - Log.e(TAG, "Error loading shortcut into hotseat " + item - + " into position (" + item.screenId + ":" + item.cellX + "," - + item.cellY + ") occupied by all apps"); - return false; - } - - final GridOccupancy hotseatOccupancy = - occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT); - - if (item.screenId >= profile.numHotseatIcons) { - Log.e(TAG, "Error loading shortcut " + item - + " into hotseat position " + item.screenId - + ", position out of bounds: (0 to " + (profile.numHotseatIcons - 1) - + ")"); - return false; - } - - if (hotseatOccupancy != null) { - if (hotseatOccupancy.cells[(int) item.screenId][0]) { - Log.e(TAG, "Error loading shortcut into hotseat " + item - + " into position (" + item.screenId + ":" + item.cellX + "," - + item.cellY + ") already occupied"); - return false; - } else { - hotseatOccupancy.cells[(int) item.screenId][0] = true; - return true; - } - } else { - final GridOccupancy occupancy = new GridOccupancy(profile.numHotseatIcons, 1); - occupancy.cells[(int) item.screenId][0] = true; - occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, occupancy); - return true; - } - } else if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { - if (!workspaceScreens.contains((Long) item.screenId)) { - // The item has an invalid screen id. - return false; - } - } else { - // Skip further checking if it is not the hotseat or workspace container - return true; - } - - final int countX = profile.numColumns; - final int countY = profile.numRows; - if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && - item.cellX < 0 || item.cellY < 0 || - item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) { - Log.e(TAG, "Error loading shortcut " + item - + " into cell (" + containerIndex + "-" + item.screenId + ":" - + item.cellX + "," + item.cellY - + ") out of screen bounds ( " + countX + "x" + countY + ")"); - return false; - } - - if (!occupied.containsKey(item.screenId)) { - GridOccupancy screen = new GridOccupancy(countX + 1, countY + 1); - if (item.screenId == Workspace.FIRST_SCREEN_ID) { - // Mark the first row as occupied (if the feature is enabled) - // in order to account for the QSB. - screen.markCells(0, 0, countX + 1, 1, FeatureFlags.QSB_ON_FIRST_SCREEN); - } - occupied.put(item.screenId, screen); - } - final GridOccupancy occupancy = occupied.get(item.screenId); - - // Check if any workspace icons overlap with each other - if (occupancy.isRegionVacant(item.cellX, item.cellY, item.spanX, item.spanY)) { - occupancy.markCells(item, true); - return true; - } else { - Log.e(TAG, "Error loading shortcut " + item - + " into cell (" + containerIndex + "-" + item.screenId + ":" - + item.cellX + "," + item.cellX + "," + item.spanX + "," + item.spanY - + ") already occupied"); - return false; - } - } - private void loadWorkspace() { if (LauncherAppState.PROFILE_STARTUP) { Trace.beginSection("Loading Workspace"); @@ -1237,37 +1137,19 @@ public class LauncherModel extends BroadcastReceiver .getInstance(mContext).updateAndGetActiveSessionCache(); sBgDataModel.workspaceScreens.addAll(loadWorkspaceScreensDb(mContext)); - final ArrayList<Long> itemsToRemove = new ArrayList<>(); - final ArrayList<Long> restoredRows = new ArrayList<>(); Map<ShortcutKey, ShortcutInfoCompat> shortcutKeyToPinnedShortcuts = new HashMap<>(); - final Uri contentUri = LauncherSettings.Favorites.CONTENT_URI; - if (DEBUG_LOADERS) Log.d(TAG, "loading model from " + contentUri); - final Cursor c = contentResolver.query(contentUri, null, null, null, null); - - // +1 for the hotseat (it can be larger than the workspace) - // Load workspace in reverse order to ensure that latest items are loaded first (and - // before any earlier duplicates) - final LongArrayMap<GridOccupancy> occupied = new LongArrayMap<>(); + final LoaderCursor c = new LoaderCursor(contentResolver.query( + LauncherSettings.Favorites.CONTENT_URI, null, null, null, null), mApp); + HashMap<ComponentKey, AppWidgetProviderInfo> widgetProvidersMap = null; try { - final int idIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites._ID); final int intentIndex = c.getColumnIndexOrThrow (LauncherSettings.Favorites.INTENT); - final int containerIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.CONTAINER); - final int itemTypeIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.ITEM_TYPE); final int appWidgetIdIndex = c.getColumnIndexOrThrow( LauncherSettings.Favorites.APPWIDGET_ID); final int appWidgetProviderIndex = c.getColumnIndexOrThrow( LauncherSettings.Favorites.APPWIDGET_PROVIDER); - final int screenIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.SCREEN); - final int cellXIndex = c.getColumnIndexOrThrow - (LauncherSettings.Favorites.CELLX); - final int cellYIndex = c.getColumnIndexOrThrow - (LauncherSettings.Favorites.CELLY); final int spanXIndex = c.getColumnIndexOrThrow (LauncherSettings.Favorites.SPANX); final int spanYIndex = c.getColumnIndexOrThrow( @@ -1276,13 +1158,10 @@ public class LauncherModel extends BroadcastReceiver LauncherSettings.Favorites.RANK); final int restoredIndex = c.getColumnIndexOrThrow( LauncherSettings.Favorites.RESTORED); - final int profileIdIndex = c.getColumnIndexOrThrow( - LauncherSettings.Favorites.PROFILE_ID); final int optionsIndex = c.getColumnIndexOrThrow( LauncherSettings.Favorites.OPTIONS); - final CursorIconInfo cursorIconInfo = new CursorIconInfo(mContext, c); - final LongSparseArray<UserHandle> allUsers = new LongSparseArray<>(); + final LongSparseArray<UserHandle> allUsers = c.allUsers; final LongSparseArray<Boolean> quietMode = new LongSparseArray<>(); final LongSparseArray<Boolean> unlockedUsers = new LongSparseArray<>(); for (UserHandle user : mUserManager.getUserProfiles()) { @@ -1314,44 +1193,42 @@ public class LauncherModel extends BroadcastReceiver ShortcutInfo info; String intentDescription; LauncherAppWidgetInfo appWidgetInfo; - int container; - long id; - long serialNumber; Intent intent; - UserHandle user; String targetPackage; while (!mStopped && c.moveToNext()) { try { - int itemType = c.getInt(itemTypeIndex); + if (c.user == null) { + // User has been deleted, remove the item. + c.markDeleted("User has been deleted"); + continue; + } + boolean restored = 0 != c.getInt(restoredIndex); boolean allowMissingTarget = false; - container = c.getInt(containerIndex); - - switch (itemType) { + switch (c.itemType) { + case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: { + if (!Process.myUserHandle().equals(c.user)) { + c.markDeleted("Legacy shortcuts are only allowed for default user"); + continue; + } + // Follow through. + } case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION: - case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT: case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT: - id = c.getLong(idIndex); intentDescription = c.getString(intentIndex); - serialNumber = c.getInt(profileIdIndex); - user = allUsers.get(serialNumber); int promiseType = c.getInt(restoredIndex); int disabledState = 0; targetPackage = null; - if (user == null) { - // User has been deleted remove the item. - itemsToRemove.add(id); - continue; - } + try { intent = Intent.parseUri(intentDescription, 0); ComponentName cn = intent.getComponent(); if (cn != null && cn.getPackageName() != null) { boolean validPkg = launcherApps.isPackageEnabledForProfile( - cn.getPackageName(), user); + cn.getPackageName(), c.user); boolean validComponent = validPkg && - launcherApps.isActivityEnabledForProfile(cn, user); + launcherApps.isActivityEnabledForProfile(cn, c.user); if (validPkg) { targetPackage = cn.getPackageName(); } @@ -1359,10 +1236,10 @@ public class LauncherModel extends BroadcastReceiver if (validComponent) { if (restored) { // no special handling necessary for this item - restoredRows.add(id); + c.markRestored(); restored = false; } - if (quietMode.get(serialNumber)) { + if (quietMode.get(c.serialNumber)) { disabledState = ShortcutInfo.FLAG_DISABLED_QUIET_USER; } } else if (validPkg) { @@ -1373,22 +1250,20 @@ public class LauncherModel extends BroadcastReceiver intent = manager.getLaunchIntentForPackage( cn.getPackageName()); if (intent != null) { - ContentValues values = new ContentValues(); - values.put(LauncherSettings.Favorites.INTENT, - intent.toUri(0)); - updateItem(id, values); + c.updater().put( + LauncherSettings.Favorites.INTENT, + intent.toUri(0)).commit(); } } if (intent == null) { // The app is installed but the component is no // longer available. - FileLog.d(TAG, "Invalid component removed: " + cn); - itemsToRemove.add(id); + c.markDeleted("Invalid component removed: " + cn); continue; } else { // no special handling necessary for this item - restoredRows.add(id); + c.markRestored(); restored = false; } } else if (restored) { @@ -1401,13 +1276,11 @@ public class LauncherModel extends BroadcastReceiver } else if (installingPkgs.containsKey(cn.getPackageName())) { // App restore has started. Update the flag promiseType |= ShortcutInfo.FLAG_RESTORE_STARTED; - ContentValues values = new ContentValues(); - values.put(LauncherSettings.Favorites.RESTORED, - promiseType); - updateItem(id, values); + c.updater().put( + LauncherSettings.Favorites.RESTORED, + promiseType).commit(); } else { - FileLog.d(TAG, "Unrestored package removed: " + cn); - itemsToRemove.add(id); + c.markDeleted("Unrestored package removed: " + cn); continue; } } else if (PackageManagerHelper.isAppOnSdcard( @@ -1419,70 +1292,64 @@ public class LauncherModel extends BroadcastReceiver // SdCard is not ready yet. Package might get available, // once it is ready. Log.d(TAG, "Invalid package: " + cn + " (check again later)"); - pendingPackages.addToList(user, cn.getPackageName()); + pendingPackages.addToList(c.user, cn.getPackageName()); allowMissingTarget = true; // Add the icon on the workspace anyway. } else { // Do not wait for external media load anymore. // Log the invalid package, and remove it - FileLog.d(TAG, "Invalid package removed: " + cn); - itemsToRemove.add(id); + c.markDeleted("Invalid package removed: " + cn); continue; } } else if (cn == null) { // For shortcuts with no component, keep them as they are - restoredRows.add(id); + c.markRestored(); restored = false; } } catch (URISyntaxException e) { - FileLog.d(TAG, "Invalid uri: " + intentDescription); - itemsToRemove.add(id); + c.markDeleted("Invalid uri: " + intentDescription); continue; } - boolean useLowResIcon = container >= 0 && + boolean useLowResIcon = !c.isOnWorkspaceOrHotseat() && c.getInt(rankIndex) >= FolderIcon.NUM_ITEMS_IN_PREVIEW; if (restored) { - if (user.equals(Process.myUserHandle())) { - info = getRestoredItemInfo(c, intent, - promiseType, itemType, cursorIconInfo); - intent = getRestoredItemIntent(c, context, intent); + if (c.user.equals(Process.myUserHandle())) { + info = c.getRestoredItemInfo(intent, promiseType); + intent = PackageManagerHelper.getMarketIntent( + intent.getComponent().getPackageName()); } else { // Don't restore items for other profiles. - itemsToRemove.add(id); + c.markDeleted("Restore from managed profile not supported"); continue; } - } else if (itemType == + } else if (c.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { - info = getAppShortcutInfo(intent, user, c, - cursorIconInfo, allowMissingTarget, useLowResIcon); - } else if (itemType == + info = c.getAppShortcutInfo( + intent, allowMissingTarget, useLowResIcon); + } else if (c.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) { - ShortcutKey key = ShortcutKey.fromIntent(intent, user); - if (unlockedUsers.get(serialNumber)) { + ShortcutKey key = ShortcutKey.fromIntent(intent, c.user); + if (unlockedUsers.get(c.serialNumber)) { ShortcutInfoCompat pinnedShortcut = shortcutKeyToPinnedShortcuts.get(key); if (pinnedShortcut == null) { // The shortcut is no longer valid. - itemsToRemove.add(id); + c.markDeleted("Pinned shortcut not found"); continue; } info = new ShortcutInfo(pinnedShortcut, context); intent = info.intent; } else { // Create a shortcut info in disabled mode for now. - info = new ShortcutInfo(); - info.user = user; - info.itemType = itemType; - loadInfoFromCursor(info, c, cursorIconInfo); - + info = c.loadSimpleShortcut(); info.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER; } } else { // item type == ITEM_TYPE_SHORTCUT - info = getShortcutInfo(c, cursorIconInfo); + info = c.loadSimpleShortcut(); // Shortcuts are only available on the primary profile if (PackageManagerHelper.isAppSuspended(manager, targetPackage)) { @@ -1503,30 +1370,23 @@ public class LauncherModel extends BroadcastReceiver } if (info != null) { - info.id = id; + c.applyCommonProperties(info); + info.intent = intent; - info.container = container; - info.screenId = c.getInt(screenIndex); - info.cellX = c.getInt(cellXIndex); - info.cellY = c.getInt(cellYIndex); info.rank = c.getInt(rankIndex); info.spanX = 1; info.spanY = 1; - info.intent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber); + // TODO: Remove this extra. Instead we should be using + // itemInfo#user. + info.intent.putExtra(ItemInfo.EXTRA_PROFILE, c.serialNumber); if (info.promisedIntent != null) { - info.promisedIntent.putExtra(ItemInfo.EXTRA_PROFILE, serialNumber); + info.promisedIntent.putExtra(ItemInfo.EXTRA_PROFILE, c.serialNumber); } info.isDisabled |= disabledState; if (isSafeMode && !Utilities.isSystemApp(context, intent)) { info.isDisabled |= ShortcutInfo.FLAG_DISABLED_SAFEMODE; } - // check & update map of what's occupied - if (!checkItemPlacement(occupied, info, sBgDataModel.workspaceScreens)) { - itemsToRemove.add(id); - break; - } - if (restored) { ComponentName cn = info.getTargetComponent(); if (cn != null) { @@ -1539,55 +1399,38 @@ public class LauncherModel extends BroadcastReceiver } } - sBgDataModel.addItem(info, false); + c.checkAndAddItem(info, sBgDataModel); } else { throw new RuntimeException("Unexpected null ShortcutInfo"); } break; case LauncherSettings.Favorites.ITEM_TYPE_FOLDER: - id = c.getLong(idIndex); - FolderInfo folderInfo = sBgDataModel.findOrMakeFolder(id); + FolderInfo folderInfo = sBgDataModel.findOrMakeFolder(c.id); + c.applyCommonProperties(folderInfo); // Do not trim the folder label, as is was set by the user. - folderInfo.title = c.getString(cursorIconInfo.titleIndex); - folderInfo.id = id; - folderInfo.container = container; - folderInfo.screenId = c.getInt(screenIndex); - folderInfo.cellX = c.getInt(cellXIndex); - folderInfo.cellY = c.getInt(cellYIndex); + folderInfo.title = c.getString(c.titleIndex); folderInfo.spanX = 1; folderInfo.spanY = 1; folderInfo.options = c.getInt(optionsIndex); - // check & update map of what's occupied - if (!checkItemPlacement(occupied, folderInfo, sBgDataModel.workspaceScreens)) { - itemsToRemove.add(id); - break; - } if (restored) { // no special handling required for restored folders - restoredRows.add(id); + c.markRestored(); } - sBgDataModel.addItem(folderInfo, false); + c.checkAndAddItem(folderInfo, sBgDataModel); break; case LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET: case LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: // Read all Launcher-specific widget details - boolean customWidget = itemType == + boolean customWidget = c.itemType == LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET; int appWidgetId = c.getInt(appWidgetIdIndex); - serialNumber = c.getLong(profileIdIndex); String savedProvider = c.getString(appWidgetProviderIndex); - id = c.getLong(idIndex); - user = allUsers.get(serialNumber); - if (user == null) { - itemsToRemove.add(id); - continue; - } final ComponentName component = ComponentName.unflattenFromString(savedProvider); @@ -1605,14 +1448,14 @@ public class LauncherModel extends BroadcastReceiver final AppWidgetProviderInfo provider = widgetProvidersMap.get( new ComponentKey( ComponentName.unflattenFromString(savedProvider), - user)); + c.user)); final boolean isProviderReady = isValidProvider(provider); if (!isSafeMode && !customWidget && wasProviderReady && !isProviderReady) { - FileLog.d(TAG, "Deleting widget that isn't installed anymore: " + c.markDeleted( + "Deleting widget that isn't installed anymore: " + provider); - itemsToRemove.add(id); } else { if (isProviderReady) { appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, @@ -1637,7 +1480,7 @@ public class LauncherModel extends BroadcastReceiver } appWidgetInfo.restoreStatus = status; } else { - Log.v(TAG, "Widget restore pending id=" + id + Log.v(TAG, "Widget restore pending id=" + c.id + " appWidgetId=" + appWidgetId + " status =" + restoreStatus); appWidgetInfo = new LauncherAppWidgetInfo(appWidgetId, @@ -1652,8 +1495,7 @@ public class LauncherModel extends BroadcastReceiver appWidgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_RESTORE_STARTED; } else if (!isSafeMode) { - FileLog.d(TAG, "Unrestored widget removed: " + component); - itemsToRemove.add(id); + c.markDeleted("Unrestored widget removed: " + component); continue; } @@ -1669,44 +1511,31 @@ public class LauncherModel extends BroadcastReceiver } } - appWidgetInfo.id = id; - appWidgetInfo.screenId = c.getInt(screenIndex); - appWidgetInfo.cellX = c.getInt(cellXIndex); - appWidgetInfo.cellY = c.getInt(cellYIndex); + c.applyCommonProperties(appWidgetInfo); appWidgetInfo.spanX = c.getInt(spanXIndex); appWidgetInfo.spanY = c.getInt(spanYIndex); - appWidgetInfo.user = user; + appWidgetInfo.user = c.user; - if (container != LauncherSettings.Favorites.CONTAINER_DESKTOP && - container != LauncherSettings.Favorites.CONTAINER_HOTSEAT) { - Log.e(TAG, "Widget found where container != " + + if (!c.isOnWorkspaceOrHotseat()) { + c.markDeleted("Widget found where container != " + "CONTAINER_DESKTOP nor CONTAINER_HOTSEAT - ignoring!"); - itemsToRemove.add(id); continue; } - appWidgetInfo.container = container; - // check & update map of what's occupied - if (!checkItemPlacement(occupied, appWidgetInfo, sBgDataModel.workspaceScreens)) { - itemsToRemove.add(id); - break; - } - if (!customWidget) { String providerName = appWidgetInfo.providerName.flattenToString(); if (!providerName.equals(savedProvider) || (appWidgetInfo.restoreStatus != restoreStatus)) { - ContentValues values = new ContentValues(); - values.put( - LauncherSettings.Favorites.APPWIDGET_PROVIDER, - providerName); - values.put(LauncherSettings.Favorites.RESTORED, - appWidgetInfo.restoreStatus); - updateItem(id, values); + c.updater() + .put(LauncherSettings.Favorites.APPWIDGET_PROVIDER, + providerName) + .put(LauncherSettings.Favorites.RESTORED, + appWidgetInfo.restoreStatus) + .commit(); } } - sBgDataModel.addItem(appWidgetInfo, false); + c.checkAndAddItem(appWidgetInfo, sBgDataModel); } break; } @@ -1724,16 +1553,8 @@ public class LauncherModel extends BroadcastReceiver return; } - if (itemsToRemove.size() > 0) { - // Remove dead items - contentResolver.delete(LauncherSettings.Favorites.CONTENT_URI, - Utilities.createDbSelectionQuery( - LauncherSettings.Favorites._ID, itemsToRemove), null); - if (DEBUG_LOADERS) { - Log.d(TAG, "Removed = " + Utilities.createDbSelectionQuery( - LauncherSettings.Favorites._ID, itemsToRemove)); - } - + // Remove dead items + if (c.commitDeleted()) { // Remove any empty folder ArrayList<Long> deletedFolderIds = (ArrayList<Long>) LauncherSettings.Settings .call(contentResolver, @@ -1774,15 +1595,7 @@ public class LauncherModel extends BroadcastReceiver } } - if (restoredRows.size() > 0) { - // Update restored items that no longer require special handling - ContentValues values = new ContentValues(); - values.put(LauncherSettings.Favorites.RESTORED, 0); - contentResolver.update(LauncherSettings.Favorites.CONTENT_URI, values, - Utilities.createDbSelectionQuery( - LauncherSettings.Favorites._ID, restoredRows), null); - } - + c.commitRestoredItems(); if (!isSdCardReady && !pendingPackages.isEmpty()) { context.registerReceiver( new SdCardAvailableReceiver( @@ -1793,7 +1606,7 @@ public class LauncherModel extends BroadcastReceiver } // Remove any empty screens - ArrayList<Long> unusedScreens = new ArrayList<Long>(sBgDataModel.workspaceScreens); + ArrayList<Long> unusedScreens = new ArrayList<>(sBgDataModel.workspaceScreens); for (ItemInfo item: sBgDataModel.itemsIdMap) { long screenId = item.screenId; if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && @@ -1807,40 +1620,12 @@ public class LauncherModel extends BroadcastReceiver sBgDataModel.workspaceScreens.removeAll(unusedScreens); updateWorkspaceScreenOrder(context, sBgDataModel.workspaceScreens); } - - if (DEBUG_LOADERS) { - Log.d(TAG, "loaded workspace in " + (SystemClock.uptimeMillis()-t) + "ms"); - Log.d(TAG, "workspace layout: "); - int nScreens = occupied.size(); - for (int y = 0; y < countY; y++) { - String line = ""; - - for (int i = 0; i < nScreens; i++) { - long screenId = occupied.keyAt(i); - if (screenId > 0) { - line += " | "; - } - } - Log.d(TAG, "[ " + line + " ]"); - } - } } if (LauncherAppState.PROFILE_STARTUP) { Trace.endSection(); } } - /** - * Partially updates the item without any notification. Must be called on the worker thread. - */ - private void updateItem(long itemId, ContentValues update) { - mContext.getContentResolver().update( - LauncherSettings.Favorites.CONTENT_URI, - update, - BaseColumns._ID + "= ?", - new String[]{Long.toString(itemId)}); - } - /** Filters the set of items who are directly or indirectly (via another container) on the * specified screen. */ private void filterCurrentWorkspaceItems(long currentScreenId, @@ -2482,179 +2267,6 @@ public class LauncherModel extends BroadcastReceiver }); } - /** - * Make an ShortcutInfo object for a restored application or shortcut item that points - * to a package that is not yet installed on the system. - */ - public ShortcutInfo getRestoredItemInfo(Cursor c, Intent intent, - int promiseType, int itemType, CursorIconInfo iconInfo) { - final ShortcutInfo info = new ShortcutInfo(); - info.user = Process.myUserHandle(); - info.promisedIntent = intent; - - info.iconBitmap = iconInfo.loadIcon(c, info); - // the fallback icon - if (info.iconBitmap == null) { - mIconCache.getTitleAndIcon(info, false /* useLowResIcon */); - } - - if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) { - String title = iconInfo.getTitle(c); - if (!TextUtils.isEmpty(title)) { - info.title = Utilities.trim(title); - } - } else if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) { - if (TextUtils.isEmpty(info.title)) { - info.title = iconInfo.getTitle(c); - } - } else { - throw new InvalidParameterException("Invalid restoreType " + promiseType); - } - - info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user); - info.itemType = itemType; - info.status = promiseType; - return info; - } - - /** - * Make an Intent object for a restored application or shortcut item that points - * to the market page for the item. - */ - @Thunk Intent getRestoredItemIntent(Cursor c, Context context, Intent intent) { - ComponentName componentName = intent.getComponent(); - return getMarketIntent(componentName.getPackageName()); - } - - static Intent getMarketIntent(String packageName) { - return new Intent(Intent.ACTION_VIEW) - .setData(new Uri.Builder() - .scheme("market") - .authority("details") - .appendQueryParameter("id", packageName) - .build()); - } - - /** - * Make an ShortcutInfo object for a shortcut that is an application. - * - * If c is not null, then it will be used to fill in missing data like the title and icon. - */ - public ShortcutInfo getAppShortcutInfo(Intent intent, UserHandle user, Cursor c, - CursorIconInfo iconInfo, boolean allowMissingTarget, boolean useLowResIcon) { - if (user == null) { - Log.d(TAG, "Null user found in getShortcutInfo"); - return null; - } - - ComponentName componentName = intent.getComponent(); - if (componentName == null) { - Log.d(TAG, "Missing component found in getShortcutInfo"); - return null; - } - - Intent newIntent = new Intent(intent.getAction(), null); - newIntent.addCategory(Intent.CATEGORY_LAUNCHER); - newIntent.setComponent(componentName); - LauncherActivityInfoCompat lai = mLauncherApps.resolveActivity(newIntent, user); - if ((lai == null) && !allowMissingTarget) { - Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName); - return null; - } - - final ShortcutInfo info = new ShortcutInfo(); - info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; - info.user = user; - info.intent = newIntent; - - mIconCache.getTitleAndIcon(info, lai, useLowResIcon); - if (mIconCache.isDefaultIcon(info.iconBitmap, user) && c != null) { - Bitmap icon = iconInfo.loadIcon(c); - info.iconBitmap = icon != null ? icon : mIconCache.getDefaultIcon(user); - } - - if (lai != null && PackageManagerHelper.isAppSuspended(lai.getApplicationInfo())) { - info.isDisabled = ShortcutInfo.FLAG_DISABLED_SUSPENDED; - } - - // from the db - if (TextUtils.isEmpty(info.title) && c != null) { - info.title = iconInfo.getTitle(c); - } - - // fall back to the class name of the activity - if (info.title == null) { - info.title = componentName.getClassName(); - } - - info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user); - return info; - } - - /** - * Make an ShortcutInfo object for a shortcut that isn't an application. - */ - @Thunk ShortcutInfo getShortcutInfo(Cursor c, CursorIconInfo iconInfo) { - final ShortcutInfo info = new ShortcutInfo(); - // Non-app shortcuts are only supported for current user. - info.user = Process.myUserHandle(); - info.itemType = LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; - - // TODO: If there's an explicit component and we can't install that, delete it. - - loadInfoFromCursor(info, c, iconInfo); - return info; - } - - /** - * Make an ShortcutInfo object for a shortcut that isn't an application. - */ - public void loadInfoFromCursor(ShortcutInfo info, Cursor c, CursorIconInfo iconInfo) { - info.title = iconInfo.getTitle(c); - info.iconBitmap = iconInfo.loadIcon(c, info); - // the fallback icon - if (info.iconBitmap == null) { - info.iconBitmap = mIconCache.getDefaultIcon(info.user); - } - } - - ShortcutInfo infoFromShortcutIntent(Context context, Intent data) { - Intent intent = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_INTENT); - String name = data.getStringExtra(Intent.EXTRA_SHORTCUT_NAME); - Parcelable bitmap = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON); - - if (intent == null) { - // If the intent is null, we can't construct a valid ShortcutInfo, so we return null - Log.e(TAG, "Can't construct ShorcutInfo with null intent"); - return null; - } - - final ShortcutInfo info = new ShortcutInfo(); - - // Only support intents for current user for now. Intents sent from other - // users wouldn't get here without intent forwarding anyway. - info.user = Process.myUserHandle(); - - if (bitmap instanceof Bitmap) { - info.iconBitmap = LauncherIcons.createIconBitmap((Bitmap) bitmap, context); - } else { - Parcelable extra = data.getParcelableExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE); - if (extra instanceof ShortcutIconResource) { - info.iconResource = (ShortcutIconResource) extra; - info.iconBitmap = LauncherIcons.createIconBitmap(info.iconResource, context); - } - } - if (info.iconBitmap == null) { - info.iconBitmap = mIconCache.getDefaultIcon(info.user); - } - - info.title = Utilities.trim(name); - info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user); - info.intent = intent; - - return info; - } - static boolean isValidProvider(AppWidgetProviderInfo provider) { return (provider != null) && (provider.provider != null) && (provider.provider.getPackageName() != null); diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/graphics/LauncherIcons.java index 3f191bdc3..0619187bd 100644 --- a/src/com/android/launcher3/graphics/LauncherIcons.java +++ b/src/com/android/launcher3/graphics/LauncherIcons.java @@ -20,9 +20,7 @@ import android.content.Context; import android.content.Intent.ShortcutIconResource; import android.content.pm.PackageManager; import android.content.res.Resources; -import android.database.Cursor; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PaintFlagsDrawFilter; @@ -51,16 +49,6 @@ public class LauncherIcons { Paint.FILTER_BITMAP_FLAG)); } - - public static Bitmap createIconBitmap(Cursor c, int iconIndex, Context context) { - byte[] data = c.getBlob(iconIndex); - try { - return createIconBitmap(BitmapFactory.decodeByteArray(data, 0, data.length), context); - } catch (Exception e) { - return null; - } - } - /** * Returns a bitmap suitable for the all apps view. If the package or the resource do not * exist, it returns null. diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java new file mode 100644 index 000000000..99a6cdf5e --- /dev/null +++ b/src/com/android/launcher3/model/LoaderCursor.java @@ -0,0 +1,445 @@ +/* + * 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.ComponentName; +import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; +import android.content.Intent.ShortcutIconResource; +import android.database.Cursor; +import android.database.CursorWrapper; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.UserHandle; +import android.provider.BaseColumns; +import android.text.TextUtils; +import android.util.Log; +import android.util.LongSparseArray; + +import com.android.launcher3.IconCache; +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherSettings; +import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.Utilities; +import com.android.launcher3.Workspace; +import com.android.launcher3.compat.LauncherActivityInfoCompat; +import com.android.launcher3.compat.LauncherAppsCompat; +import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.logging.FileLog; +import com.android.launcher3.util.ContentWriter; +import com.android.launcher3.util.GridOccupancy; +import com.android.launcher3.util.LongArrayMap; +import com.android.launcher3.util.PackageManagerHelper; + +import java.security.InvalidParameterException; +import java.util.ArrayList; + +/** + * Extension of {@link Cursor} with utility methods for workspace loading. + */ +public class LoaderCursor extends CursorWrapper { + + private static final String TAG = "LoaderCursor"; + + public final LongSparseArray<UserHandle> allUsers = new LongSparseArray<>(); + + private final Context mContext; + private final UserManagerCompat mUserManager; + private final IconCache mIconCache; + private final InvariantDeviceProfile mIDP; + + private final ArrayList<Long> itemsToRemove = new ArrayList<>(); + private final ArrayList<Long> restoredRows = new ArrayList<>(); + private final LongArrayMap<GridOccupancy> occupied = new LongArrayMap<>(); + + private final int iconPackageIndex; + private final int iconResourceIndex; + private final int iconIndex; + public final int titleIndex; + + private final int idIndex; + private final int containerIndex; + private final int itemTypeIndex; + private final int screenIndex; + private final int cellXIndex; + private final int cellYIndex; + private final int profileIdIndex; + + // Properties loaded per iteration + public long serialNumber; + public UserHandle user; + public long id; + public long container; + public int itemType; + + public LoaderCursor(Cursor c, LauncherAppState app) { + super(c); + mContext = app.getContext(); + mIconCache = app.getIconCache(); + mIDP = app.getInvariantDeviceProfile(); + mUserManager = UserManagerCompat.getInstance(mContext); + + // Init column indices + iconIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); + iconPackageIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE); + iconResourceIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE); + titleIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); + + idIndex = getColumnIndexOrThrow(LauncherSettings.Favorites._ID); + containerIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.CONTAINER); + itemTypeIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.ITEM_TYPE); + screenIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.SCREEN); + cellXIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.CELLX); + cellYIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.CELLY); + profileIdIndex = getColumnIndexOrThrow(LauncherSettings.Favorites.PROFILE_ID); + } + + @Override + public boolean moveToNext() { + boolean result = super.moveToNext(); + if (result) { + // Load common properties. + itemType = getInt(itemTypeIndex); + container = getInt(containerIndex); + id = getLong(idIndex); + serialNumber = getInt(profileIdIndex); + user = allUsers.get(serialNumber); + } + return result; + } + + public ShortcutInfo loadSimpleShortcut() { + final ShortcutInfo info = new ShortcutInfo(); + // Non-app shortcuts are only supported for current user. + info.user = user; + info.itemType = itemType; + info.title = getTitle(); + info.iconBitmap = loadIcon(info); + // the fallback icon + if (info.iconBitmap == null) { + info.iconBitmap = mIconCache.getDefaultIcon(info.user); + } + + // TODO: If there's an explicit component and we can't install that, delete it. + + return info; + } + + /** + * Loads the icon from the cursor and updates the {@param info} if the icon is an app resource. + */ + protected Bitmap loadIcon(ShortcutInfo info) { + Bitmap icon = null; + if (itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) { + String packageName = getString(iconPackageIndex); + String resourceName = getString(iconResourceIndex); + if (!TextUtils.isEmpty(packageName) || !TextUtils.isEmpty(resourceName)) { + info.iconResource = new ShortcutIconResource(); + info.iconResource.packageName = packageName; + info.iconResource.resourceName = resourceName; + icon = LauncherIcons.createIconBitmap(info.iconResource, mContext); + } + } + if (icon == null) { + // Failed to load from resource, try loading from DB. + byte[] data = getBlob(iconIndex); + try { + icon = LauncherIcons.createIconBitmap( + BitmapFactory.decodeByteArray(data, 0, data.length), mContext); + } catch (Exception e) { + return null; + } + } + return icon; + } + + /** + * Returns the title or empty string + */ + private String getTitle() { + String title = getString(titleIndex); + return TextUtils.isEmpty(title) ? "" : Utilities.trim(title); + } + + + /** + * Make an ShortcutInfo object for a restored application or shortcut item that points + * to a package that is not yet installed on the system. + */ + public ShortcutInfo getRestoredItemInfo(Intent intent, int promiseType) { + final ShortcutInfo info = new ShortcutInfo(); + info.user = user; + info.promisedIntent = intent; + + info.iconBitmap = loadIcon(info); + // the fallback icon + if (info.iconBitmap == null) { + mIconCache.getTitleAndIcon(info, false /* useLowResIcon */); + } + + if ((promiseType & ShortcutInfo.FLAG_RESTORED_ICON) != 0) { + String title = getTitle(); + if (!TextUtils.isEmpty(title)) { + info.title = Utilities.trim(title); + } + } else if ((promiseType & ShortcutInfo.FLAG_AUTOINTALL_ICON) != 0) { + if (TextUtils.isEmpty(info.title)) { + info.title = getTitle(); + } + } else { + throw new InvalidParameterException("Invalid restoreType " + promiseType); + } + + info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user); + info.itemType = itemType; + info.status = promiseType; + return info; + } + + /** + * Make an ShortcutInfo object for a shortcut that is an application. + */ + public ShortcutInfo getAppShortcutInfo( + Intent intent, boolean allowMissingTarget, boolean useLowResIcon) { + if (user == null) { + Log.d(TAG, "Null user found in getShortcutInfo"); + return null; + } + + ComponentName componentName = intent.getComponent(); + if (componentName == null) { + Log.d(TAG, "Missing component found in getShortcutInfo"); + return null; + } + + Intent newIntent = new Intent(Intent.ACTION_MAIN, null); + newIntent.addCategory(Intent.CATEGORY_LAUNCHER); + newIntent.setComponent(componentName); + LauncherActivityInfoCompat lai = LauncherAppsCompat.getInstance(mContext) + .resolveActivity(newIntent, user); + if ((lai == null) && !allowMissingTarget) { + Log.d(TAG, "Missing activity found in getShortcutInfo: " + componentName); + return null; + } + + final ShortcutInfo info = new ShortcutInfo(); + info.itemType = LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; + info.user = user; + info.intent = newIntent; + + mIconCache.getTitleAndIcon(info, lai, useLowResIcon); + if (mIconCache.isDefaultIcon(info.iconBitmap, user)) { + Bitmap icon = loadIcon(info); + info.iconBitmap = icon != null ? icon : info.iconBitmap; + } + + if (lai != null && PackageManagerHelper.isAppSuspended(lai.getApplicationInfo())) { + info.isDisabled = ShortcutInfo.FLAG_DISABLED_SUSPENDED; + } + + // from the db + if (TextUtils.isEmpty(info.title)) { + info.title = getTitle(); + } + + // fall back to the class name of the activity + if (info.title == null) { + info.title = componentName.getClassName(); + } + + info.contentDescription = mUserManager.getBadgedLabelForUser(info.title, info.user); + return info; + } + + /** + * Returns a {@link ContentWriter} which can be used to update the current item. + */ + public ContentWriter updater() { + return new ContentWriter(mContext, new ContentWriter.CommitParams( + BaseColumns._ID + "= ?", new String[]{Long.toString(id)})); + } + + /** + * Marks the current item for removal + */ + public void markDeleted(String reason) { + FileLog.e(TAG, reason); + itemsToRemove.add(id); + } + + /** + * Removes any items marked for removal. + * @return true is any item was removed. + */ + public boolean commitDeleted() { + if (itemsToRemove.size() > 0) { + // Remove dead items + mContext.getContentResolver().delete(LauncherSettings.Favorites.CONTENT_URI, + Utilities.createDbSelectionQuery( + LauncherSettings.Favorites._ID, itemsToRemove), null); + return true; + } + return false; + } + + /** + * Marks the current item as restored + */ + public void markRestored() { + restoredRows.add(id); + } + + public void commitRestoredItems() { + if (restoredRows.size() > 0) { + // Update restored items that no longer require special handling + ContentValues values = new ContentValues(); + values.put(LauncherSettings.Favorites.RESTORED, 0); + mContext.getContentResolver().update(LauncherSettings.Favorites.CONTENT_URI, values, + Utilities.createDbSelectionQuery( + LauncherSettings.Favorites._ID, restoredRows), null); + } + } + + /** + * Returns true is the item is on workspace or hotseat + */ + public boolean isOnWorkspaceOrHotseat() { + return container == LauncherSettings.Favorites.CONTAINER_DESKTOP || + container == LauncherSettings.Favorites.CONTAINER_HOTSEAT; + } + + /** + * Applies the following properties: + * {@link ItemInfo#id} + * {@link ItemInfo#container} + * {@link ItemInfo#screenId} + * {@link ItemInfo#cellX} + * {@link ItemInfo#cellY} + */ + public void applyCommonProperties(ItemInfo info) { + info.id = id; + info.container = container; + info.screenId = getInt(screenIndex); + info.cellX = getInt(cellXIndex); + info.cellY = getInt(cellYIndex); + } + + /** + * Adds the {@param info} to {@param dataModel} if it does not overlap with any other item, + * otherwise marks it for deletion. + */ + public void checkAndAddItem(ItemInfo info, BgDataModel dataModel) { + if (checkItemPlacement(info, dataModel.workspaceScreens)) { + dataModel.addItem(info, false); + } else { + markDeleted("Item position overlap"); + } + } + + /** + * check & update map of what's occupied; used to discard overlapping/invalid items + */ + protected boolean checkItemPlacement(ItemInfo item, ArrayList<Long> workspaceScreens) { + long containerIndex = item.screenId; + if (item.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT) { + // Return early if we detect that an item is under the hotseat button + if (!FeatureFlags.NO_ALL_APPS_ICON && + mIDP.isAllAppsButtonRank((int) item.screenId)) { + Log.e(TAG, "Error loading shortcut into hotseat " + item + + " into position (" + item.screenId + ":" + item.cellX + "," + + item.cellY + ") occupied by all apps"); + return false; + } + + final GridOccupancy hotseatOccupancy = + occupied.get((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT); + + if (item.screenId >= mIDP.numHotseatIcons) { + Log.e(TAG, "Error loading shortcut " + item + + " into hotseat position " + item.screenId + + ", position out of bounds: (0 to " + (mIDP.numHotseatIcons - 1) + + ")"); + return false; + } + + if (hotseatOccupancy != null) { + if (hotseatOccupancy.cells[(int) item.screenId][0]) { + Log.e(TAG, "Error loading shortcut into hotseat " + item + + " into position (" + item.screenId + ":" + item.cellX + "," + + item.cellY + ") already occupied"); + return false; + } else { + hotseatOccupancy.cells[(int) item.screenId][0] = true; + return true; + } + } else { + final GridOccupancy occupancy = new GridOccupancy(mIDP.numHotseatIcons, 1); + occupancy.cells[(int) item.screenId][0] = true; + occupied.put((long) LauncherSettings.Favorites.CONTAINER_HOTSEAT, occupancy); + return true; + } + } else if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) { + if (!workspaceScreens.contains((Long) item.screenId)) { + // The item has an invalid screen id. + return false; + } + } else { + // Skip further checking if it is not the hotseat or workspace container + return true; + } + + final int countX = mIDP.numColumns; + final int countY = mIDP.numRows; + if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP && + item.cellX < 0 || item.cellY < 0 || + item.cellX + item.spanX > countX || item.cellY + item.spanY > countY) { + Log.e(TAG, "Error loading shortcut " + item + + " into cell (" + containerIndex + "-" + item.screenId + ":" + + item.cellX + "," + item.cellY + + ") out of screen bounds ( " + countX + "x" + countY + ")"); + return false; + } + + if (!occupied.containsKey(item.screenId)) { + GridOccupancy screen = new GridOccupancy(countX + 1, countY + 1); + if (item.screenId == Workspace.FIRST_SCREEN_ID) { + // Mark the first row as occupied (if the feature is enabled) + // in order to account for the QSB. + screen.markCells(0, 0, countX + 1, 1, FeatureFlags.QSB_ON_FIRST_SCREEN); + } + occupied.put(item.screenId, screen); + } + final GridOccupancy occupancy = occupied.get(item.screenId); + + // Check if any workspace icons overlap with each other + if (occupancy.isRegionVacant(item.cellX, item.cellY, item.spanX, item.spanY)) { + occupancy.markCells(item, true); + return true; + } else { + Log.e(TAG, "Error loading shortcut " + item + + " into cell (" + containerIndex + "-" + item.screenId + ":" + + item.cellX + "," + item.cellX + "," + item.spanX + "," + item.spanY + + ") already occupied"); + return false; + } + } +} diff --git a/src/com/android/launcher3/util/ContentWriter.java b/src/com/android/launcher3/util/ContentWriter.java index 1c347c0a4..76ba9d63a 100644 --- a/src/com/android/launcher3/util/ContentWriter.java +++ b/src/com/android/launcher3/util/ContentWriter.java @@ -20,6 +20,7 @@ import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.graphics.Bitmap; +import android.net.Uri; import android.os.UserHandle; import com.android.launcher3.LauncherAppState; @@ -35,9 +36,15 @@ public class ContentWriter { private final ContentValues mValues; private final Context mContext; + private CommitParams mCommitParams; private Bitmap mIcon; private UserHandle mUser; + public ContentWriter(Context context, CommitParams commitParams) { + this(context); + mCommitParams = commitParams; + } + public ContentWriter(Context context) { this(new ContentValues(), context); } @@ -95,4 +102,25 @@ public class ContentWriter { } return mValues; } + + public int commit() { + if (mCommitParams != null) { + return mContext.getContentResolver().update(mCommitParams.mUri, getValues(), + mCommitParams.mWhere, mCommitParams.mSelectionArgs); + } + return 0; + } + + public static final class CommitParams { + + final Uri mUri = LauncherSettings.Favorites.CONTENT_URI; + String mWhere; + String[] mSelectionArgs; + + public CommitParams(String where, String[] selectionArgs) { + mWhere = where; + mSelectionArgs = selectionArgs; + } + + } } diff --git a/src/com/android/launcher3/util/CursorIconInfo.java b/src/com/android/launcher3/util/CursorIconInfo.java deleted file mode 100644 index 3bc4eabc5..000000000 --- a/src/com/android/launcher3/util/CursorIconInfo.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2015 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.launcher3.util; - -import android.content.Context; -import android.content.Intent.ShortcutIconResource; -import android.database.Cursor; -import android.graphics.Bitmap; -import android.text.TextUtils; - -import com.android.launcher3.LauncherSettings; -import com.android.launcher3.ShortcutInfo; -import com.android.launcher3.Utilities; -import com.android.launcher3.graphics.LauncherIcons; - -/** - * Utility class to load icon from a cursor. - */ -public class CursorIconInfo { - public final int iconPackageIndex; - public final int iconResourceIndex; - public final int iconIndex; - - public final int titleIndex; - - private final Context mContext; - - public CursorIconInfo(Context context, Cursor c) { - mContext = context; - - iconIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON); - iconPackageIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_PACKAGE); - iconResourceIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.ICON_RESOURCE); - - titleIndex = c.getColumnIndexOrThrow(LauncherSettings.Favorites.TITLE); - } - - /** - * Loads the icon from the cursor and updates the {@param info} if the icon is an app resource. - */ - public Bitmap loadIcon(Cursor c, ShortcutInfo info) { - Bitmap icon = null; - String packageName = c.getString(iconPackageIndex); - String resourceName = c.getString(iconResourceIndex); - if (!TextUtils.isEmpty(packageName) || !TextUtils.isEmpty(resourceName)) { - info.iconResource = new ShortcutIconResource(); - info.iconResource.packageName = packageName; - info.iconResource.resourceName = resourceName; - icon = LauncherIcons.createIconBitmap(info.iconResource, mContext); - } - if (icon == null) { - // Failed to load from resource, try loading from DB. - icon = loadIcon(c); - } - return icon; - } - - /** - * Loads the fixed bitmap from the icon if available. - */ - public Bitmap loadIcon(Cursor c) { - return LauncherIcons.createIconBitmap(c, iconIndex, mContext); - } - - /** - * Returns the title or empty string - */ - public String getTitle(Cursor c) { - String title = c.getString(titleIndex); - return TextUtils.isEmpty(title) ? "" : Utilities.trim(c.getString(titleIndex)); - } -} diff --git a/src/com/android/launcher3/util/PackageManagerHelper.java b/src/com/android/launcher3/util/PackageManagerHelper.java index b61d6095f..33a9fc68d 100644 --- a/src/com/android/launcher3/util/PackageManagerHelper.java +++ b/src/com/android/launcher3/util/PackageManagerHelper.java @@ -23,6 +23,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; +import android.net.Uri; import android.os.Build; import android.text.TextUtils; @@ -124,4 +125,13 @@ public class PackageManagerHelper { return false; } + + public static Intent getMarketIntent(String packageName) { + return new Intent(Intent.ACTION_VIEW) + .setData(new Uri.Builder() + .scheme("market") + .authority("details") + .appendQueryParameter("id", packageName) + .build()); + } } diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java new file mode 100644 index 000000000..d40e6c2fc --- /dev/null +++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java @@ -0,0 +1,234 @@ +package com.android.launcher3.model; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.database.MatrixCursor; +import android.graphics.Bitmap; +import android.os.Process; +import android.support.test.InstrumentationRegistry; +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; + +import com.android.launcher3.IconCache; +import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.ItemInfo; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.ShortcutInfo; +import com.android.launcher3.Utilities; +import com.android.launcher3.compat.LauncherAppsCompat; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.ArrayList; +import java.util.Arrays; + +import static com.android.launcher3.LauncherSettings.Favorites.CELLX; +import static com.android.launcher3.LauncherSettings.Favorites.CELLY; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP; +import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT; +import static com.android.launcher3.LauncherSettings.Favorites.ICON; +import static com.android.launcher3.LauncherSettings.Favorites.ICON_PACKAGE; +import static com.android.launcher3.LauncherSettings.Favorites.ICON_RESOURCE; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION; +import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT; +import static com.android.launcher3.LauncherSettings.Favorites.PROFILE_ID; +import static com.android.launcher3.LauncherSettings.Favorites.SCREEN; +import static com.android.launcher3.LauncherSettings.Favorites.TITLE; +import static com.android.launcher3.LauncherSettings.Favorites._ID; +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertNull; +import static junit.framework.Assert.assertTrue; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Tests for {@link LoaderCursor} + */ +@SmallTest +@RunWith(AndroidJUnit4.class) +public class LoaderCursorTest { + + private LauncherAppState mMockApp; + private IconCache mMockIconCache; + + private MatrixCursor mCursor; + private InvariantDeviceProfile mIDP; + private Context mContext; + private LauncherAppsCompat mLauncherApps; + + private LoaderCursor mLoaderCursor; + + @Before + public void setup() { + mIDP = new InvariantDeviceProfile(); + mCursor = new MatrixCursor(new String[] { + ICON, ICON_PACKAGE, ICON_RESOURCE, TITLE, + _ID, CONTAINER, ITEM_TYPE, PROFILE_ID, + SCREEN, CELLX, CELLY, + }); + mContext = InstrumentationRegistry.getTargetContext(); + + mMockApp = mock(LauncherAppState.class); + mMockIconCache = mock(IconCache.class); + when(mMockApp.getIconCache()).thenReturn(mMockIconCache); + when(mMockApp.getInvariantDeviceProfile()).thenReturn(mIDP); + when(mMockApp.getContext()).thenReturn(mContext); + mLauncherApps = LauncherAppsCompat.getInstance(mContext); + + mLoaderCursor = new LoaderCursor(mCursor, mMockApp); + mLoaderCursor.allUsers.put(0, Process.myUserHandle()); + } + + private void initCursor(int itemType, String title) { + mCursor.newRow() + .add(_ID, 1) + .add(PROFILE_ID, 0) + .add(ITEM_TYPE, itemType) + .add(TITLE, title) + .add(CONTAINER, CONTAINER_DESKTOP); + } + + @Test + public void getAppShortcutInfo_dontAllowMissing_invalidComponent() { + initCursor(ITEM_TYPE_APPLICATION, ""); + assertTrue(mLoaderCursor.moveToNext()); + ComponentName cn = new ComponentName(mContext.getPackageName(), "dummy-do"); + assertNull(mLoaderCursor.getAppShortcutInfo( + new Intent().setComponent(cn), false /* allowMissingTarget */, true)); + } + + @Test + public void getAppShortcutInfo_dontAllowMissing_validComponent() { + initCursor(ITEM_TYPE_APPLICATION, ""); + assertTrue(mLoaderCursor.moveToNext()); + + ComponentName cn = mLauncherApps.getActivityList(null, mLoaderCursor.user) + .get(0).getComponentName(); + ShortcutInfo info = mLoaderCursor.getAppShortcutInfo( + new Intent().setComponent(cn), false /* allowMissingTarget */, true); + assertNotNull(info); + assertTrue(Utilities.isLauncherAppTarget(info.intent)); + } + + @Test + public void getAppShortcutInfo_allowMissing_invalidComponent() { + initCursor(ITEM_TYPE_APPLICATION, ""); + assertTrue(mLoaderCursor.moveToNext()); + + ComponentName cn = new ComponentName(mContext.getPackageName(), "dummy-do"); + ShortcutInfo info = mLoaderCursor.getAppShortcutInfo( + new Intent().setComponent(cn), true /* allowMissingTarget */, true); + assertNotNull(info); + assertTrue(Utilities.isLauncherAppTarget(info.intent)); + } + + @Test + public void loadSimpleShortcut() { + initCursor(ITEM_TYPE_SHORTCUT, "my-shortcut"); + assertTrue(mLoaderCursor.moveToNext()); + + Bitmap icon = Bitmap.createBitmap(1, 1, Bitmap.Config.ALPHA_8); + when(mMockIconCache.getDefaultIcon(eq(mLoaderCursor.user))).thenReturn(icon); + ShortcutInfo info = mLoaderCursor.loadSimpleShortcut(); + assertEquals(icon, info.iconBitmap); + assertEquals("my-shortcut", info.title); + assertEquals(ITEM_TYPE_SHORTCUT, info.itemType); + } + + @Test + public void checkItemPlacement_wrongWorkspaceScreen() { + ArrayList<Long> workspaceScreens = new ArrayList<>(Arrays.asList(1L, 3L)); + mIDP.numRows = 4; + mIDP.numColumns = 4; + mIDP.numHotseatIcons = 3; + + // Item on unknown screen are not placed + assertFalse(mLoaderCursor.checkItemPlacement( + newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 4L), workspaceScreens)); + assertFalse(mLoaderCursor.checkItemPlacement( + newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 5L), workspaceScreens)); + assertFalse(mLoaderCursor.checkItemPlacement( + newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2L), workspaceScreens)); + + assertTrue(mLoaderCursor.checkItemPlacement( + newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens)); + assertTrue(mLoaderCursor.checkItemPlacement( + newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 3L), workspaceScreens)); + + } + @Test + public void checkItemPlacement_outsideBounds() { + ArrayList<Long> workspaceScreens = new ArrayList<>(Arrays.asList(1L, 2L)); + mIDP.numRows = 4; + mIDP.numColumns = 4; + mIDP.numHotseatIcons = 3; + + // Item outside screen bounds are not placed + assertFalse(mLoaderCursor.checkItemPlacement( + newItemInfo(4, 4, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens)); + } + + @Test + public void checkItemPlacement_overlappingItems() { + ArrayList<Long> workspaceScreens = new ArrayList<>(Arrays.asList(1L, 2L)); + mIDP.numRows = 4; + mIDP.numColumns = 4; + mIDP.numHotseatIcons = 3; + + // Overlapping items are not placed + assertTrue(mLoaderCursor.checkItemPlacement( + newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens)); + assertFalse(mLoaderCursor.checkItemPlacement( + newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens)); + + assertTrue(mLoaderCursor.checkItemPlacement( + newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2L), workspaceScreens)); + assertFalse(mLoaderCursor.checkItemPlacement( + newItemInfo(0, 0, 1, 1, CONTAINER_DESKTOP, 2L), workspaceScreens)); + + assertTrue(mLoaderCursor.checkItemPlacement( + newItemInfo(1, 1, 1, 1, CONTAINER_DESKTOP, 1L), workspaceScreens)); + assertTrue(mLoaderCursor.checkItemPlacement( + newItemInfo(2, 2, 2, 2, CONTAINER_DESKTOP, 1L), workspaceScreens)); + + assertFalse(mLoaderCursor.checkItemPlacement( + newItemInfo(3, 2, 1, 2, CONTAINER_DESKTOP, 1L), workspaceScreens)); + } + + @Test + public void checkItemPlacement_hotseat() { + ArrayList<Long> workspaceScreens = new ArrayList<>(); + mIDP.numRows = 4; + mIDP.numColumns = 4; + mIDP.numHotseatIcons = 3; + + // Hotseat items are only placed based on screenId + assertTrue(mLoaderCursor.checkItemPlacement( + newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 1L), workspaceScreens)); + assertTrue(mLoaderCursor.checkItemPlacement( + newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 2L), workspaceScreens)); + + assertFalse(mLoaderCursor.checkItemPlacement( + newItemInfo(3, 3, 1, 1, CONTAINER_HOTSEAT, 3L), workspaceScreens)); + } + + private ItemInfo newItemInfo(int cellX, int cellY, int spanX, int spanY, + long container, long screenId) { + ItemInfo info = new ItemInfo(); + info.cellX = cellX; + info.cellY = cellY; + info.spanX = spanX; + info.spanY = spanY; + info.container = container; + info.screenId = screenId; + return info; + } +} |