Refactoring some loadWorkspace logic in a separate class
Bug: 34112546 Change-Id: I8a43ed1646056aa1957ac3d6ea82018691df6386
This commit is contained in:
@@ -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},
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
final LoaderCursor c = new LoaderCursor(contentResolver.query(
|
||||
LauncherSettings.Favorites.CONTENT_URI, null, null, null, null), mApp);
|
||||
|
||||
// +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<>();
|
||||
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);
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user