summaryrefslogtreecommitdiffstats
path: root/src/com/android/launcher3/model
diff options
context:
space:
mode:
authorSunny Goyal <sunnygoyal@google.com>2017-01-05 21:50:27 -0800
committerSunny Goyal <sunnygoyal@google.com>2017-01-10 13:53:50 -0800
commitaaf86fe9a0406720cca7cfe204f28e2d2de085eb (patch)
treecc3c034548692ac963545d9eb11dc5742099ff74 /src/com/android/launcher3/model
parente09bacc1ef50aa7c4fe551a7360d7f5613927ee9 (diff)
downloadandroid_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
Diffstat (limited to 'src/com/android/launcher3/model')
-rw-r--r--src/com/android/launcher3/model/LoaderCursor.java445
1 files changed, 445 insertions, 0 deletions
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;
+ }
+ }
+}