Moving various runnables in LauncherModel to individual tasks
> Adding tests for some of the runnable Change-Id: I1a315d38878857df3371f0e69d622a41fc3b081a
This commit is contained in:
parent
0d547bfd57
commit
f0ba8b7ca1
|
@ -45,6 +45,7 @@ android {
|
|||
|
||||
androidTest {
|
||||
java.srcDirs = ['tests/src']
|
||||
res.srcDirs = ['tests/res']
|
||||
manifest.srcFile "tests/AndroidManifest.xml"
|
||||
}
|
||||
|
||||
|
@ -65,6 +66,9 @@ dependencies {
|
|||
compile 'com.google.protobuf.nano:protobuf-javanano:3.0.0-alpha-2'
|
||||
|
||||
testCompile 'junit:junit:4.12'
|
||||
androidTestCompile "org.mockito:mockito-core:1.+"
|
||||
androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
|
||||
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
|
||||
androidTestCompile 'com.android.support.test:runner:0.5'
|
||||
androidTestCompile 'com.android.support.test.uiautomator:uiautomator-v18:2.1.2'
|
||||
androidTestCompile 'com.android.support:support-annotations:23.2.0'
|
||||
|
|
|
@ -155,10 +155,9 @@ public class AllAppsList {
|
|||
// to the removed list.
|
||||
for (int i = data.size() - 1; i >= 0; i--) {
|
||||
final AppInfo applicationInfo = data.get(i);
|
||||
final ComponentName component = applicationInfo.intent.getComponent();
|
||||
if (user.equals(applicationInfo.user)
|
||||
&& packageName.equals(component.getPackageName())) {
|
||||
if (!findActivity(matches, component)) {
|
||||
&& packageName.equals(applicationInfo.componentName.getPackageName())) {
|
||||
if (!findActivity(matches, applicationInfo.componentName)) {
|
||||
removed.add(applicationInfo);
|
||||
data.remove(i);
|
||||
}
|
||||
|
@ -182,11 +181,10 @@ public class AllAppsList {
|
|||
// Remove all data for this package.
|
||||
for (int i = data.size() - 1; i >= 0; i--) {
|
||||
final AppInfo applicationInfo = data.get(i);
|
||||
final ComponentName component = applicationInfo.intent.getComponent();
|
||||
if (user.equals(applicationInfo.user)
|
||||
&& packageName.equals(component.getPackageName())) {
|
||||
&& packageName.equals(applicationInfo.componentName.getPackageName())) {
|
||||
removed.add(applicationInfo);
|
||||
mIconCache.remove(component, user);
|
||||
mIconCache.remove(applicationInfo.componentName, user);
|
||||
data.remove(i);
|
||||
}
|
||||
}
|
||||
|
@ -238,9 +236,8 @@ public class AllAppsList {
|
|||
private AppInfo findApplicationInfoLocked(String packageName, UserHandleCompat user,
|
||||
String className) {
|
||||
for (AppInfo info: data) {
|
||||
final ComponentName component = info.intent.getComponent();
|
||||
if (user.equals(info.user) && packageName.equals(component.getPackageName())
|
||||
&& className.equals(component.getClassName())) {
|
||||
if (user.equals(info.user) && packageName.equals(info.componentName.getPackageName())
|
||||
&& className.equals(info.componentName.getClassName())) {
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@ public class IconCache {
|
|||
|
||||
@Thunk static final Object ICON_UPDATE_TOKEN = new Object();
|
||||
|
||||
@Thunk static class CacheEntry {
|
||||
public static class CacheEntry {
|
||||
public Bitmap icon;
|
||||
public CharSequence title = "";
|
||||
public CharSequence contentDescription = "";
|
||||
|
@ -544,7 +544,7 @@ public class IconCache {
|
|||
* Retrieves the entry from the cache. If the entry is not present, it creates a new entry.
|
||||
* This method is not thread safe, it must be called from a synchronized method.
|
||||
*/
|
||||
private CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info,
|
||||
protected CacheEntry cacheLocked(ComponentName componentName, LauncherActivityInfoCompat info,
|
||||
UserHandleCompat user, boolean usePackageIcon, boolean useLowResIcon) {
|
||||
ComponentKey cacheKey = new ComponentKey(componentName, user);
|
||||
CacheEntry entry = mCache.get(cacheKey);
|
||||
|
|
|
@ -241,7 +241,7 @@ public class InstallShortcutReceiver extends BroadcastReceiver {
|
|||
// Add the new apps to the model and bind them
|
||||
if (!addShortcuts.isEmpty()) {
|
||||
LauncherAppState app = LauncherAppState.getInstance();
|
||||
app.getModel().addAndBindAddedWorkspaceItems(context, addShortcuts);
|
||||
app.getModel().addAndBindAddedWorkspaceItems(addShortcuts);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,12 +84,12 @@ public class LauncherAppWidgetInfo extends ItemInfo {
|
|||
/**
|
||||
* Indicates the restore status of the widget.
|
||||
*/
|
||||
int restoreStatus;
|
||||
public int restoreStatus;
|
||||
|
||||
/**
|
||||
* Indicates the installation progress of the widget provider
|
||||
*/
|
||||
int installProgress = -1;
|
||||
public int installProgress = -1;
|
||||
|
||||
/**
|
||||
* Optional extras sent during widget bind. See {@link #FLAG_DIRECT_CONFIG}.
|
||||
|
@ -98,7 +98,7 @@ public class LauncherAppWidgetInfo extends ItemInfo {
|
|||
|
||||
private boolean mHasNotifiedInitialWidgetSizeChanged;
|
||||
|
||||
LauncherAppWidgetInfo(int appWidgetId, ComponentName providerName) {
|
||||
public LauncherAppWidgetInfo(int appWidgetId, ComponentName providerName) {
|
||||
if (appWidgetId == CUSTOM_WIDGET_ID) {
|
||||
itemType = LauncherSettings.Favorites.ITEM_TYPE_CUSTOM_APPWIDGET;
|
||||
} else {
|
||||
|
@ -117,6 +117,11 @@ public class LauncherAppWidgetInfo extends ItemInfo {
|
|||
restoreStatus = RESTORE_COMPLETED;
|
||||
}
|
||||
|
||||
/** Used for testing **/
|
||||
public LauncherAppWidgetInfo() {
|
||||
itemType = LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
|
||||
}
|
||||
|
||||
public boolean isCustomWidget() {
|
||||
return appWidgetId == CUSTOM_WIDGET_ID;
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -80,7 +80,7 @@ public class ShortcutInfo extends ItemInfo {
|
|||
* Indicates whether we're using the default fallback icon instead of something from the
|
||||
* app.
|
||||
*/
|
||||
boolean usingFallbackIcon;
|
||||
public boolean usingFallbackIcon;
|
||||
|
||||
/**
|
||||
* Indicates whether we're using a low res icon
|
||||
|
@ -132,7 +132,7 @@ public class ShortcutInfo extends ItemInfo {
|
|||
* Could be disabled, if the the app is installed but unavailable (eg. in safe mode or when
|
||||
* sd-card is not available).
|
||||
*/
|
||||
int isDisabled = DEFAULT;
|
||||
public int isDisabled = DEFAULT;
|
||||
|
||||
/**
|
||||
* A message to display when the user tries to start a disabled shortcut.
|
||||
|
@ -140,7 +140,7 @@ public class ShortcutInfo extends ItemInfo {
|
|||
*/
|
||||
CharSequence disabledMessage;
|
||||
|
||||
int status;
|
||||
public int status;
|
||||
|
||||
/**
|
||||
* The installation progress [0-100] of the package that this shortcut represents.
|
||||
|
@ -152,7 +152,7 @@ public class ShortcutInfo extends ItemInfo {
|
|||
* this will hold the original intent from the database. Otherwise, null.
|
||||
* Refer {@link #FLAG_RESTORED_ICON}, {@link #FLAG_AUTOINTALL_ICON}
|
||||
*/
|
||||
Intent promisedIntent;
|
||||
public Intent promisedIntent;
|
||||
|
||||
public ShortcutInfo() {
|
||||
itemType = LauncherSettings.BaseLauncherColumns.ITEM_TYPE_SHORTCUT;
|
||||
|
|
|
@ -0,0 +1,262 @@
|
|||
/*
|
||||
* 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;
|
||||
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;
|
||||
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.compat.UserHandleCompat;
|
||||
import com.android.launcher3.util.GridOccupancy;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Task to add auto-created workspace items.
|
||||
*/
|
||||
public class AddWorkspaceItemsTask extends ExtendedModelTask {
|
||||
|
||||
private final ArrayList<? extends ItemInfo> mWorkspaceApps;
|
||||
|
||||
/**
|
||||
* @param workspaceApps items to add on the workspace
|
||||
*/
|
||||
public AddWorkspaceItemsTask(ArrayList<? extends ItemInfo> workspaceApps) {
|
||||
mWorkspaceApps = workspaceApps;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
|
||||
if (mWorkspaceApps.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Context context = app.getContext();
|
||||
|
||||
final ArrayList<ItemInfo> addedShortcutsFinal = new ArrayList<ItemInfo>();
|
||||
final ArrayList<Long> addedWorkspaceScreensFinal = new ArrayList<Long>();
|
||||
|
||||
// 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) {
|
||||
for (ItemInfo item : mWorkspaceApps) {
|
||||
if (item instanceof ShortcutInfo) {
|
||||
// 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.
|
||||
Pair<Long, int[]> coords = findSpaceForItem(
|
||||
app, dataModel, workspaceScreens, addedWorkspaceScreensFinal, 1, 1);
|
||||
long screenId = coords.first;
|
||||
int[] cordinates = coords.second;
|
||||
|
||||
ItemInfo itemInfo;
|
||||
if (item instanceof ShortcutInfo || item instanceof FolderInfo) {
|
||||
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
|
||||
addedShortcutsFinal.add(itemInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the workspace screens
|
||||
updateScreens(context, workspaceScreens);
|
||||
|
||||
if (!addedShortcutsFinal.isEmpty()) {
|
||||
scheduleCallbackTask(new CallbackTask() {
|
||||
@Override
|
||||
public void execute(Callbacks callbacks) {
|
||||
final ArrayList<ItemInfo> addAnimated = new ArrayList<ItemInfo>();
|
||||
final ArrayList<ItemInfo> addNotAnimated = new ArrayList<ItemInfo>();
|
||||
if (!addedShortcutsFinal.isEmpty()) {
|
||||
ItemInfo info = addedShortcutsFinal.get(addedShortcutsFinal.size() - 1);
|
||||
long lastScreenId = info.screenId;
|
||||
for (ItemInfo i : addedShortcutsFinal) {
|
||||
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.
|
||||
*/
|
||||
protected boolean shortcutExists(BgDataModel dataModel, Intent intent, UserHandleCompat user) {
|
||||
final String intentWithPkg, intentWithoutPkg;
|
||||
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;
|
||||
Intent targetIntent = info.promisedIntent == null
|
||||
? info.intent : info.promisedIntent;
|
||||
if (targetIntent != null && info.user.equals(user)) {
|
||||
Intent copyIntent = new Intent(targetIntent);
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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.ComponentName;
|
||||
|
||||
import com.android.launcher3.AllAppsList;
|
||||
import com.android.launcher3.AppInfo;
|
||||
import com.android.launcher3.IconCache;
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
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.compat.UserHandleCompat;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* Handles changes due to cache updates.
|
||||
*/
|
||||
public class CacheDataUpdatedTask extends ExtendedModelTask {
|
||||
|
||||
public static final int OP_CACHE_UPDATE = 1;
|
||||
public static final int OP_SESSION_UPDATE = 2;
|
||||
|
||||
private final int mOp;
|
||||
private final UserHandleCompat mUser;
|
||||
private final HashSet<String> mPackages;
|
||||
|
||||
public CacheDataUpdatedTask(int op, UserHandleCompat user, HashSet<String> packages) {
|
||||
mOp = op;
|
||||
mUser = user;
|
||||
mPackages = packages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
|
||||
IconCache iconCache = app.getIconCache();
|
||||
|
||||
final ArrayList<AppInfo> updatedApps = new ArrayList<>();
|
||||
|
||||
ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
|
||||
synchronized (dataModel) {
|
||||
for (ItemInfo info : dataModel.itemsIdMap) {
|
||||
if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
|
||||
ShortcutInfo si = (ShortcutInfo) info;
|
||||
ComponentName cn = si.getTargetComponent();
|
||||
if (isValidShortcut(si) &&
|
||||
cn != null && mPackages.contains(cn.getPackageName())) {
|
||||
si.updateIcon(iconCache);
|
||||
updatedShortcuts.add(si);
|
||||
}
|
||||
}
|
||||
}
|
||||
apps.updateIconsAndLabels(mPackages, mUser, updatedApps);
|
||||
}
|
||||
bindUpdatedShortcuts(updatedShortcuts, mUser);
|
||||
|
||||
if (!updatedApps.isEmpty()) {
|
||||
scheduleCallbackTask(new CallbackTask() {
|
||||
@Override
|
||||
public void execute(Callbacks callbacks) {
|
||||
callbacks.bindAppsUpdated(updatedApps);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isValidShortcut(ShortcutInfo si) {
|
||||
switch (mOp) {
|
||||
case OP_CACHE_UPDATE:
|
||||
return si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
|
||||
case OP_SESSION_UPDATE:
|
||||
return si.isPromise();
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 com.android.launcher3.LauncherModel.CallbackTask;
|
||||
import com.android.launcher3.LauncherModel.Callbacks;
|
||||
import com.android.launcher3.LauncherModel.BaseModelUpdateTask;
|
||||
import com.android.launcher3.ShortcutInfo;
|
||||
import com.android.launcher3.compat.UserHandleCompat;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.MultiHashMap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Extension of {@link BaseModelUpdateTask} with some utility methods
|
||||
*/
|
||||
public abstract class ExtendedModelTask extends BaseModelUpdateTask {
|
||||
|
||||
public void bindUpdatedShortcuts(
|
||||
ArrayList<ShortcutInfo> updatedShortcuts, UserHandleCompat user) {
|
||||
bindUpdatedShortcuts(updatedShortcuts, new ArrayList<ShortcutInfo>(), user);
|
||||
}
|
||||
|
||||
public void bindUpdatedShortcuts(
|
||||
final ArrayList<ShortcutInfo> updatedShortcuts,
|
||||
final ArrayList<ShortcutInfo> removedShortcuts,
|
||||
final UserHandleCompat user) {
|
||||
if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) {
|
||||
scheduleCallbackTask(new CallbackTask() {
|
||||
@Override
|
||||
public void execute(Callbacks callbacks) {
|
||||
callbacks.bindShortcutsChanged(updatedShortcuts, removedShortcuts, user);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public void bindDeepShortcuts(BgDataModel dataModel) {
|
||||
final MultiHashMap<ComponentKey, String> shortcutMapCopy = dataModel.deepShortcutMap.clone();
|
||||
scheduleCallbackTask(new CallbackTask() {
|
||||
@Override
|
||||
public void execute(Callbacks callbacks) {
|
||||
callbacks.bindDeepShortcutMap(shortcutMapCopy);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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.ComponentName;
|
||||
|
||||
import com.android.launcher3.AllAppsList;
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.LauncherModel.CallbackTask;
|
||||
import com.android.launcher3.LauncherModel.Callbacks;
|
||||
import com.android.launcher3.ShortcutInfo;
|
||||
import com.android.launcher3.compat.PackageInstallerCompat;
|
||||
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* Handles changes due to a sessions updates for a currently installing app.
|
||||
*/
|
||||
public class PackageInstallStateChangedTask extends ExtendedModelTask {
|
||||
|
||||
private final PackageInstallInfo mInstallInfo;
|
||||
|
||||
public PackageInstallStateChangedTask(PackageInstallInfo installInfo) {
|
||||
mInstallInfo = installInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
|
||||
if (mInstallInfo.state == PackageInstallerCompat.STATUS_INSTALLED) {
|
||||
// Ignore install success events as they are handled by Package add events.
|
||||
return;
|
||||
}
|
||||
|
||||
synchronized (dataModel) {
|
||||
final HashSet<ItemInfo> updates = new HashSet<>();
|
||||
for (ItemInfo info : dataModel.itemsIdMap) {
|
||||
if (info instanceof ShortcutInfo) {
|
||||
ShortcutInfo si = (ShortcutInfo) info;
|
||||
ComponentName cn = si.getTargetComponent();
|
||||
if (si.isPromise() && (cn != null)
|
||||
&& mInstallInfo.packageName.equals(cn.getPackageName())) {
|
||||
si.setInstallProgress(mInstallInfo.progress);
|
||||
|
||||
if (mInstallInfo.state == PackageInstallerCompat.STATUS_FAILED) {
|
||||
// Mark this info as broken.
|
||||
si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE;
|
||||
}
|
||||
updates.add(si);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (LauncherAppWidgetInfo widget : dataModel.appWidgets) {
|
||||
if (widget.providerName.getPackageName().equals(mInstallInfo.packageName)) {
|
||||
widget.installProgress = mInstallInfo.progress;
|
||||
updates.add(widget);
|
||||
}
|
||||
}
|
||||
|
||||
if (!updates.isEmpty()) {
|
||||
scheduleCallbackTask(new CallbackTask() {
|
||||
@Override
|
||||
public void execute(Callbacks callbacks) {
|
||||
callbacks.bindRestoreItemsChange(updates);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,378 @@
|
|||
/*
|
||||
* 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.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.graphics.Bitmap;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.launcher3.AllAppsList;
|
||||
import com.android.launcher3.AppInfo;
|
||||
import com.android.launcher3.IconCache;
|
||||
import com.android.launcher3.InstallShortcutReceiver;
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherAppWidgetInfo;
|
||||
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.Utilities;
|
||||
import com.android.launcher3.compat.LauncherAppsCompat;
|
||||
import com.android.launcher3.compat.UserHandleCompat;
|
||||
import com.android.launcher3.compat.UserManagerCompat;
|
||||
import com.android.launcher3.graphics.LauncherIcons;
|
||||
import com.android.launcher3.util.FlagOp;
|
||||
import com.android.launcher3.util.ItemInfoMatcher;
|
||||
import com.android.launcher3.util.ManagedProfileHeuristic;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* Handles updates due to changes in package manager (app installed/updated/removed)
|
||||
* or when a user availability changes.
|
||||
*/
|
||||
public class PackageUpdatedTask extends ExtendedModelTask {
|
||||
|
||||
private static final boolean DEBUG = false;
|
||||
private static final String TAG = "PackageUpdatedTask";
|
||||
|
||||
public static final int OP_NONE = 0;
|
||||
public static final int OP_ADD = 1;
|
||||
public static final int OP_UPDATE = 2;
|
||||
public static final int OP_REMOVE = 3; // uninstalled
|
||||
public static final int OP_UNAVAILABLE = 4; // external media unmounted
|
||||
public static final int OP_SUSPEND = 5; // package suspended
|
||||
public static final int OP_UNSUSPEND = 6; // package unsuspended
|
||||
public static final int OP_USER_AVAILABILITY_CHANGE = 7; // user available/unavailable
|
||||
|
||||
private final int mOp;
|
||||
private final UserHandleCompat mUser;
|
||||
private final String[] mPackages;
|
||||
|
||||
public PackageUpdatedTask(int op, UserHandleCompat user, String... packages) {
|
||||
mOp = op;
|
||||
mUser = user;
|
||||
mPackages = packages;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList appsList) {
|
||||
final Context context = app.getContext();
|
||||
final IconCache iconCache = app.getIconCache();
|
||||
|
||||
final String[] packages = mPackages;
|
||||
final int N = packages.length;
|
||||
FlagOp flagOp = FlagOp.NO_OP;
|
||||
final HashSet<String> packageSet = new HashSet<>(Arrays.asList(packages));
|
||||
switch (mOp) {
|
||||
case OP_ADD: {
|
||||
for (int i = 0; i < N; i++) {
|
||||
if (DEBUG) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]);
|
||||
iconCache.updateIconsForPkg(packages[i], mUser);
|
||||
appsList.addPackage(context, packages[i], mUser);
|
||||
}
|
||||
|
||||
ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
|
||||
if (heuristic != null) {
|
||||
heuristic.processPackageAdd(mPackages);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OP_UPDATE:
|
||||
for (int i = 0; i < N; i++) {
|
||||
if (DEBUG) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]);
|
||||
iconCache.updateIconsForPkg(packages[i], mUser);
|
||||
appsList.updatePackage(context, packages[i], mUser);
|
||||
app.getWidgetCache().removePackage(packages[i], mUser);
|
||||
}
|
||||
// Since package was just updated, the target must be available now.
|
||||
flagOp = FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
|
||||
break;
|
||||
case OP_REMOVE: {
|
||||
ManagedProfileHeuristic heuristic = ManagedProfileHeuristic.get(context, mUser);
|
||||
if (heuristic != null) {
|
||||
heuristic.processPackageRemoved(mPackages);
|
||||
}
|
||||
for (int i = 0; i < N; i++) {
|
||||
iconCache.removeIconsForPkg(packages[i], mUser);
|
||||
}
|
||||
// Fall through
|
||||
}
|
||||
case OP_UNAVAILABLE:
|
||||
for (int i = 0; i < N; i++) {
|
||||
if (DEBUG) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]);
|
||||
appsList.removePackage(packages[i], mUser);
|
||||
app.getWidgetCache().removePackage(packages[i], mUser);
|
||||
}
|
||||
flagOp = FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_NOT_AVAILABLE);
|
||||
break;
|
||||
case OP_SUSPEND:
|
||||
case OP_UNSUSPEND:
|
||||
flagOp = mOp == OP_SUSPEND ?
|
||||
FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED) :
|
||||
FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_SUSPENDED);
|
||||
if (DEBUG) Log.d(TAG, "mAllAppsList.(un)suspend " + N);
|
||||
appsList.updateDisabledFlags(
|
||||
ItemInfoMatcher.ofPackages(packageSet, mUser), flagOp);
|
||||
break;
|
||||
case OP_USER_AVAILABILITY_CHANGE:
|
||||
flagOp = UserManagerCompat.getInstance(context).isQuietModeEnabled(mUser)
|
||||
? FlagOp.addFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER)
|
||||
: FlagOp.removeFlag(ShortcutInfo.FLAG_DISABLED_QUIET_USER);
|
||||
// We want to update all packages for this user.
|
||||
appsList.updateDisabledFlags(ItemInfoMatcher.ofUser(mUser), flagOp);
|
||||
break;
|
||||
}
|
||||
|
||||
ArrayList<AppInfo> added = null;
|
||||
ArrayList<AppInfo> modified = null;
|
||||
final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>();
|
||||
|
||||
if (appsList.added.size() > 0) {
|
||||
added = new ArrayList<>(appsList.added);
|
||||
appsList.added.clear();
|
||||
}
|
||||
if (appsList.modified.size() > 0) {
|
||||
modified = new ArrayList<>(appsList.modified);
|
||||
appsList.modified.clear();
|
||||
}
|
||||
if (appsList.removed.size() > 0) {
|
||||
removedApps.addAll(appsList.removed);
|
||||
appsList.removed.clear();
|
||||
}
|
||||
|
||||
final HashMap<ComponentName, AppInfo> addedOrUpdatedApps = new HashMap<>();
|
||||
|
||||
if (added != null) {
|
||||
final ArrayList<AppInfo> addedApps = added;
|
||||
scheduleCallbackTask(new CallbackTask() {
|
||||
@Override
|
||||
public void execute(Callbacks callbacks) {
|
||||
callbacks.bindAppsAdded(null, null, null, addedApps);
|
||||
}
|
||||
});
|
||||
for (AppInfo ai : added) {
|
||||
addedOrUpdatedApps.put(ai.componentName, ai);
|
||||
}
|
||||
}
|
||||
|
||||
if (modified != null) {
|
||||
final ArrayList<AppInfo> modifiedFinal = modified;
|
||||
for (AppInfo ai : modified) {
|
||||
addedOrUpdatedApps.put(ai.componentName, ai);
|
||||
}
|
||||
scheduleCallbackTask(new CallbackTask() {
|
||||
@Override
|
||||
public void execute(Callbacks callbacks) {
|
||||
callbacks.bindAppsUpdated(modifiedFinal);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update shortcut infos
|
||||
if (mOp == OP_ADD || flagOp != FlagOp.NO_OP) {
|
||||
final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>();
|
||||
final ArrayList<ShortcutInfo> removedShortcuts = new ArrayList<>();
|
||||
final ArrayList<LauncherAppWidgetInfo> widgets = new ArrayList<>();
|
||||
|
||||
synchronized (dataModel) {
|
||||
for (ItemInfo info : dataModel.itemsIdMap) {
|
||||
if (info instanceof ShortcutInfo && mUser.equals(info.user)) {
|
||||
ShortcutInfo si = (ShortcutInfo) info;
|
||||
boolean infoUpdated = false;
|
||||
boolean shortcutUpdated = false;
|
||||
|
||||
// Update shortcuts which use iconResource.
|
||||
if ((si.iconResource != null)
|
||||
&& packageSet.contains(si.iconResource.packageName)) {
|
||||
Bitmap icon = LauncherIcons.createIconBitmap(
|
||||
si.iconResource.packageName,
|
||||
si.iconResource.resourceName, context);
|
||||
if (icon != null) {
|
||||
si.setIcon(icon);
|
||||
si.usingFallbackIcon = false;
|
||||
infoUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
ComponentName cn = si.getTargetComponent();
|
||||
if (cn != null && packageSet.contains(cn.getPackageName())) {
|
||||
AppInfo appInfo = addedOrUpdatedApps.get(cn);
|
||||
|
||||
if (si.isPromise()) {
|
||||
if (si.hasStatusFlag(ShortcutInfo.FLAG_AUTOINTALL_ICON)) {
|
||||
// Auto install icon
|
||||
PackageManager pm = context.getPackageManager();
|
||||
ResolveInfo matched = pm.resolveActivity(
|
||||
new Intent(Intent.ACTION_MAIN)
|
||||
.setComponent(cn).addCategory(Intent.CATEGORY_LAUNCHER),
|
||||
PackageManager.MATCH_DEFAULT_ONLY);
|
||||
if (matched == null) {
|
||||
// Try to find the best match activity.
|
||||
Intent intent = pm.getLaunchIntentForPackage(
|
||||
cn.getPackageName());
|
||||
if (intent != null) {
|
||||
cn = intent.getComponent();
|
||||
appInfo = addedOrUpdatedApps.get(cn);
|
||||
}
|
||||
|
||||
if ((intent == null) || (appInfo == null)) {
|
||||
removedShortcuts.add(si);
|
||||
continue;
|
||||
}
|
||||
si.promisedIntent = intent;
|
||||
}
|
||||
}
|
||||
|
||||
si.intent = si.promisedIntent;
|
||||
si.promisedIntent = null;
|
||||
si.status = ShortcutInfo.DEFAULT;
|
||||
infoUpdated = true;
|
||||
si.updateIcon(iconCache);
|
||||
}
|
||||
|
||||
if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction())
|
||||
&& si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) {
|
||||
si.updateIcon(iconCache);
|
||||
si.title = Utilities.trim(appInfo.title);
|
||||
si.contentDescription = appInfo.contentDescription;
|
||||
infoUpdated = true;
|
||||
}
|
||||
|
||||
int oldDisabledFlags = si.isDisabled;
|
||||
si.isDisabled = flagOp.apply(si.isDisabled);
|
||||
if (si.isDisabled != oldDisabledFlags) {
|
||||
shortcutUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (infoUpdated || shortcutUpdated) {
|
||||
updatedShortcuts.add(si);
|
||||
}
|
||||
if (infoUpdated) {
|
||||
LauncherModel.updateItemInDatabase(context, si);
|
||||
}
|
||||
} else if (info instanceof LauncherAppWidgetInfo && mOp == OP_ADD) {
|
||||
LauncherAppWidgetInfo widgetInfo = (LauncherAppWidgetInfo) info;
|
||||
if (mUser.equals(widgetInfo.user)
|
||||
&& widgetInfo.hasRestoreFlag(LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY)
|
||||
&& packageSet.contains(widgetInfo.providerName.getPackageName())) {
|
||||
widgetInfo.restoreStatus &=
|
||||
~LauncherAppWidgetInfo.FLAG_PROVIDER_NOT_READY &
|
||||
~LauncherAppWidgetInfo.FLAG_RESTORE_STARTED;
|
||||
|
||||
// adding this flag ensures that launcher shows 'click to setup'
|
||||
// if the widget has a config activity. In case there is no config
|
||||
// activity, it will be marked as 'restored' during bind.
|
||||
widgetInfo.restoreStatus |= LauncherAppWidgetInfo.FLAG_UI_NOT_READY;
|
||||
|
||||
widgets.add(widgetInfo);
|
||||
LauncherModel.updateItemInDatabase(context, widgetInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bindUpdatedShortcuts(updatedShortcuts, removedShortcuts, mUser);
|
||||
if (!removedShortcuts.isEmpty()) {
|
||||
LauncherModel.deleteItemsFromDatabase(context, removedShortcuts);
|
||||
}
|
||||
|
||||
if (!widgets.isEmpty()) {
|
||||
scheduleCallbackTask(new CallbackTask() {
|
||||
@Override
|
||||
public void execute(Callbacks callbacks) {
|
||||
callbacks.bindWidgetsRestored(widgets);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
final HashSet<String> removedPackages = new HashSet<>();
|
||||
final HashSet<ComponentName> removedComponents = new HashSet<>();
|
||||
if (mOp == OP_REMOVE) {
|
||||
// Mark all packages in the broadcast to be removed
|
||||
Collections.addAll(removedPackages, packages);
|
||||
|
||||
// No need to update the removedComponents as
|
||||
// removedPackages is a super-set of removedComponents
|
||||
} else if (mOp == OP_UPDATE) {
|
||||
// Mark disabled packages in the broadcast to be removed
|
||||
final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context);
|
||||
for (int i=0; i<N; i++) {
|
||||
if (!launcherApps.isPackageEnabledForProfile(packages[i], mUser)) {
|
||||
removedPackages.add(packages[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Update removedComponents as some components can get removed during package update
|
||||
for (AppInfo info : removedApps) {
|
||||
removedComponents.add(info.componentName);
|
||||
}
|
||||
}
|
||||
|
||||
if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
|
||||
LauncherModel.deleteItemsFromDatabase(
|
||||
context, ItemInfoMatcher.ofPackages(removedPackages, mUser));
|
||||
LauncherModel.deleteItemsFromDatabase(
|
||||
context, ItemInfoMatcher.ofComponents(removedComponents, mUser));
|
||||
|
||||
// Remove any queued items from the install queue
|
||||
InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser);
|
||||
|
||||
// Call the components-removed callback
|
||||
scheduleCallbackTask(new CallbackTask() {
|
||||
@Override
|
||||
public void execute(Callbacks callbacks) {
|
||||
callbacks.bindWorkspaceComponentsRemoved(
|
||||
removedPackages, removedComponents, mUser);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (!removedApps.isEmpty()) {
|
||||
// Remove corresponding apps from All-Apps
|
||||
scheduleCallbackTask(new CallbackTask() {
|
||||
@Override
|
||||
public void execute(Callbacks callbacks) {
|
||||
callbacks.bindAppInfosRemoved(removedApps);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Notify launcher of widget update. From marshmallow onwards we use AppWidgetHost to
|
||||
// get widget update signals.
|
||||
if (!Utilities.ATLEAST_MARSHMALLOW &&
|
||||
(mOp == OP_ADD || mOp == OP_REMOVE || mOp == OP_UPDATE)) {
|
||||
scheduleCallbackTask(new CallbackTask() {
|
||||
@Override
|
||||
public void execute(Callbacks callbacks) {
|
||||
callbacks.notifyWidgetProvidersChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
* 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 com.android.launcher3.AllAppsList;
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherModel;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.ShortcutInfo;
|
||||
import com.android.launcher3.compat.UserHandleCompat;
|
||||
import com.android.launcher3.shortcuts.DeepShortcutManager;
|
||||
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
|
||||
import com.android.launcher3.util.MultiHashMap;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Handles changes due to shortcut manager updates (deep shortcut changes)
|
||||
*/
|
||||
public class ShortcutsChangedTask extends ExtendedModelTask {
|
||||
|
||||
private final String mPackageName;
|
||||
private final List<ShortcutInfoCompat> mShortcuts;
|
||||
private final UserHandleCompat mUser;
|
||||
private final boolean mUpdateIdMap;
|
||||
|
||||
public ShortcutsChangedTask(String packageName, List<ShortcutInfoCompat> shortcuts,
|
||||
UserHandleCompat user, boolean updateIdMap) {
|
||||
mPackageName = packageName;
|
||||
mShortcuts = shortcuts;
|
||||
mUser = user;
|
||||
mUpdateIdMap = updateIdMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
|
||||
DeepShortcutManager deepShortcutManager = app.getShortcutManager();
|
||||
deepShortcutManager.onShortcutsChanged(mShortcuts);
|
||||
|
||||
// Find ShortcutInfo's that have changed on the workspace.
|
||||
final ArrayList<ShortcutInfo> removedShortcutInfos = new ArrayList<>();
|
||||
MultiHashMap<String, ShortcutInfo> idsToWorkspaceShortcutInfos = new MultiHashMap<>();
|
||||
for (ItemInfo itemInfo : dataModel.itemsIdMap) {
|
||||
if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) {
|
||||
ShortcutInfo si = (ShortcutInfo) itemInfo;
|
||||
if (si.getPromisedIntent().getPackage().equals(mPackageName)
|
||||
&& si.user.equals(mUser)) {
|
||||
idsToWorkspaceShortcutInfos.addToList(si.getDeepShortcutId(), si);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final Context context = LauncherAppState.getInstance().getContext();
|
||||
final ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
|
||||
if (!idsToWorkspaceShortcutInfos.isEmpty()) {
|
||||
// Update the workspace to reflect the changes to updated shortcuts residing on it.
|
||||
List<ShortcutInfoCompat> shortcuts = deepShortcutManager.queryForFullDetails(
|
||||
mPackageName, new ArrayList<>(idsToWorkspaceShortcutInfos.keySet()), mUser);
|
||||
for (ShortcutInfoCompat fullDetails : shortcuts) {
|
||||
List<ShortcutInfo> shortcutInfos = idsToWorkspaceShortcutInfos
|
||||
.remove(fullDetails.getId());
|
||||
if (!fullDetails.isPinned()) {
|
||||
// The shortcut was previously pinned but is no longer, so remove it from
|
||||
// the workspace and our pinned shortcut counts.
|
||||
// Note that we put this check here, after querying for full details,
|
||||
// because there's a possible race condition between pinning and
|
||||
// receiving this callback.
|
||||
removedShortcutInfos.addAll(shortcutInfos);
|
||||
continue;
|
||||
}
|
||||
for (ShortcutInfo shortcutInfo : shortcutInfos) {
|
||||
shortcutInfo.updateFromDeepShortcutInfo(fullDetails, context);
|
||||
updatedShortcutInfos.add(shortcutInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If there are still entries in idsToWorkspaceShortcutInfos, that means that
|
||||
// the corresponding shortcuts weren't passed in onShortcutsChanged(). This
|
||||
// means they were cleared, so we remove and unpin them now.
|
||||
for (String id : idsToWorkspaceShortcutInfos.keySet()) {
|
||||
removedShortcutInfos.addAll(idsToWorkspaceShortcutInfos.get(id));
|
||||
}
|
||||
|
||||
bindUpdatedShortcuts(updatedShortcutInfos, removedShortcutInfos, mUser);
|
||||
if (!removedShortcutInfos.isEmpty()) {
|
||||
LauncherModel.deleteItemsFromDatabase(context, removedShortcutInfos);
|
||||
}
|
||||
|
||||
if (mUpdateIdMap) {
|
||||
// Update the deep shortcut map if the list of ids has changed for an activity.
|
||||
dataModel.updateDeepShortcutMap(mPackageName, mUser, mShortcuts);
|
||||
bindDeepShortcuts(dataModel);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,114 @@
|
|||
/*
|
||||
* 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 com.android.launcher3.AllAppsList;
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherModel;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.ShortcutInfo;
|
||||
import com.android.launcher3.compat.UserHandleCompat;
|
||||
import com.android.launcher3.compat.UserManagerCompat;
|
||||
import com.android.launcher3.shortcuts.DeepShortcutManager;
|
||||
import com.android.launcher3.shortcuts.ShortcutInfoCompat;
|
||||
import com.android.launcher3.shortcuts.ShortcutKey;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Task to handle changing of lock state of the user
|
||||
*/
|
||||
public class UserLockStateChangedTask extends ExtendedModelTask {
|
||||
|
||||
private final UserHandleCompat mUser;
|
||||
|
||||
public UserLockStateChangedTask(UserHandleCompat user) {
|
||||
mUser = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) {
|
||||
Context context = app.getContext();
|
||||
boolean isUserUnlocked = UserManagerCompat.getInstance(context).isUserUnlocked(mUser);
|
||||
DeepShortcutManager deepShortcutManager = app.getShortcutManager();
|
||||
|
||||
HashMap<ShortcutKey, ShortcutInfoCompat> pinnedShortcuts = new HashMap<>();
|
||||
if (isUserUnlocked) {
|
||||
List<ShortcutInfoCompat> shortcuts =
|
||||
deepShortcutManager.queryForPinnedShortcuts(null, mUser);
|
||||
if (deepShortcutManager.wasLastCallSuccess()) {
|
||||
for (ShortcutInfoCompat shortcut : shortcuts) {
|
||||
pinnedShortcuts.put(ShortcutKey.fromInfo(shortcut), shortcut);
|
||||
}
|
||||
} else {
|
||||
// Shortcut manager can fail due to some race condition when the lock state
|
||||
// changes too frequently. For the purpose of the update,
|
||||
// consider it as still locked.
|
||||
isUserUnlocked = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Update the workspace to reflect the changes to updated shortcuts residing on it.
|
||||
ArrayList<ShortcutInfo> updatedShortcutInfos = new ArrayList<>();
|
||||
ArrayList<ShortcutInfo> deletedShortcutInfos = new ArrayList<>();
|
||||
for (ItemInfo itemInfo : dataModel.itemsIdMap) {
|
||||
if (itemInfo.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
|
||||
&& mUser.equals(itemInfo.user)) {
|
||||
ShortcutInfo si = (ShortcutInfo) itemInfo;
|
||||
if (isUserUnlocked) {
|
||||
ShortcutInfoCompat shortcut =
|
||||
pinnedShortcuts.get(ShortcutKey.fromShortcutInfo(si));
|
||||
// We couldn't verify the shortcut during loader. If its no longer available
|
||||
// (probably due to clear data), delete the workspace item as well
|
||||
if (shortcut == null) {
|
||||
deletedShortcutInfos.add(si);
|
||||
continue;
|
||||
}
|
||||
si.isDisabled &= ~ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
|
||||
si.updateFromDeepShortcutInfo(shortcut, context);
|
||||
} else {
|
||||
si.isDisabled |= ShortcutInfo.FLAG_DISABLED_LOCKED_USER;
|
||||
}
|
||||
updatedShortcutInfos.add(si);
|
||||
}
|
||||
}
|
||||
bindUpdatedShortcuts(updatedShortcutInfos, deletedShortcutInfos, mUser);
|
||||
if (!deletedShortcutInfos.isEmpty()) {
|
||||
LauncherModel.deleteItemsFromDatabase(context, deletedShortcutInfos);
|
||||
}
|
||||
|
||||
// Remove shortcut id map for that user
|
||||
Iterator<ComponentKey> keysIter = dataModel.deepShortcutMap.keySet().iterator();
|
||||
while (keysIter.hasNext()) {
|
||||
if (keysIter.next().user.equals(mUser)) {
|
||||
keysIter.remove();
|
||||
}
|
||||
}
|
||||
|
||||
if (isUserUnlocked) {
|
||||
dataModel.updateDeepShortcutMap(
|
||||
null, mUser, deepShortcutManager.queryForAllShortcuts(mUser));
|
||||
}
|
||||
bindDeepShortcuts(dataModel);
|
||||
}
|
||||
}
|
|
@ -121,7 +121,7 @@ public class ManagedProfileHeuristic {
|
|||
// getting filled with the managed user apps, when it start with a fresh DB (or after
|
||||
// a very long time).
|
||||
if (userAppsExisted && !homescreenApps.isEmpty()) {
|
||||
mModel.addAndBindAddedWorkspaceItems(mContext, homescreenApps);
|
||||
mModel.addAndBindAddedWorkspaceItems(homescreenApps);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,7 +175,7 @@ public class ManagedProfileHeuristic {
|
|||
// Add the item to home screen and DB. This also generates an item id synchronously.
|
||||
ArrayList<ItemInfo> itemList = new ArrayList<ItemInfo>(1);
|
||||
itemList.add(workFolder);
|
||||
mModel.addAndBindAddedWorkspaceItems(mContext, itemList);
|
||||
mModel.addAndBindAddedWorkspaceItems(itemList);
|
||||
mPrefs.edit().putLong(folderIdKey, workFolder.id).apply();
|
||||
|
||||
saveWorkFolderShortcuts(workFolder.id, 0, workFolderApps);
|
||||
|
@ -200,7 +200,6 @@ public class ManagedProfileHeuristic {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verifies that entries corresponding to {@param users} exist and removes all invalid entries.
|
||||
*/
|
||||
|
|
|
@ -17,11 +17,12 @@ LOCAL_PATH := $(call my-dir)
|
|||
include $(CLEAR_VARS)
|
||||
|
||||
LOCAL_MODULE_TAGS := tests
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator
|
||||
LOCAL_STATIC_JAVA_LIBRARIES := android-support-test ub-uiautomator mockito-target-minus-junit4
|
||||
|
||||
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||
|
||||
LOCAL_SDK_VERSION := current
|
||||
LOCAL_MIN_SDK_VERSION := 21
|
||||
|
||||
LOCAL_PACKAGE_NAME := Launcher3Tests
|
||||
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
# Model data used by CacheDataUpdatedTaskTest
|
||||
|
||||
classMap s com.android.launcher3.ShortcutInfo
|
||||
|
||||
# Items for the BgDataModel
|
||||
|
||||
# App shortcuts
|
||||
bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
|
||||
bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
|
||||
bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
|
||||
bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
|
||||
|
||||
# Auto install app shortcut
|
||||
bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
|
||||
bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
|
||||
|
||||
# Custom shortcuts
|
||||
bgItem s itemType=1 title=app1-shrt intent=component=app1/class3 id=7
|
||||
bgItem s itemType=1 title=app4-shrt intent=component=app4/class1 id=8
|
||||
|
||||
# Restored custom shortcut
|
||||
bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=9
|
||||
bgItem s itemType=1 status=1 title=app5-shrt intent=component=app5/class1 id=10
|
||||
|
||||
allApps componentName=app1/class1
|
||||
allApps componentName=app1/class2
|
||||
allApps componentName=app2/class1
|
||||
allApps componentName=app2/class2
|
|
@ -0,0 +1,24 @@
|
|||
# Model data used by PackageInstallStateChangeTaskTest
|
||||
|
||||
classMap s com.android.launcher3.ShortcutInfo
|
||||
classMap w com.android.launcher3.LauncherAppWidgetInfo
|
||||
|
||||
# Items for the BgDataModel
|
||||
|
||||
# App shortcuts
|
||||
bgItem s itemType=0 title=app1-class1 intent=component=app1/class1 id=1
|
||||
bgItem s itemType=0 title=app1-class2 intent=component=app1/class2 id=2
|
||||
bgItem s itemType=0 title=app2-class1 intent=component=app2/class1 id=3
|
||||
bgItem s itemType=0 title=app2-class2 intent=component=app2/class2 id=4
|
||||
|
||||
# Promise icons for app3
|
||||
bgItem s itemType=0 status=2 title=app3-class1 intent=component=app3/class1 id=5
|
||||
bgItem s itemType=0 status=2 title=app3-class2 intent=component=app3/class2 id=6
|
||||
bgItem s itemType=1 status=1 title=app3-shrt intent=component=app3/class3 id=7
|
||||
|
||||
# Promise icon for app4
|
||||
bgItem s itemType=1 status=1 title=app4-shrt intent=component=app4/class1 id=8
|
||||
|
||||
# Widget
|
||||
bgItem w providerName=app4/provider1 id=9
|
||||
bgItem w providerName=app5/provider1 id=10
|
|
@ -0,0 +1,190 @@
|
|||
package com.android.launcher3.model;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.ContentProviderOperation;
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Rect;
|
||||
import android.net.Uri;
|
||||
import android.util.Pair;
|
||||
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.ShortcutInfo;
|
||||
import com.android.launcher3.config.ProviderConfig;
|
||||
import com.android.launcher3.util.GridOccupancy;
|
||||
import com.android.launcher3.util.LongArrayMap;
|
||||
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for {@link AddWorkspaceItemsTask}
|
||||
*/
|
||||
public class AddWorkspaceItemsTaskTest extends BaseModelUpdateTaskTestCase {
|
||||
|
||||
private final ComponentName mComponent1 = new ComponentName("a", "b");
|
||||
private final ComponentName mComponent2 = new ComponentName("b", "b");
|
||||
|
||||
private ArrayList<Long> existingScreens;
|
||||
private ArrayList<Long> newScreens;
|
||||
private LongArrayMap<GridOccupancy> screenOccupancy;
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
existingScreens = new ArrayList<>();
|
||||
screenOccupancy = new LongArrayMap<>();
|
||||
newScreens = new ArrayList<>();
|
||||
|
||||
idp.numColumns = 5;
|
||||
idp.numRows = 5;
|
||||
}
|
||||
|
||||
private <T extends ItemInfo> AddWorkspaceItemsTask newTask(T... items) {
|
||||
return new AddWorkspaceItemsTask(new ArrayList<>(Arrays.asList(items))) {
|
||||
|
||||
@Override
|
||||
protected void addItemToDatabase(Context context, ItemInfo item,
|
||||
long screenId, int[] pos) {
|
||||
item.screenId = screenId;
|
||||
item.cellX = pos[0];
|
||||
item.cellY = pos[1];
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void updateScreens(Context context, ArrayList<Long> workspaceScreens) { }
|
||||
};
|
||||
}
|
||||
|
||||
public void testFindSpaceForItem_prefers_second() {
|
||||
// First screen has only one hole of size 1
|
||||
int nextId = setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
|
||||
|
||||
// Second screen has 2 holes of sizes 3x2 and 2x3
|
||||
setupWorkspaceWithHoles(nextId, 2, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
|
||||
|
||||
Pair<Long, int[]> spaceFound = newTask()
|
||||
.findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 1, 1);
|
||||
assertEquals(2L, (long) spaceFound.first);
|
||||
assertTrue(screenOccupancy.get(spaceFound.first)
|
||||
.isRegionVacant(spaceFound.second[0], spaceFound.second[1], 1, 1));
|
||||
|
||||
// Find a larger space
|
||||
spaceFound = newTask()
|
||||
.findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 2, 3);
|
||||
assertEquals(2L, (long) spaceFound.first);
|
||||
assertTrue(screenOccupancy.get(spaceFound.first)
|
||||
.isRegionVacant(spaceFound.second[0], spaceFound.second[1], 2, 3));
|
||||
}
|
||||
|
||||
public void testFindSpaceForItem_adds_new_screen() throws Exception {
|
||||
// First screen has 2 holes of sizes 3x2 and 2x3
|
||||
setupWorkspaceWithHoles(1, 1, new Rect(2, 0, 5, 2), new Rect(0, 2, 2, 5));
|
||||
commitScreensToDb();
|
||||
|
||||
when(appState.getContext()).thenReturn(getMockContext());
|
||||
|
||||
ArrayList<Long> oldScreens = new ArrayList<>(existingScreens);
|
||||
Pair<Long, int[]> spaceFound = newTask()
|
||||
.findSpaceForItem(appState, bgDataModel, existingScreens, newScreens, 3, 3);
|
||||
assertFalse(oldScreens.contains(spaceFound.first));
|
||||
assertTrue(newScreens.contains(spaceFound.first));
|
||||
}
|
||||
|
||||
public void testAddItem_existing_item_ignored() throws Exception {
|
||||
ShortcutInfo info = new ShortcutInfo();
|
||||
info.intent = new Intent().setComponent(mComponent1);
|
||||
|
||||
// Setup a screen with a hole
|
||||
setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
|
||||
commitScreensToDb();
|
||||
|
||||
when(appState.getContext()).thenReturn(getMockContext());
|
||||
|
||||
// Nothing was added
|
||||
assertTrue(executeTaskForTest(newTask(info)).isEmpty());
|
||||
}
|
||||
|
||||
public void testAddItem_some_items_added() throws Exception {
|
||||
ShortcutInfo info = new ShortcutInfo();
|
||||
info.intent = new Intent().setComponent(mComponent1);
|
||||
|
||||
ShortcutInfo info2 = new ShortcutInfo();
|
||||
info2.intent = new Intent().setComponent(mComponent2);
|
||||
|
||||
// Setup a screen with a hole
|
||||
setupWorkspaceWithHoles(1, 1, new Rect(2, 2, 3, 3));
|
||||
commitScreensToDb();
|
||||
|
||||
when(appState.getContext()).thenReturn(getMockContext());
|
||||
|
||||
executeTaskForTest(newTask(info, info2)).get(0).run();
|
||||
ArgumentCaptor<ArrayList> notAnimated = ArgumentCaptor.forClass(ArrayList.class);
|
||||
ArgumentCaptor<ArrayList> animated = ArgumentCaptor.forClass(ArrayList.class);
|
||||
|
||||
// only info2 should be added because info was already added to the workspace
|
||||
// in setupWorkspaceWithHoles()
|
||||
verify(callbacks).bindAppsAdded(any(ArrayList.class), notAnimated.capture(),
|
||||
animated.capture(), any(ArrayList.class));
|
||||
assertTrue(notAnimated.getValue().isEmpty());
|
||||
|
||||
assertEquals(1, animated.getValue().size());
|
||||
assertTrue(animated.getValue().contains(info2));
|
||||
}
|
||||
|
||||
private int setupWorkspaceWithHoles(int startId, long screenId, Rect... holes) {
|
||||
GridOccupancy occupancy = new GridOccupancy(idp.numColumns, idp.numRows);
|
||||
occupancy.markCells(0, 0, idp.numColumns, idp.numRows, true);
|
||||
for (Rect r : holes) {
|
||||
occupancy.markCells(r, false);
|
||||
}
|
||||
|
||||
existingScreens.add(screenId);
|
||||
screenOccupancy.append(screenId, occupancy);
|
||||
|
||||
for (int x = 0; x < idp.numColumns; x++) {
|
||||
for (int y = 0; y < idp.numRows; y++) {
|
||||
if (!occupancy.cells[x][y]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ShortcutInfo info = new ShortcutInfo();
|
||||
info.intent = new Intent().setComponent(mComponent1);
|
||||
info.id = startId++;
|
||||
info.screenId = screenId;
|
||||
info.cellX = x;
|
||||
info.cellY = y;
|
||||
info.container = LauncherSettings.Favorites.CONTAINER_DESKTOP;
|
||||
bgDataModel.addItem(info, false);
|
||||
}
|
||||
}
|
||||
return startId;
|
||||
}
|
||||
|
||||
private void commitScreensToDb() throws Exception {
|
||||
LauncherSettings.Settings.call(getMockContentResolver(),
|
||||
LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
|
||||
|
||||
Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI;
|
||||
ArrayList<ContentProviderOperation> ops = new ArrayList<>();
|
||||
// Clear the table
|
||||
ops.add(ContentProviderOperation.newDelete(uri).build());
|
||||
int count = existingScreens.size();
|
||||
for (int i = 0; i < count; i++) {
|
||||
ContentValues v = new ContentValues();
|
||||
long screenId = existingScreens.get(i);
|
||||
v.put(LauncherSettings.WorkspaceScreens._ID, screenId);
|
||||
v.put(LauncherSettings.WorkspaceScreens.SCREEN_RANK, i);
|
||||
ops.add(ContentProviderOperation.newInsert(uri).withValues(v).build());
|
||||
}
|
||||
getMockContentResolver().applyBatch(ProviderConfig.AUTHORITY, ops);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,208 @@
|
|||
package com.android.launcher3.model;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Bitmap.Config;
|
||||
import android.support.test.InstrumentationRegistry;
|
||||
import android.test.ProviderTestCase2;
|
||||
|
||||
import com.android.launcher3.AllAppsList;
|
||||
import com.android.launcher3.AppInfo;
|
||||
import com.android.launcher3.DeferredHandler;
|
||||
import com.android.launcher3.IconCache;
|
||||
import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherModel;
|
||||
import com.android.launcher3.LauncherModel.Callbacks;
|
||||
import com.android.launcher3.LauncherModel.BaseModelUpdateTask;
|
||||
import com.android.launcher3.compat.LauncherActivityInfoCompat;
|
||||
import com.android.launcher3.compat.UserHandleCompat;
|
||||
import com.android.launcher3.config.ProviderConfig;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.TestLauncherProvider;
|
||||
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import static org.mockito.Mockito.atLeast;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Base class for writing tests for Model update tasks.
|
||||
*/
|
||||
public class BaseModelUpdateTaskTestCase extends ProviderTestCase2<TestLauncherProvider> {
|
||||
|
||||
public final HashMap<Class, HashMap<String, Field>> fieldCache = new HashMap<>();
|
||||
|
||||
public Context targetContext;
|
||||
public UserHandleCompat myUser;
|
||||
|
||||
public InvariantDeviceProfile idp;
|
||||
public LauncherAppState appState;
|
||||
public MyIconCache iconCache;
|
||||
|
||||
public BgDataModel bgDataModel;
|
||||
public AllAppsList allAppsList;
|
||||
public Callbacks callbacks;
|
||||
|
||||
public BaseModelUpdateTaskTestCase() {
|
||||
super(TestLauncherProvider.class, ProviderConfig.AUTHORITY);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
callbacks = mock(Callbacks.class);
|
||||
appState = mock(LauncherAppState.class);
|
||||
myUser = UserHandleCompat.myUserHandle();
|
||||
|
||||
bgDataModel = new BgDataModel();
|
||||
targetContext = InstrumentationRegistry.getTargetContext();
|
||||
idp = new InvariantDeviceProfile();
|
||||
iconCache = new MyIconCache(targetContext, idp);
|
||||
|
||||
allAppsList = new AllAppsList(iconCache, null);
|
||||
|
||||
when(appState.getIconCache()).thenReturn(iconCache);
|
||||
when(appState.getInvariantDeviceProfile()).thenReturn(idp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronously executes the task and returns all the UI callbacks posted.
|
||||
*/
|
||||
public List<Runnable> executeTaskForTest(BaseModelUpdateTask task) throws Exception {
|
||||
LauncherModel mockModel = mock(LauncherModel.class);
|
||||
when(mockModel.getCallback()).thenReturn(callbacks);
|
||||
|
||||
Field f = BaseModelUpdateTask.class.getDeclaredField("mModel");
|
||||
f.setAccessible(true);
|
||||
f.set(task, mockModel);
|
||||
|
||||
DeferredHandler mockHandler = mock(DeferredHandler.class);
|
||||
f = BaseModelUpdateTask.class.getDeclaredField("mUiHandler");
|
||||
f.setAccessible(true);
|
||||
f.set(task, mockHandler);
|
||||
|
||||
task.execute(appState, bgDataModel, allAppsList);
|
||||
ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
|
||||
verify(mockHandler, atLeast(0)).post(captor.capture());
|
||||
|
||||
return captor.getAllValues();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes mock data for the test.
|
||||
*/
|
||||
public void initializeData(String resourceName) throws Exception {
|
||||
Context myContext = InstrumentationRegistry.getContext();
|
||||
Resources res = myContext.getResources();
|
||||
int id = res.getIdentifier(resourceName, "raw", myContext.getPackageName());
|
||||
try (BufferedReader reader =
|
||||
new BufferedReader(new InputStreamReader(res.openRawResource(id)))) {
|
||||
String line;
|
||||
HashMap<String, Class> classMap = new HashMap<>();
|
||||
while((line = reader.readLine()) != null) {
|
||||
line = line.trim();
|
||||
if (line.startsWith("#") || line.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
String[] commands = line.split(" ");
|
||||
switch (commands[0]) {
|
||||
case "classMap":
|
||||
classMap.put(commands[1], Class.forName(commands[2]));
|
||||
break;
|
||||
case "bgItem":
|
||||
bgDataModel.addItem(
|
||||
(ItemInfo) initItem(classMap.get(commands[1]), commands, 2), false);
|
||||
break;
|
||||
case "allApps":
|
||||
allAppsList.add((AppInfo) initItem(AppInfo.class, commands, 1));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Object initItem(Class clazz, String[] fieldDef, int startIndex) throws Exception {
|
||||
HashMap<String, Field> cache = fieldCache.get(clazz);
|
||||
if (cache == null) {
|
||||
cache = new HashMap<>();
|
||||
Class c = clazz;
|
||||
while (c != null) {
|
||||
for (Field f : c.getDeclaredFields()) {
|
||||
f.setAccessible(true);
|
||||
cache.put(f.getName(), f);
|
||||
}
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
fieldCache.put(clazz, cache);
|
||||
}
|
||||
|
||||
Object item = clazz.newInstance();
|
||||
for (int i = startIndex; i < fieldDef.length; i++) {
|
||||
String[] fieldData = fieldDef[i].split("=", 2);
|
||||
Field f = cache.get(fieldData[0]);
|
||||
Class type = f.getType();
|
||||
if (type == int.class || type == long.class) {
|
||||
f.set(item, Integer.parseInt(fieldData[1]));
|
||||
} else if (type == CharSequence.class || type == String.class) {
|
||||
f.set(item, fieldData[1]);
|
||||
} else if (type == Intent.class) {
|
||||
if (!fieldData[1].startsWith("#Intent")) {
|
||||
fieldData[1] = "#Intent;" + fieldData[1] + ";end";
|
||||
}
|
||||
f.set(item, Intent.parseUri(fieldData[1], 0));
|
||||
} else if (type == ComponentName.class) {
|
||||
f.set(item, ComponentName.unflattenFromString(fieldData[1]));
|
||||
} else {
|
||||
throw new Exception("Added parsing logic for "
|
||||
+ f.getName() + " of type " + f.getType());
|
||||
}
|
||||
}
|
||||
return item;
|
||||
}
|
||||
|
||||
public static class MyIconCache extends IconCache {
|
||||
|
||||
private final HashMap<ComponentKey, CacheEntry> mCache = new HashMap<>();
|
||||
|
||||
public MyIconCache(Context context, InvariantDeviceProfile idp) {
|
||||
super(context, idp);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected CacheEntry cacheLocked(ComponentName componentName,
|
||||
LauncherActivityInfoCompat info, UserHandleCompat user,
|
||||
boolean usePackageIcon, boolean useLowResIcon) {
|
||||
CacheEntry entry = mCache.get(new ComponentKey(componentName, user));
|
||||
if (entry == null) {
|
||||
entry = new CacheEntry();
|
||||
entry.icon = getDefaultIcon(user);
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
public void addCache(ComponentName key, String title) {
|
||||
CacheEntry entry = new CacheEntry();
|
||||
entry.icon = newIcon();
|
||||
entry.title = title;
|
||||
mCache.put(new ComponentKey(key, UserHandleCompat.myUserHandle()), entry);
|
||||
}
|
||||
|
||||
public Bitmap newIcon() {
|
||||
return Bitmap.createBitmap(1, 1, Config.ARGB_8888);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
package com.android.launcher3.model;
|
||||
|
||||
import com.android.launcher3.AppInfo;
|
||||
import com.android.launcher3.IconCache;
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.ShortcutInfo;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link CacheDataUpdatedTask}
|
||||
*/
|
||||
public class CacheDataUpdatedTaskTest extends BaseModelUpdateTaskTestCase {
|
||||
|
||||
private static final String NEW_LABEL_PREFIX = "new-label-";
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
|
||||
initializeData("cache_data_updated_task_data");
|
||||
// Add dummy entries in the cache to simulate update
|
||||
for (ItemInfo info : bgDataModel.itemsIdMap) {
|
||||
iconCache.addCache(info.getTargetComponent(), NEW_LABEL_PREFIX + info.id);
|
||||
}
|
||||
}
|
||||
|
||||
private CacheDataUpdatedTask newTask(int op, String... pkg) {
|
||||
return new CacheDataUpdatedTask(op, myUser, new HashSet<>(Arrays.asList(pkg)));
|
||||
}
|
||||
|
||||
public void testCacheUpdate_update_apps() throws Exception {
|
||||
executeTaskForTest(newTask(CacheDataUpdatedTask.OP_CACHE_UPDATE, "app1"));
|
||||
|
||||
// Verify that only the app icons of app1 (id 1 & 2) are updated. Custom shortcut (id 7)
|
||||
// is not updated
|
||||
verifyUpdate(1L, 2L);
|
||||
|
||||
// Verify that only app1 var updated in allAppsList
|
||||
assertFalse(allAppsList.data.isEmpty());
|
||||
for (AppInfo info : allAppsList.data) {
|
||||
if (info.componentName.getPackageName().equals("app1")) {
|
||||
assertNotNull(info.iconBitmap);
|
||||
} else {
|
||||
assertNull(info.iconBitmap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void testSessionUpdate_ignores_normal_apps() throws Exception {
|
||||
executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app1"));
|
||||
|
||||
// app1 has no restored shortcuts. Verify that nothing was updated.
|
||||
verifyUpdate();
|
||||
}
|
||||
|
||||
public void testSessionUpdate_updates_pending_apps() throws Exception {
|
||||
executeTaskForTest(newTask(CacheDataUpdatedTask.OP_SESSION_UPDATE, "app3"));
|
||||
|
||||
// app3 has only restored apps (id 5, 6) and shortcuts (id 9). Verify that only apps were
|
||||
// were updated
|
||||
verifyUpdate(5L, 6L);
|
||||
}
|
||||
|
||||
private void verifyUpdate(Long... idsUpdated) {
|
||||
HashSet<Long> updates = new HashSet<>(Arrays.asList(idsUpdated));
|
||||
IconCache noOpIconCache = mock(IconCache.class);
|
||||
for (ItemInfo info : bgDataModel.itemsIdMap) {
|
||||
if (updates.contains(info.id)) {
|
||||
assertEquals(NEW_LABEL_PREFIX + info.id, info.title);
|
||||
assertNotNull(((ShortcutInfo) info).getIcon(noOpIconCache));
|
||||
} else {
|
||||
assertNotSame(NEW_LABEL_PREFIX + info.id, info.title);
|
||||
assertNull(((ShortcutInfo) info).getIcon(noOpIconCache));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package com.android.launcher3.model;
|
||||
|
||||
import com.android.launcher3.ItemInfo;
|
||||
import com.android.launcher3.LauncherAppWidgetInfo;
|
||||
import com.android.launcher3.ShortcutInfo;
|
||||
import com.android.launcher3.compat.PackageInstallerCompat;
|
||||
import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
/**
|
||||
* Tests for {@link PackageInstallStateChangedTask}
|
||||
*/
|
||||
public class PackageInstallStateChangedTaskTest extends BaseModelUpdateTaskTestCase {
|
||||
|
||||
@Override
|
||||
protected void setUp() throws Exception {
|
||||
super.setUp();
|
||||
initializeData("package_install_state_change_task_data");
|
||||
}
|
||||
|
||||
private PackageInstallStateChangedTask newTask(String pkg, int progress) {
|
||||
PackageInstallInfo installInfo = new PackageInstallInfo(pkg);
|
||||
installInfo.progress = progress;
|
||||
installInfo.state = PackageInstallerCompat.STATUS_INSTALLING;
|
||||
return new PackageInstallStateChangedTask(installInfo);
|
||||
}
|
||||
|
||||
public void testSessionUpdate_ignore_installed() throws Exception {
|
||||
executeTaskForTest(newTask("app1", 30));
|
||||
|
||||
// No shortcuts were updated
|
||||
verifyProgressUpdate(0);
|
||||
}
|
||||
|
||||
public void testSessionUpdate_shortcuts_updated() throws Exception {
|
||||
executeTaskForTest(newTask("app3", 30));
|
||||
|
||||
verifyProgressUpdate(30, 5L, 6L, 7L);
|
||||
}
|
||||
|
||||
public void testSessionUpdate_widgets_updated() throws Exception {
|
||||
executeTaskForTest(newTask("app4", 30));
|
||||
|
||||
verifyProgressUpdate(30, 8L, 9L);
|
||||
}
|
||||
|
||||
private void verifyProgressUpdate(int progress, Long... idsUpdated) {
|
||||
HashSet<Long> updates = new HashSet<>(Arrays.asList(idsUpdated));
|
||||
for (ItemInfo info : bgDataModel.itemsIdMap) {
|
||||
if (info instanceof ShortcutInfo) {
|
||||
assertEquals(updates.contains(info.id) ? progress: 0,
|
||||
((ShortcutInfo) info).getInstallProgress());
|
||||
} else {
|
||||
assertEquals(updates.contains(info.id) ? progress: -1,
|
||||
((LauncherAppWidgetInfo) info).installProgress);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue