2016-09-09 15:47:55 -07:00
|
|
|
/*
|
|
|
|
|
* Copyright (C) 2016 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.Context;
|
|
|
|
|
import android.content.Intent;
|
2016-12-15 15:53:17 -08:00
|
|
|
import android.os.UserHandle;
|
2016-09-09 15:47:55 -07:00
|
|
|
import android.util.LongSparseArray;
|
|
|
|
|
import android.util.Pair;
|
|
|
|
|
|
|
|
|
|
import com.android.launcher3.AllAppsList;
|
|
|
|
|
import com.android.launcher3.AppInfo;
|
|
|
|
|
import com.android.launcher3.FolderInfo;
|
|
|
|
|
import com.android.launcher3.InvariantDeviceProfile;
|
|
|
|
|
import com.android.launcher3.ItemInfo;
|
|
|
|
|
import com.android.launcher3.LauncherAppState;
|
2016-12-08 09:59:25 -08:00
|
|
|
import com.android.launcher3.LauncherAppWidgetInfo;
|
2016-09-09 15:47:55 -07:00
|
|
|
import com.android.launcher3.LauncherModel;
|
|
|
|
|
import com.android.launcher3.LauncherModel.CallbackTask;
|
|
|
|
|
import com.android.launcher3.LauncherModel.Callbacks;
|
|
|
|
|
import com.android.launcher3.LauncherSettings;
|
|
|
|
|
import com.android.launcher3.ShortcutInfo;
|
|
|
|
|
import com.android.launcher3.util.GridOccupancy;
|
2016-11-16 09:23:42 -08:00
|
|
|
import com.android.launcher3.util.Provider;
|
2016-09-09 15:47:55 -07:00
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
2016-11-16 09:23:42 -08:00
|
|
|
import java.util.List;
|
2016-09-09 15:47:55 -07:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Task to add auto-created workspace items.
|
|
|
|
|
*/
|
|
|
|
|
public class AddWorkspaceItemsTask extends ExtendedModelTask {
|
|
|
|
|
|
2016-11-16 09:23:42 -08:00
|
|
|
private final Provider<List<ItemInfo>> mAppsProvider;
|
2016-09-09 15:47:55 -07:00
|
|
|
|
|
|
|
|
/**
|
2016-11-16 09:23:42 -08:00
|
|
|
* @param appsProvider items to add on the workspace
|
2016-09-09 15:47:55 -07:00
|
|
|
*/
|
2016-11-16 09:23:42 -08:00
|
|
|
public AddWorkspaceItemsTask(Provider<List<ItemInfo>> appsProvider) {
|
|
|
|
|
mAppsProvider = appsProvider;
|
2016-09-09 15:47:55 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
|
2016-11-16 09:23:42 -08:00
|
|
|
List<ItemInfo> workspaceApps = mAppsProvider.get();
|
|
|
|
|
if (workspaceApps.isEmpty()) {
|
2016-09-09 15:47:55 -07:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
Context context = app.getContext();
|
|
|
|
|
|
2016-12-08 09:59:25 -08:00
|
|
|
final ArrayList<ItemInfo> addedItemsFinal = new ArrayList<>();
|
|
|
|
|
final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<>();
|
2016-09-09 15:47:55 -07:00
|
|
|
|
|
|
|
|
// Get the list of workspace screens. We need to append to this list and
|
|
|
|
|
// can not use sBgWorkspaceScreens because loadWorkspace() may not have been
|
|
|
|
|
// called.
|
|
|
|
|
ArrayList<Long> workspaceScreens = LauncherModel.loadWorkspaceScreensDb(context);
|
|
|
|
|
synchronized(dataModel) {
|
2016-11-16 09:23:42 -08:00
|
|
|
for (ItemInfo item : workspaceApps) {
|
2017-01-13 12:15:53 -08:00
|
|
|
if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
|
|
|
|
|
item.itemType == LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT) {
|
2016-09-09 15:47:55 -07:00
|
|
|
// Short-circuit this logic if the icon exists somewhere on the workspace
|
|
|
|
|
if (shortcutExists(dataModel, item.getIntent(), item.user)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find appropriate space for the item.
|
2016-12-08 09:59:25 -08:00
|
|
|
Pair<Long, int[]> coords = findSpaceForItem(app, dataModel, workspaceScreens,
|
|
|
|
|
addedWorkspaceScreensFinal, item.spanX, item.spanY);
|
2016-09-09 15:47:55 -07:00
|
|
|
long screenId = coords.first;
|
|
|
|
|
int[] cordinates = coords.second;
|
|
|
|
|
|
|
|
|
|
ItemInfo itemInfo;
|
2016-12-08 09:59:25 -08:00
|
|
|
if (item instanceof ShortcutInfo || item instanceof FolderInfo ||
|
|
|
|
|
item instanceof LauncherAppWidgetInfo) {
|
2016-09-09 15:47:55 -07:00
|
|
|
itemInfo = item;
|
|
|
|
|
} else if (item instanceof AppInfo) {
|
|
|
|
|
itemInfo = ((AppInfo) item).makeShortcut();
|
|
|
|
|
} else {
|
|
|
|
|
throw new RuntimeException("Unexpected info type");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Add the shortcut to the db
|
|
|
|
|
addItemToDatabase(context, itemInfo, screenId, cordinates);
|
|
|
|
|
|
|
|
|
|
// Save the ShortcutInfo for binding in the workspace
|
2016-12-08 09:59:25 -08:00
|
|
|
addedItemsFinal.add(itemInfo);
|
2016-09-09 15:47:55 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update the workspace screens
|
|
|
|
|
updateScreens(context, workspaceScreens);
|
|
|
|
|
|
2016-12-08 09:59:25 -08:00
|
|
|
if (!addedItemsFinal.isEmpty()) {
|
2016-09-09 15:47:55 -07:00
|
|
|
scheduleCallbackTask(new CallbackTask() {
|
|
|
|
|
@Override
|
|
|
|
|
public void execute(Callbacks callbacks) {
|
|
|
|
|
final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
|
|
|
|
|
final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
|
2016-12-08 09:59:25 -08:00
|
|
|
if (!addedItemsFinal.isEmpty()) {
|
|
|
|
|
ItemInfo info = addedItemsFinal.get(addedItemsFinal.size() - 1);
|
2016-09-09 15:47:55 -07:00
|
|
|
long lastScreenId = info.screenId;
|
2016-12-08 09:59:25 -08:00
|
|
|
for (ItemInfo i : addedItemsFinal) {
|
2016-09-09 15:47:55 -07:00
|
|
|
if (i.screenId == lastScreenId) {
|
|
|
|
|
addAnimated.add(i);
|
|
|
|
|
} else {
|
|
|
|
|
addNotAnimated.add(i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
callbacks.bindAppsAdded(addedWorkspaceScreensFinal,
|
|
|
|
|
addNotAnimated, addAnimated, null);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected void addItemToDatabase(Context context, ItemInfo item, long screenId, int[] pos) {
|
|
|
|
|
LauncherModel.addItemToDatabase(context, item,
|
|
|
|
|
LauncherSettings.Favorites.CONTAINER_DESKTOP, screenId, pos[0], pos[1]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected void updateScreens(Context context, ArrayList<Long> workspaceScreens) {
|
|
|
|
|
LauncherModel.updateWorkspaceScreenOrder(context, workspaceScreens);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Returns true if the shortcuts already exists on the workspace. This must be called after
|
|
|
|
|
* the workspace has been loaded. We identify a shortcut by its intent.
|
|
|
|
|
*/
|
2016-12-15 15:53:17 -08:00
|
|
|
protected boolean shortcutExists(BgDataModel dataModel, Intent intent, UserHandle user) {
|
2016-09-09 15:47:55 -07:00
|
|
|
final String intentWithPkg, intentWithoutPkg;
|
2017-01-13 12:15:53 -08:00
|
|
|
if (intent == null) {
|
|
|
|
|
// Skip items with null intents
|
|
|
|
|
return true;
|
|
|
|
|
}
|
2016-09-09 15:47:55 -07:00
|
|
|
if (intent.getComponent() != null) {
|
|
|
|
|
// If component is not null, an intent with null package will produce
|
|
|
|
|
// the same result and should also be a match.
|
|
|
|
|
String packageName = intent.getComponent().getPackageName();
|
|
|
|
|
if (intent.getPackage() != null) {
|
|
|
|
|
intentWithPkg = intent.toUri(0);
|
|
|
|
|
intentWithoutPkg = new Intent(intent).setPackage(null).toUri(0);
|
|
|
|
|
} else {
|
|
|
|
|
intentWithPkg = new Intent(intent).setPackage(packageName).toUri(0);
|
|
|
|
|
intentWithoutPkg = intent.toUri(0);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
intentWithPkg = intent.toUri(0);
|
|
|
|
|
intentWithoutPkg = intent.toUri(0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
synchronized (dataModel) {
|
|
|
|
|
for (ItemInfo item : dataModel.itemsIdMap) {
|
|
|
|
|
if (item instanceof ShortcutInfo) {
|
|
|
|
|
ShortcutInfo info = (ShortcutInfo) item;
|
2017-01-12 16:55:36 -08:00
|
|
|
if (item.getIntent() != null && info.user.equals(user)) {
|
|
|
|
|
Intent copyIntent = new Intent(item.getIntent());
|
2016-09-09 15:47:55 -07:00
|
|
|
copyIntent.setSourceBounds(intent.getSourceBounds());
|
|
|
|
|
String s = copyIntent.toUri(0);
|
|
|
|
|
if (intentWithPkg.equals(s) || intentWithoutPkg.equals(s)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Find a position on the screen for the given size or adds a new screen.
|
|
|
|
|
* @return screenId and the coordinates for the item.
|
|
|
|
|
*/
|
|
|
|
|
protected Pair<Long, int[]> findSpaceForItem(
|
|
|
|
|
LauncherAppState app, BgDataModel dataModel,
|
|
|
|
|
ArrayList<Long> workspaceScreens,
|
|
|
|
|
ArrayList<Long> addedWorkspaceScreensFinal,
|
|
|
|
|
int spanX, int spanY) {
|
|
|
|
|
LongSparseArray<ArrayList<ItemInfo>> screenItems = new LongSparseArray<>();
|
|
|
|
|
|
|
|
|
|
// Use sBgItemsIdMap as all the items are already loaded.
|
|
|
|
|
synchronized (dataModel) {
|
|
|
|
|
for (ItemInfo info : dataModel.itemsIdMap) {
|
|
|
|
|
if (info.container == LauncherSettings.Favorites.CONTAINER_DESKTOP) {
|
|
|
|
|
ArrayList<ItemInfo> items = screenItems.get(info.screenId);
|
|
|
|
|
if (items == null) {
|
|
|
|
|
items = new ArrayList<>();
|
|
|
|
|
screenItems.put(info.screenId, items);
|
|
|
|
|
}
|
|
|
|
|
items.add(info);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Find appropriate space for the item.
|
|
|
|
|
long screenId = 0;
|
|
|
|
|
int[] cordinates = new int[2];
|
|
|
|
|
boolean found = false;
|
|
|
|
|
|
|
|
|
|
int screenCount = workspaceScreens.size();
|
|
|
|
|
// First check the preferred screen.
|
|
|
|
|
int preferredScreenIndex = workspaceScreens.isEmpty() ? 0 : 1;
|
|
|
|
|
if (preferredScreenIndex < screenCount) {
|
|
|
|
|
screenId = workspaceScreens.get(preferredScreenIndex);
|
|
|
|
|
found = findNextAvailableIconSpaceInScreen(
|
|
|
|
|
app, screenItems.get(screenId), cordinates, spanX, spanY);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!found) {
|
|
|
|
|
// Search on any of the screens starting from the first screen.
|
|
|
|
|
for (int screen = 1; screen < screenCount; screen++) {
|
|
|
|
|
screenId = workspaceScreens.get(screen);
|
|
|
|
|
if (findNextAvailableIconSpaceInScreen(
|
|
|
|
|
app, screenItems.get(screenId), cordinates, spanX, spanY)) {
|
|
|
|
|
// We found a space for it
|
|
|
|
|
found = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!found) {
|
|
|
|
|
// Still no position found. Add a new screen to the end.
|
|
|
|
|
screenId = LauncherSettings.Settings.call(app.getContext().getContentResolver(),
|
|
|
|
|
LauncherSettings.Settings.METHOD_NEW_SCREEN_ID)
|
|
|
|
|
.getLong(LauncherSettings.Settings.EXTRA_VALUE);
|
|
|
|
|
|
|
|
|
|
// Save the screen id for binding in the workspace
|
|
|
|
|
workspaceScreens.add(screenId);
|
|
|
|
|
addedWorkspaceScreensFinal.add(screenId);
|
|
|
|
|
|
|
|
|
|
// If we still can't find an empty space, then God help us all!!!
|
|
|
|
|
if (!findNextAvailableIconSpaceInScreen(
|
|
|
|
|
app, screenItems.get(screenId), cordinates, spanX, spanY)) {
|
|
|
|
|
throw new RuntimeException("Can't find space to add the item");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return Pair.create(screenId, cordinates);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private boolean findNextAvailableIconSpaceInScreen(
|
|
|
|
|
LauncherAppState app, ArrayList<ItemInfo> occupiedPos,
|
|
|
|
|
int[] xy, int spanX, int spanY) {
|
|
|
|
|
InvariantDeviceProfile profile = app.getInvariantDeviceProfile();
|
|
|
|
|
|
|
|
|
|
GridOccupancy occupied = new GridOccupancy(profile.numColumns, profile.numRows);
|
|
|
|
|
if (occupiedPos != null) {
|
|
|
|
|
for (ItemInfo r : occupiedPos) {
|
|
|
|
|
occupied.markCells(r, true);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return occupied.findVacantCell(xy, spanX, spanY);
|
|
|
|
|
}
|
|
|
|
|
}
|