diff options
Diffstat (limited to 'src/com/android')
32 files changed, 1519 insertions, 1196 deletions
diff --git a/src/com/android/launcher3/AllAppsList.java b/src/com/android/launcher3/AllAppsList.java index 0e465a41e..b13c20b20 100644 --- a/src/com/android/launcher3/AllAppsList.java +++ b/src/com/android/launcher3/AllAppsList.java @@ -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; } } diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 7d693ec2b..dbb797dc5 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -128,7 +128,9 @@ public class BubbleTextView extends TextView setCompoundDrawablePadding(grid.allAppsIconDrawablePaddingPx); defaultIconSize = grid.allAppsIconSizePx; } else if (display == DISPLAY_FOLDER) { + setTextSize(TypedValue.COMPLEX_UNIT_PX, grid.folderChildTextSizePx); setCompoundDrawablePadding(grid.folderChildDrawablePaddingPx); + defaultIconSize = grid.folderChildIconSizePx; } mCenterVertically = a.getBoolean(R.styleable.BubbleTextView_centerVertically, false); @@ -189,9 +191,7 @@ public class BubbleTextView extends TextView private void applyIconAndLabel(Bitmap icon, ItemInfo info) { FastBitmapDrawable iconDrawable = mLauncher.createIconDrawable(icon); - if (info.isDisabled()) { - iconDrawable.setState(FastBitmapDrawable.State.DISABLED); - } + iconDrawable.setIsDisabled(info.isDisabled()); setIcon(iconDrawable); setText(info.title); if (info.contentDescription != null) { @@ -260,10 +260,7 @@ public class BubbleTextView extends TextView private void updateIconState() { if (mIcon instanceof FastBitmapDrawable) { FastBitmapDrawable d = (FastBitmapDrawable) mIcon; - if (getTag() instanceof ItemInfo - && ((ItemInfo) getTag()).isDisabled()) { - d.animateState(FastBitmapDrawable.State.DISABLED); - } else if (isPressed() || mStayPressed) { + if (isPressed() || mStayPressed) { d.animateState(FastBitmapDrawable.State.PRESSED); } else { d.animateState(FastBitmapDrawable.State.NORMAL); diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 51e174ff8..cc21920b0 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -96,8 +96,14 @@ public class DeviceProfile { public int folderBackgroundOffset; public int folderIconSizePx; public int folderIconPreviewPadding; + + // Folder cell public int folderCellWidthPx; public int folderCellHeightPx; + + // Folder child + public int folderChildIconSizePx; + public int folderChildTextSizePx; public int folderChildDrawablePaddingPx; // Hotseat @@ -228,19 +234,17 @@ public class DeviceProfile { } private void updateAvailableDimensions(DisplayMetrics dm, Resources res) { - // Check to see if the icons fit in the new available height. If not, then we need to - // shrink the icon size. - float scale = 1f; - int drawablePadding = iconDrawablePaddingOriginalPx; - updateIconSize(1f, drawablePadding, res, dm); - float usedHeight = (cellHeightPx * inv.numRows); + updateIconSize(1f, iconDrawablePaddingOriginalPx, res, dm); + // Check to see if the icons fit within the available height. If not, then scale down. + float usedHeight = (cellHeightPx * inv.numRows); int maxHeight = (availableHeightPx - getTotalWorkspacePadding().y); if (usedHeight > maxHeight) { - scale = maxHeight / usedHeight; - drawablePadding = 0; + float scale = maxHeight / usedHeight; + updateIconSize(scale, 0, res, dm); } - updateIconSize(scale, drawablePadding, res, dm); + + updateAvailableFolderCellDimensions(dm, res); } private void updateIconSize(float scale, int drawablePadding, Resources res, @@ -276,29 +280,45 @@ public class DeviceProfile { res.getInteger(R.integer.config_workspaceSpringLoadShrinkPercentage) / 100.0f; } - // Folder cell - int cellPaddingX = res.getDimensionPixelSize(R.dimen.folder_cell_x_padding); - int cellPaddingY = res.getDimensionPixelSize(R.dimen.folder_cell_y_padding); - final int folderChildTextSize = - Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_child_text_size)); + // Folder icon + folderBackgroundOffset = -edgeMarginPx; + folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset; + folderIconPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding); + } - final int folderBottomPanelSize = - res.getDimensionPixelSize(R.dimen.folder_label_padding_top) - + res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom) + private void updateAvailableFolderCellDimensions(DisplayMetrics dm, Resources res) { + int folderBottomPanelSize = res.getDimensionPixelSize(R.dimen.folder_label_padding_top) + + res.getDimensionPixelSize(R.dimen.folder_label_padding_bottom) + Utilities.calculateTextHeight(res.getDimension(R.dimen.folder_label_text_size)); + updateFolderCellSize(1f, dm, res, folderBottomPanelSize); + + // Check to see if the icons fit within the available height. If not, then scale down. + float usedHeight = (folderCellHeightPx * inv.numFolderRows) + folderBottomPanelSize; + int maxHeight = availableHeightPx - getTotalWorkspacePadding().y - (2 * edgeMarginPx); + if (usedHeight > maxHeight) { + float scale = maxHeight / usedHeight; + updateFolderCellSize(scale, dm, res, folderBottomPanelSize); + } + } + + private void updateFolderCellSize(float scale, DisplayMetrics dm, Resources res, + int folderBottomPanelSize) { + folderChildIconSizePx = (int) (Utilities.pxFromDp(inv.iconSize, dm) * scale); + folderChildTextSizePx = + (int) (res.getDimensionPixelSize(R.dimen.folder_child_text_size) * scale); + + int textHeight = Utilities.calculateTextHeight(folderChildTextSizePx); + int cellPaddingX = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_x_padding) * scale); + int cellPaddingY = (int) (res.getDimensionPixelSize(R.dimen.folder_cell_y_padding) * scale); + // Don't let the folder get too close to the edges of the screen. - folderCellWidthPx = Math.min(iconSizePx + 2 * cellPaddingX, + folderCellWidthPx = Math.min(folderChildIconSizePx + 2 * cellPaddingX, (availableWidthPx - 4 * edgeMarginPx) / inv.numFolderColumns); - folderCellHeightPx = Math.min(iconSizePx + 3 * cellPaddingY + folderChildTextSize, + folderCellHeightPx = Math.min(folderChildIconSizePx + 2 * cellPaddingY + textHeight, (availableHeightPx - 4 * edgeMarginPx - folderBottomPanelSize) / inv.numFolderRows); folderChildDrawablePaddingPx = Math.max(0, - (folderCellHeightPx - iconSizePx - folderChildTextSize) / 3); - - // Folder icon - folderBackgroundOffset = -edgeMarginPx; - folderIconSizePx = iconSizePx + 2 * -folderBackgroundOffset; - folderIconPreviewPadding = res.getDimensionPixelSize(R.dimen.folder_preview_padding); + (folderCellHeightPx - folderChildIconSizePx - textHeight) / 3); } public void updateInsets(Rect insets) { @@ -592,10 +612,10 @@ public class DeviceProfile { return new int[]{ padding, padding }; } - public boolean shouldIgnoreLongPressToOverview(float touchX, float edgeThreshold) { + public boolean shouldIgnoreLongPressToOverview(float touchX) { boolean inMultiWindowMode = this != inv.landscapeProfile && this != inv.portraitProfile; - boolean touchedLhsEdge = mInsets.left == 0 && touchX < edgeThreshold; - boolean touchedRhsEdge = mInsets.right == 0 && touchX > (widthPx - edgeThreshold); + boolean touchedLhsEdge = mInsets.left == 0 && touchX < edgeMarginPx; + boolean touchedRhsEdge = mInsets.right == 0 && touchX > (widthPx - edgeMarginPx); return !inMultiWindowMode && (touchedLhsEdge || touchedRhsEdge); } } diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java index 38700805f..7eaae5a44 100644 --- a/src/com/android/launcher3/FastBitmapDrawable.java +++ b/src/com/android/launcher3/FastBitmapDrawable.java @@ -34,6 +34,8 @@ import android.util.SparseArray; import android.view.animation.DecelerateInterpolator; public class FastBitmapDrawable extends Drawable { + private static final float DISABLED_DESATURATION = 1f; + private static final float DISABLED_BRIGHTNESS = 0.5f; /** * The possible states that a FastBitmapDrawable can be in. @@ -43,8 +45,7 @@ public class FastBitmapDrawable extends Drawable { NORMAL (0f, 0f, 1f, new DecelerateInterpolator()), PRESSED (0f, 100f / 255f, 1f, CLICK_FEEDBACK_INTERPOLATOR), FAST_SCROLL_HIGHLIGHTED (0f, 0f, 1.15f, new DecelerateInterpolator()), - FAST_SCROLL_UNHIGHLIGHTED (0f, 0f, 1f, new DecelerateInterpolator()), - DISABLED (1f, 0.5f, 1f, new DecelerateInterpolator()); + FAST_SCROLL_UNHIGHLIGHTED (0f, 0f, 1f, new DecelerateInterpolator()); public final float desaturation; public final float brightness; @@ -96,6 +97,7 @@ public class FastBitmapDrawable extends Drawable { private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG); private final Bitmap mBitmap; private State mState = State.NORMAL; + private boolean mIsDisabled; // The saturation and brightness are values that are mapped to REDUCED_FILTER_VALUE_SPACE and // as a result, can be used to compose the key for the cached ColorMatrixColorFilters @@ -177,13 +179,14 @@ public class FastBitmapDrawable extends Drawable { if (mState != newState) { mState = newState; + float desaturation = mIsDisabled ? DISABLED_DESATURATION : newState.desaturation; + float brightness = mIsDisabled ? DISABLED_BRIGHTNESS: newState.brightness; + mPropertyAnimator = cancelAnimator(mPropertyAnimator); mPropertyAnimator = new AnimatorSet(); mPropertyAnimator.playTogether( - ObjectAnimator - .ofFloat(this, "desaturation", newState.desaturation), - ObjectAnimator - .ofFloat(this, "brightness", newState.brightness)); + ObjectAnimator.ofFloat(this, "desaturation", desaturation), + ObjectAnimator.ofFloat(this, "brightness", brightness)); mPropertyAnimator.setInterpolator(newState.interpolator); mPropertyAnimator.setDuration(getDurationForStateChange(prevState, newState)); mPropertyAnimator.setStartDelay(getStartDelayForStateChange(prevState, newState)); @@ -204,13 +207,17 @@ public class FastBitmapDrawable extends Drawable { mPropertyAnimator = cancelAnimator(mPropertyAnimator); - setDesaturation(newState.desaturation); - setBrightness(newState.brightness); + invalidateDesaturationAndBrightness(); return true; } return false; } + private void invalidateDesaturationAndBrightness() { + setDesaturation(mIsDisabled ? DISABLED_DESATURATION : mState.desaturation); + setBrightness(mIsDisabled ? DISABLED_BRIGHTNESS: mState.brightness); + } + /** * Returns the current state. */ @@ -218,6 +225,13 @@ public class FastBitmapDrawable extends Drawable { return mState; } + public void setIsDisabled(boolean isDisabled) { + if (mIsDisabled != isDisabled) { + mIsDisabled = isDisabled; + invalidateDesaturationAndBrightness(); + } + } + /** * Returns the duration for the state change animation. */ diff --git a/src/com/android/launcher3/IconCache.java b/src/com/android/launcher3/IconCache.java index 661f99b9e..04d0c8c91 100644 --- a/src/com/android/launcher3/IconCache.java +++ b/src/com/android/launcher3/IconCache.java @@ -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); diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java index bd20e324b..056facba5 100644 --- a/src/com/android/launcher3/InstallShortcutReceiver.java +++ b/src/com/android/launcher3/InstallShortcutReceiver.java @@ -22,7 +22,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.text.TextUtils; @@ -241,7 +240,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); } } } @@ -434,22 +433,16 @@ public class InstallShortcutReceiver extends BroadcastReceiver { // Already an activity target return original; } - if (!Utilities.isLauncherAppTarget(original.launchIntent) - || !original.user.equals(UserHandleCompat.myUserHandle())) { - // We can only convert shortcuts which point to a main activity in the current user. + if (!Utilities.isLauncherAppTarget(original.launchIntent)) { return original; } - PackageManager pm = original.mContext.getPackageManager(); - ResolveInfo info = pm.resolveActivity(original.launchIntent, 0); - + LauncherActivityInfoCompat info = LauncherAppsCompat.getInstance(original.mContext) + .resolveActivity(original.launchIntent, original.user); if (info == null) { return original; } - // Ignore any conflicts in the label name, as that can change based on locale. - LauncherActivityInfoCompat launcherInfo = LauncherActivityInfoCompat - .fromResolveInfo(info, original.mContext); - return new PendingInstallShortcutInfo(launcherInfo, original.mContext); + return new PendingInstallShortcutInfo(info, original.mContext); } } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index f372d9518..0a5d44e20 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -86,6 +86,7 @@ import com.android.launcher3.accessibility.LauncherAccessibilityDelegate; import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.allapps.AllAppsTransitionController; import com.android.launcher3.allapps.DefaultAppSearchController; +import com.android.launcher3.anim.AnimationLayerSet; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.LauncherActivityInfoCompat; import com.android.launcher3.compat.LauncherAppsCompat; @@ -315,7 +316,6 @@ public class Launcher extends Activity private UserEventDispatcher mUserEventDispatcher; private float mLastDispatchTouchEventX = 0.0f; - private float mEdgeOfScreenThresholdPx = 0.0f; public ViewGroupFocusHelper mFocusHandler; private boolean mRotationEnabled = false; @@ -392,10 +392,7 @@ public class Launcher extends Activity // LauncherModel load. mPaused = false; - setContentView(R.layout.launcher); - - mEdgeOfScreenThresholdPx = getResources() - .getDimensionPixelSize(R.dimen.edge_of_screen_threshold); + mLauncherView = getLayoutInflater().inflate(R.layout.launcher, null); setupViews(); mDeviceProfile.layout(this, false /* notifyListeners */); @@ -447,12 +444,18 @@ public class Launcher extends Activity // we want the screen to auto-rotate based on the current orientation setOrientation(); + setContentView(mLauncherView); if (mLauncherCallbacks != null) { mLauncherCallbacks.onCreate(savedInstanceState); } } @Override + public View findViewById(int id) { + return mLauncherView.findViewById(id); + } + + @Override public void onExtractedColorsChanged() { loadExtractedColorsAndColorItems(); } @@ -1271,7 +1274,6 @@ public class Launcher extends Activity * Finds all the views we need and configure them properly. */ private void setupViews() { - mLauncherView = findViewById(R.id.launcher); mDragLayer = (DragLayer) findViewById(R.id.drag_layer); mFocusHandler = mDragLayer.getFocusIndicatorHelper(); mWorkspace = (Workspace) mDragLayer.findViewById(R.id.workspace); @@ -2717,8 +2719,8 @@ public class Launcher extends Activity } - boolean ignoreLongPressToOverview = mDeviceProfile.shouldIgnoreLongPressToOverview( - mLastDispatchTouchEventX, mEdgeOfScreenThresholdPx); + boolean ignoreLongPressToOverview = + mDeviceProfile.shouldIgnoreLongPressToOverview(mLastDispatchTouchEventX); if (v instanceof Workspace) { if (!mWorkspace.isInOverviewMode()) { @@ -2970,7 +2972,7 @@ public class Launcher extends Activity * new state. */ public Animator startWorkspaceStateChangeAnimation(Workspace.State toState, - boolean animated, HashMap<View, Integer> layerViews) { + boolean animated, AnimationLayerSet layerViews) { Workspace.State fromState = mWorkspace.getState(); Animator anim = mWorkspace.setStateWithAnimation(toState, animated, layerViews); updateInteraction(fromState, toState); diff --git a/src/com/android/launcher3/LauncherAppWidgetHostView.java b/src/com/android/launcher3/LauncherAppWidgetHostView.java index c18408607..a4ea44916 100644 --- a/src/com/android/launcher3/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/LauncherAppWidgetHostView.java @@ -22,6 +22,7 @@ import android.content.Context; import android.graphics.Rect; import android.os.Handler; import android.os.SystemClock; +import android.util.Log; import android.util.SparseBooleanArray; import android.view.KeyEvent; import android.view.LayoutInflater; @@ -36,13 +37,17 @@ import android.widget.RemoteViews; import com.android.launcher3.dragndrop.DragLayer.TouchCompleteListener; +import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.concurrent.Executor; /** * {@inheritDoc} */ public class LauncherAppWidgetHostView extends AppWidgetHostView implements TouchCompleteListener { + private static final String TAG = "LauncherWidgetHostView"; + // Related to the auto-advancing of widgets private static final long ADVANCE_INTERVAL = 20000; private static final long ADVANCE_STAGGER = 250; @@ -75,6 +80,16 @@ public class LauncherAppWidgetHostView extends AppWidgetHostView implements Touc mInflater = LayoutInflater.from(context); setAccessibilityDelegate(Launcher.getLauncher(context).getAccessibilityDelegate()); setBackgroundResource(R.drawable.widget_internal_focus_bg); + + if (Utilities.isAtLeastO()) { + try { + Method asyncMethod = AppWidgetHostView.class + .getMethod("setAsyncExecutor", Executor.class); + asyncMethod.invoke(this, Utilities.THREAD_POOL_EXECUTOR); + } catch (Exception e) { + Log.e(TAG, "Unable to set async executor", e); + } + } } @Override diff --git a/src/com/android/launcher3/LauncherAppWidgetInfo.java b/src/com/android/launcher3/LauncherAppWidgetInfo.java index 66d895726..78f5b8edb 100644 --- a/src/com/android/launcher3/LauncherAppWidgetInfo.java +++ b/src/com/android/launcher3/LauncherAppWidgetInfo.java @@ -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; } diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index 955f51f5f..c70a47595 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -27,7 +27,6 @@ import android.content.Intent; import android.content.Intent.ShortcutIconResource; import android.content.IntentFilter; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.database.Cursor; import android.graphics.Bitmap; import android.net.Uri; @@ -43,7 +42,6 @@ import android.text.TextUtils; import android.util.Log; import android.util.LongSparseArray; import android.util.MutableInt; -import android.util.Pair; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.LauncherActivityInfoCompat; @@ -59,11 +57,18 @@ 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.ExtendedModelTask; import com.android.launcher3.model.BgDataModel; +import com.android.launcher3.model.CacheDataUpdatedTask; import com.android.launcher3.model.GridSizeMigrationTask; import com.android.launcher3.model.PackageItemInfo; import com.android.launcher3.model.SdCardAvailableReceiver; import com.android.launcher3.model.WidgetItem; +import com.android.launcher3.model.PackageInstallStateChangedTask; +import com.android.launcher3.model.PackageUpdatedTask; +import com.android.launcher3.model.ShortcutsChangedTask; +import com.android.launcher3.model.UserLockStateChangedTask; import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.provider.ImportDataTask; import com.android.launcher3.provider.LauncherDbUtils; @@ -72,7 +77,6 @@ import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.shortcuts.ShortcutKey; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.CursorIconInfo; -import com.android.launcher3.util.FlagOp; import com.android.launcher3.util.GridOccupancy; import com.android.launcher3.util.ItemInfoMatcher; import com.android.launcher3.util.LongArrayMap; @@ -87,7 +91,6 @@ import java.lang.ref.WeakReference; import java.net.URISyntaxException; import java.security.InvalidParameterException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -168,8 +171,8 @@ public class LauncherModel extends BroadcastReceiver // </ only access in worker thread > - private IconCache mIconCache; - private DeepShortcutManager mDeepShortcutManager; + private final IconCache mIconCache; + private final DeepShortcutManager mDeepShortcutManager; private final LauncherAppsCompat mLauncherApps; private final UserManagerCompat mUserManager; @@ -241,286 +244,26 @@ public class LauncherModel extends BroadcastReceiver } } - public void setPackageState(final PackageInstallInfo installInfo) { - Runnable updateRunnable = new Runnable() { - - @Override - public void run() { - synchronized (sBgDataModel) { - final HashSet<ItemInfo> updates = new HashSet<>(); - - if (installInfo.state == PackageInstallerCompat.STATUS_INSTALLED) { - // Ignore install success events as they are handled by Package add events. - return; - } - - for (ItemInfo info : sBgDataModel.itemsIdMap) { - if (info instanceof ShortcutInfo) { - ShortcutInfo si = (ShortcutInfo) info; - ComponentName cn = si.getTargetComponent(); - if (si.isPromise() && (cn != null) - && installInfo.packageName.equals(cn.getPackageName())) { - si.setInstallProgress(installInfo.progress); - - if (installInfo.state == PackageInstallerCompat.STATUS_FAILED) { - // Mark this info as broken. - si.status &= ~ShortcutInfo.FLAG_INSTALL_SESSION_ACTIVE; - } - updates.add(si); - } - } - } - - for (LauncherAppWidgetInfo widget : sBgDataModel.appWidgets) { - if (widget.providerName.getPackageName().equals(installInfo.packageName)) { - widget.installProgress = installInfo.progress; - updates.add(widget); - } - } - - if (!updates.isEmpty()) { - // Push changes to the callback. - Runnable r = new Runnable() { - public void run() { - Callbacks callbacks = getCallback(); - if (callbacks != null) { - callbacks.bindRestoreItemsChange(updates); - } - } - }; - mHandler.post(r); - } - } - } - }; - runOnWorkerThread(updateRunnable); + public void setPackageState(PackageInstallInfo installInfo) { + enqueueModelUpdateTask(new PackageInstallStateChangedTask(installInfo)); } /** * Updates the icons and label of all pending icons for the provided package name. */ public void updateSessionDisplayInfo(final String packageName) { - Runnable updateRunnable = new Runnable() { - - @Override - public void run() { - synchronized (sBgDataModel) { - ArrayList<ShortcutInfo> updates = new ArrayList<>(); - UserHandleCompat user = UserHandleCompat.myUserHandle(); - - for (ItemInfo info : sBgDataModel.itemsIdMap) { - if (info instanceof ShortcutInfo) { - ShortcutInfo si = (ShortcutInfo) info; - ComponentName cn = si.getTargetComponent(); - if (si.isPromise() && (cn != null) - && packageName.equals(cn.getPackageName())) { - si.updateIcon(mIconCache); - updates.add(si); - } - } - } - - bindUpdatedShortcuts(updates, user); - } - } - }; - runOnWorkerThread(updateRunnable); - } - - public void addAppsToAllApps(final Context ctx, final ArrayList<AppInfo> allAppsApps) { - final Callbacks callbacks = getCallback(); - - if (allAppsApps == null) { - throw new RuntimeException("allAppsApps must not be null"); - } - if (allAppsApps.isEmpty()) { - return; - } - - // Process the newly added applications and add them to the database first - Runnable r = new Runnable() { - public void run() { - runOnMainThread(new Runnable() { - public void run() { - Callbacks cb = getCallback(); - if (callbacks == cb && cb != null) { - callbacks.bindAppsAdded(null, null, null, allAppsApps); - } - } - }); - } - }; - runOnWorkerThread(r); - } - - private static boolean findNextAvailableIconSpaceInScreen(ArrayList<ItemInfo> occupiedPos, - int[] xy, int spanX, int spanY) { - LauncherAppState app = LauncherAppState.getInstance(); - 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); - } - - /** - * Find a position on the screen for the given size or adds a new screen. - * @return screenId and the coordinates for the item. - */ - @Thunk Pair<Long, int[]> findSpaceForItem( - Context context, - 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. - assertWorkspaceLoaded(); - synchronized (sBgDataModel) { - for (ItemInfo info : sBgDataModel.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( - 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( - 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(context.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( - screenItems.get(screenId), cordinates, spanX, spanY)) { - throw new RuntimeException("Can't find space to add the item"); - } - } - return Pair.create(screenId, cordinates); + HashSet<String> packages = new HashSet<>(); + packages.add(packageName); + enqueueModelUpdateTask(new CacheDataUpdatedTask( + CacheDataUpdatedTask.OP_SESSION_UPDATE, UserHandleCompat.myUserHandle(), packages)); } /** * Adds the provided items to the workspace. */ - public void addAndBindAddedWorkspaceItems(final Context context, + public void addAndBindAddedWorkspaceItems( final ArrayList<? extends ItemInfo> workspaceApps) { - final Callbacks callbacks = getCallback(); - if (workspaceApps.isEmpty()) { - return; - } - // Process the newly added applications and add them to the database first - Runnable r = new Runnable() { - public void run() { - 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 = loadWorkspaceScreensDb(context); - synchronized(sBgDataModel) { - for (ItemInfo item : workspaceApps) { - if (item instanceof ShortcutInfo) { - // Short-circuit this logic if the icon exists somewhere on the workspace - if (shortcutExists(context, item.getIntent(), item.user)) { - continue; - } - } - - // Find appropriate space for the item. - Pair<Long, int[]> coords = findSpaceForItem(context, - 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, - LauncherSettings.Favorites.CONTAINER_DESKTOP, - screenId, cordinates[0], cordinates[1]); - // Save the ShortcutInfo for binding in the workspace - addedShortcutsFinal.add(itemInfo); - } - } - - // Update the workspace screens - updateWorkspaceScreenOrder(context, workspaceScreens); - - if (!addedShortcutsFinal.isEmpty()) { - runOnMainThread(new Runnable() { - public void run() { - Callbacks cb = getCallback(); - if (callbacks == cb && cb != null) { - 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); - } - } - }); - } - } - }; - runOnWorkerThread(r); + enqueueModelUpdateTask(new AddWorkspaceItemsTask(workspaceApps)); } /** @@ -784,60 +527,6 @@ public class LauncherModel extends BroadcastReceiver updateItemInDatabaseHelper(context, values, item, "updateItemInDatabase"); } - private void assertWorkspaceLoaded() { - if (ProviderConfig.IS_DOGFOOD_BUILD) { - synchronized (mLock) { - if (!mHasLoaderCompletedOnce || - (mLoaderTask != null && mLoaderTask.mIsLoadingAndBindingWorkspace)) { - throw new RuntimeException("Trying to add shortcut while loader is running"); - } - } - } - } - - /** - * 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. - */ - @Thunk boolean shortcutExists(Context context, Intent intent, UserHandleCompat user) { - assertWorkspaceLoaded(); - 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 (sBgDataModel) { - for (ItemInfo item : sBgDataModel.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; - } - /** * Add an item to the database in a specified container. Sets the container, screen, cellX and * cellY fields of the item. Also assigns an ID to the item. @@ -899,7 +588,8 @@ public class LauncherModel extends BroadcastReceiver /** * Removes the specified items from the database */ - static void deleteItemsFromDatabase(Context context, final Iterable<? extends ItemInfo> items) { + public static void deleteItemsFromDatabase(Context context, + final Iterable<? extends ItemInfo> items) { final ContentResolver cr = context.getContentResolver(); Runnable r = new Runnable() { public void run() { @@ -918,7 +608,7 @@ public class LauncherModel extends BroadcastReceiver * Update the order of the workspace screens in the database. The array list contains * a list of screen ids in the order that they should appear. */ - public void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) { + public static void updateWorkspaceScreenOrder(Context context, final ArrayList<Long> screens) { final ArrayList<Long> screensCopy = new ArrayList<Long>(screens); final ContentResolver cr = context.getContentResolver(); final Uri uri = LauncherSettings.WorkspaceScreens.CONTENT_URI; @@ -997,8 +687,7 @@ public class LauncherModel extends BroadcastReceiver @Override public void onPackageChanged(String packageName, UserHandleCompat user) { int op = PackageUpdatedTask.OP_UPDATE; - enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName }, - user)); + enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName)); } @Override @@ -1008,56 +697,52 @@ public class LauncherModel extends BroadcastReceiver public void onPackagesRemoved(UserHandleCompat user, String... packages) { int op = PackageUpdatedTask.OP_REMOVE; - enqueueItemUpdatedTask(new PackageUpdatedTask(op, packages, user)); + enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packages)); } @Override public void onPackageAdded(String packageName, UserHandleCompat user) { int op = PackageUpdatedTask.OP_ADD; - enqueueItemUpdatedTask(new PackageUpdatedTask(op, new String[] { packageName }, - user)); + enqueueModelUpdateTask(new PackageUpdatedTask(op, user, packageName)); } @Override public void onPackagesAvailable(String[] packageNames, UserHandleCompat user, boolean replacing) { - enqueueItemUpdatedTask( - new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, packageNames, user)); + enqueueModelUpdateTask( + new PackageUpdatedTask(PackageUpdatedTask.OP_UPDATE, user, packageNames)); } @Override public void onPackagesUnavailable(String[] packageNames, UserHandleCompat user, boolean replacing) { if (!replacing) { - enqueueItemUpdatedTask(new PackageUpdatedTask( - PackageUpdatedTask.OP_UNAVAILABLE, packageNames, - user)); + enqueueModelUpdateTask(new PackageUpdatedTask( + PackageUpdatedTask.OP_UNAVAILABLE, user, packageNames)); } } @Override public void onPackagesSuspended(String[] packageNames, UserHandleCompat user) { - enqueueItemUpdatedTask(new PackageUpdatedTask( - PackageUpdatedTask.OP_SUSPEND, packageNames, - user)); + enqueueModelUpdateTask(new PackageUpdatedTask( + PackageUpdatedTask.OP_SUSPEND, user, packageNames)); } @Override public void onPackagesUnsuspended(String[] packageNames, UserHandleCompat user) { - enqueueItemUpdatedTask(new PackageUpdatedTask( - PackageUpdatedTask.OP_UNSUSPEND, packageNames, - user)); + enqueueModelUpdateTask(new PackageUpdatedTask( + PackageUpdatedTask.OP_UNSUSPEND, user, packageNames)); } @Override public void onShortcutsChanged(String packageName, List<ShortcutInfoCompat> shortcuts, UserHandleCompat user) { - enqueueItemUpdatedTask(new ShortcutsChangedTask(packageName, shortcuts, user, true)); + enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, true)); } public void updatePinnedShortcuts(String packageName, List<ShortcutInfoCompat> shortcuts, UserHandleCompat user) { - enqueueItemUpdatedTask(new ShortcutsChangedTask(packageName, shortcuts, user, false)); + enqueueModelUpdateTask(new ShortcutsChangedTask(packageName, shortcuts, user, false)); } /** @@ -1083,16 +768,15 @@ public class LauncherModel extends BroadcastReceiver if (user != null) { if (Intent.ACTION_MANAGED_PROFILE_AVAILABLE.equals(action) || Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action)) { - enqueueItemUpdatedTask(new PackageUpdatedTask( - PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, - new String[0], user)); + enqueueModelUpdateTask(new PackageUpdatedTask( + PackageUpdatedTask.OP_USER_AVAILABILITY_CHANGE, user)); } // ACTION_MANAGED_PROFILE_UNAVAILABLE sends the profile back to locked mode, so // we need to run the state change task again. if (Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE.equals(action) || Intent.ACTION_MANAGED_PROFILE_UNLOCKED.equals(action)) { - enqueueItemUpdatedTask(new UserLockStateChangedTask(user)); + enqueueModelUpdateTask(new UserLockStateChangedTask(user)); } } } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(action)) { @@ -2702,397 +2386,68 @@ public class LauncherModel extends BroadcastReceiver * Called when the icons for packages have been updated in the icon cache. */ public void onPackageIconsUpdated(HashSet<String> updatedPackages, UserHandleCompat user) { - final Callbacks callbacks = getCallback(); - final ArrayList<AppInfo> updatedApps = new ArrayList<>(); - final ArrayList<ShortcutInfo> updatedShortcuts = new ArrayList<>(); - // If any package icon has changed (app was updated while launcher was dead), // update the corresponding shortcuts. - synchronized (sBgDataModel) { - for (ItemInfo info : sBgDataModel.itemsIdMap) { - if (info instanceof ShortcutInfo && user.equals(info.user) - && info.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { - ShortcutInfo si = (ShortcutInfo) info; - ComponentName cn = si.getTargetComponent(); - if (cn != null && updatedPackages.contains(cn.getPackageName())) { - si.updateIcon(mIconCache); - updatedShortcuts.add(si); - } - } - } - mBgAllAppsList.updateIconsAndLabels(updatedPackages, user, updatedApps); - } - - bindUpdatedShortcuts(updatedShortcuts, user); - - if (!updatedApps.isEmpty()) { - mHandler.post(new Runnable() { - - public void run() { - Callbacks cb = getCallback(); - if (cb != null && callbacks == cb) { - cb.bindAppsUpdated(updatedApps); - } - } - }); - } + enqueueModelUpdateTask(new CacheDataUpdatedTask( + CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages)); } - private void bindUpdatedShortcuts( - ArrayList<ShortcutInfo> updatedShortcuts, UserHandleCompat user) { - bindUpdatedShortcuts(updatedShortcuts, new ArrayList<ShortcutInfo>(), user); + void enqueueModelUpdateTask(BaseModelUpdateTask task) { + task.init(this); + runOnWorkerThread(task); } - private void bindUpdatedShortcuts( - final ArrayList<ShortcutInfo> updatedShortcuts, - final ArrayList<ShortcutInfo> removedShortcuts, - final UserHandleCompat user) { - if (!updatedShortcuts.isEmpty() || !removedShortcuts.isEmpty()) { - final Callbacks callbacks = getCallback(); - mHandler.post(new Runnable() { - - public void run() { - Callbacks cb = getCallback(); - if (cb != null && callbacks == cb) { - cb.bindShortcutsChanged(updatedShortcuts, removedShortcuts, user); - } - } - }); - } - } + /** + * A task to be executed on the current callbacks on the UI thread. + * If there is no current callbacks, the task is ignored. + */ + public interface CallbackTask { - void enqueueItemUpdatedTask(Runnable task) { - sWorker.post(task); + void execute(Callbacks callbacks); } - private class PackageUpdatedTask implements Runnable { - final int mOp; - final String[] mPackages; - final UserHandleCompat mUser; + /** + * A runnable which changes/updates the data model of the launcher based on certain events. + */ + public static abstract class BaseModelUpdateTask implements Runnable { - 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; // uninstlled - 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 LauncherModel mModel; + private DeferredHandler mUiHandler; - public PackageUpdatedTask(int op, String[] packages, UserHandleCompat user) { - mOp = op; - mPackages = packages; - mUser = user; + /* package private */ + void init(LauncherModel model) { + mModel = model; + mUiHandler = mModel.mHandler; } + @Override public void run() { - if (!mHasLoaderCompletedOnce) { + if (!mModel.mHasLoaderCompletedOnce) { // Loader has not yet run. return; } - final Context context = mApp.getContext(); - - 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_LOADERS) Log.d(TAG, "mAllAppsList.addPackage " + packages[i]); - mIconCache.updateIconsForPkg(packages[i], mUser); - mBgAllAppsList.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_LOADERS) Log.d(TAG, "mAllAppsList.updatePackage " + packages[i]); - mIconCache.updateIconsForPkg(packages[i], mUser); - mBgAllAppsList.updatePackage(context, packages[i], mUser); - mApp.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++) { - if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]); - mIconCache.removeIconsForPkg(packages[i], mUser); - } - // Fall through - } - case OP_UNAVAILABLE: - for (int i=0; i<N; i++) { - if (DEBUG_LOADERS) Log.d(TAG, "mAllAppsList.removePackage " + packages[i]); - mBgAllAppsList.removePackage(packages[i], mUser); - mApp.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_LOADERS) Log.d(TAG, "mAllAppsList.(un)suspend " + N); - mBgAllAppsList.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. - mBgAllAppsList.updateDisabledFlags(ItemInfoMatcher.ofUser(mUser), flagOp); - break; - } - - ArrayList<AppInfo> added = null; - ArrayList<AppInfo> modified = null; - final ArrayList<AppInfo> removedApps = new ArrayList<AppInfo>(); - - if (mBgAllAppsList.added.size() > 0) { - added = new ArrayList<>(mBgAllAppsList.added); - mBgAllAppsList.added.clear(); - } - if (mBgAllAppsList.modified.size() > 0) { - modified = new ArrayList<>(mBgAllAppsList.modified); - mBgAllAppsList.modified.clear(); - } - if (mBgAllAppsList.removed.size() > 0) { - removedApps.addAll(mBgAllAppsList.removed); - mBgAllAppsList.removed.clear(); - } - - final HashMap<ComponentName, AppInfo> addedOrUpdatedApps = new HashMap<>(); - - if (added != null) { - addAppsToAllApps(context, added); - for (AppInfo ai : added) { - addedOrUpdatedApps.put(ai.componentName, ai); - } - } - - if (modified != null) { - final Callbacks callbacks = getCallback(); - final ArrayList<AppInfo> modifiedFinal = modified; - for (AppInfo ai : modified) { - addedOrUpdatedApps.put(ai.componentName, ai); - } - - mHandler.post(new Runnable() { - public void run() { - Callbacks cb = getCallback(); - if (callbacks == cb && cb != null) { - 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 (sBgDataModel) { - for (ItemInfo info : sBgDataModel.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(mIconCache); - } - - if (appInfo != null && Intent.ACTION_MAIN.equals(si.intent.getAction()) - && si.itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION) { - si.updateIcon(mIconCache); - 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) { - 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); - updateItemInDatabase(context, widgetInfo); - } - } - } - } - - bindUpdatedShortcuts(updatedShortcuts, removedShortcuts, mUser); - if (!removedShortcuts.isEmpty()) { - deleteItemsFromDatabase(context, removedShortcuts); - } - - if (!widgets.isEmpty()) { - final Callbacks callbacks = getCallback(); - mHandler.post(new Runnable() { - public void run() { - Callbacks cb = getCallback(); - if (callbacks == cb && cb != null) { - callbacks.bindWidgetsRestored(widgets); - } - } - }); - } - } + execute(mModel.mApp, sBgDataModel, mModel.mBgAllAppsList); + } - 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); + /** + * Execute the actual task. Called on the worker thread. + */ + public abstract void execute( + LauncherAppState app, BgDataModel dataModel, AllAppsList apps); - // 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 - for (int i=0; i<N; i++) { - if (isPackageDisabled(context, packages[i], mUser)) { - removedPackages.add(packages[i]); + /** + * Schedules a {@param task} to be executed on the current callbacks. + */ + public final void scheduleCallbackTask(final CallbackTask task) { + final Callbacks callbacks = mModel.getCallback(); + mUiHandler.post(new Runnable() { + public void run() { + Callbacks cb = mModel.getCallback(); + if (callbacks == cb && cb != null) { + task.execute(callbacks); } } - - // Update removedComponents as some components can get removed during package update - for (AppInfo info : removedApps) { - removedComponents.add(info.componentName); - } - } - - if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) { - deleteItemsFromDatabase( - context, ItemInfoMatcher.ofPackages(removedPackages, mUser)); - deleteItemsFromDatabase( - context, ItemInfoMatcher.ofComponents(removedComponents, mUser)); - - // Remove any queued items from the install queue - InstallShortcutReceiver.removeFromInstallQueue(context, removedPackages, mUser); - - // Call the components-removed callback - final Callbacks callbacks = getCallback(); - mHandler.post(new Runnable() { - public void run() { - Callbacks cb = getCallback(); - if (callbacks == cb && cb != null) { - callbacks.bindWorkspaceComponentsRemoved( - removedPackages, removedComponents, mUser); - } - } - }); - } - - if (!removedApps.isEmpty()) { - // Remove corresponding apps from All-Apps - final Callbacks callbacks = getCallback(); - mHandler.post(new Runnable() { - public void run() { - Callbacks cb = getCallback(); - if (callbacks == cb && cb != null) { - 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)) { - final Callbacks callbacks = getCallback(); - mHandler.post(new Runnable() { - public void run() { - Callbacks cb = getCallback(); - if (callbacks == cb && cb != null) { - callbacks.notifyWidgetProvidersChanged(); - } - } - }); - } + }); } } @@ -3100,171 +2455,18 @@ public class LauncherModel extends BroadcastReceiver * Repopulates the shortcut info, possibly updating any icon already on the workspace. */ public void updateShortcutInfo(final ShortcutInfoCompat fullDetail, final ShortcutInfo info) { - enqueueItemUpdatedTask(new Runnable() { + enqueueModelUpdateTask(new ExtendedModelTask() { @Override - public void run() { - info.updateFromDeepShortcutInfo( - fullDetail, LauncherAppState.getInstance().getContext()); - ArrayList<ShortcutInfo> update = new ArrayList<ShortcutInfo>(); + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + info.updateFromDeepShortcutInfo(fullDetail, app.getContext()); + + ArrayList<ShortcutInfo> update = new ArrayList<>(); update.add(info); bindUpdatedShortcuts(update, fullDetail.getUserHandle()); } }); } - private class ShortcutsChangedTask implements Runnable { - 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 run() { - mDeepShortcutManager.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 : sBgDataModel.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 = mDeepShortcutManager.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()) { - deleteItemsFromDatabase(context, removedShortcutInfos); - } - - if (mUpdateIdMap) { - // Update the deep shortcut map if the list of ids has changed for an activity. - sBgDataModel.updateDeepShortcutMap(mPackageName, mUser, mShortcuts); - bindDeepShortcuts(); - } - } - } - - /** - * Task to handle changing of lock state of the user - */ - private class UserLockStateChangedTask implements Runnable { - - private final UserHandleCompat mUser; - - public UserLockStateChangedTask(UserHandleCompat user) { - mUser = user; - } - - @Override - public void run() { - boolean isUserUnlocked = mUserManager.isUserUnlocked(mUser); - Context context = mApp.getContext(); - - HashMap<ShortcutKey, ShortcutInfoCompat> pinnedShortcuts = new HashMap<>(); - if (isUserUnlocked) { - List<ShortcutInfoCompat> shortcuts = - mDeepShortcutManager.queryForPinnedShortcuts(null, mUser); - if (mDeepShortcutManager.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 : sBgDataModel.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()) { - deleteItemsFromDatabase(context, deletedShortcutInfos); - } - - // Remove shortcut id map for that user - Iterator<ComponentKey> keysIter = sBgDataModel.deepShortcutMap.keySet().iterator(); - while (keysIter.hasNext()) { - if (keysIter.next().user.equals(mUser)) { - keysIter.remove(); - } - } - - if (isUserUnlocked) { - sBgDataModel.updateDeepShortcutMap( - null, mUser, mDeepShortcutManager.queryForAllShortcuts(mUser)); - } - bindDeepShortcuts(); - } - } - private void bindWidgetsModel(final Callbacks callbacks) { final MultiHashMap<PackageItemInfo, WidgetItem> widgets = mBgWidgetsModel.getWidgetsMap().clone(); @@ -3296,12 +2498,6 @@ public class LauncherModel extends BroadcastReceiver }); } - @Thunk static boolean isPackageDisabled(Context context, String packageName, - UserHandleCompat user) { - final LauncherAppsCompat launcherApps = LauncherAppsCompat.getInstance(context); - return !launcherApps.isPackageEnabledForProfile(packageName, user); - } - /** * Make an ShortcutInfo object for a restored application or shortcut item that points * to a package that is not yet installed on the system. diff --git a/src/com/android/launcher3/LauncherStateTransitionAnimation.java b/src/com/android/launcher3/LauncherStateTransitionAnimation.java index 6d5f95159..7e842645b 100644 --- a/src/com/android/launcher3/LauncherStateTransitionAnimation.java +++ b/src/com/android/launcher3/LauncherStateTransitionAnimation.java @@ -34,13 +34,12 @@ import android.view.animation.DecelerateInterpolator; import com.android.launcher3.allapps.AllAppsContainerView; import com.android.launcher3.allapps.AllAppsTransitionController; +import com.android.launcher3.anim.AnimationLayerSet; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.util.CircleRevealOutlineProvider; import com.android.launcher3.util.Thunk; import com.android.launcher3.widget.WidgetsContainerView; -import java.util.HashMap; - /** * TODO: figure out what kind of tests we can write for this * @@ -119,9 +118,6 @@ public class LauncherStateTransitionAnimation { public static final String TAG = "LSTAnimation"; - // Flags to determine how to set the layers on views before the transition animation - public static final int BUILD_LAYER = 0; - public static final int BUILD_AND_SET_LAYER = 1; public static final int SINGLE_FRAME_DELAY = 16; @Thunk Launcher mLauncher; @@ -243,7 +239,7 @@ public class LauncherStateTransitionAnimation { final View fromView = mLauncher.getWorkspace(); - final HashMap<View, Integer> layerViews = new HashMap<>(); + final AnimationLayerSet layerViews = new AnimationLayerSet(); // If for some reason our views aren't initialized, don't animate boolean initialized = buttonView != null; @@ -319,14 +315,14 @@ public class LauncherStateTransitionAnimation { panelAlphaAndDrift.setInterpolator(new LogDecelerateInterpolator(100, 0)); // Play the animation - layerViews.put(revealView, BUILD_AND_SET_LAYER); + layerViews.addView(revealView); animation.play(panelAlphaAndDrift); // Setup the animation for the content view contentView.setVisibility(View.VISIBLE); contentView.setAlpha(0f); contentView.setTranslationY(revealViewToYDrift); - layerViews.put(contentView, BUILD_AND_SET_LAYER); + layerViews.addView(contentView); // Create the individual animators ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", @@ -365,13 +361,6 @@ public class LauncherStateTransitionAnimation { // Hide the reveal view revealView.setVisibility(View.INVISIBLE); - // Disable all necessary layers - for (View v : layerViews.keySet()) { - if (layerViews.get(v) == BUILD_AND_SET_LAYER) { - v.setLayerType(View.LAYER_TYPE_NONE, null); - } - } - // This can hold unnecessary references to views. cleanupAnimation(); pCb.onTransitionComplete(); @@ -393,16 +382,6 @@ public class LauncherStateTransitionAnimation { dispatchOnLauncherTransitionStart(fromView, animated, false); dispatchOnLauncherTransitionStart(toView, animated, false); - // Enable all necessary layers - for (View v : layerViews.keySet()) { - if (layerViews.get(v) == BUILD_AND_SET_LAYER) { - v.setLayerType(View.LAYER_TYPE_HARDWARE, null); - } - if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { - v.buildLayer(); - } - } - // Focus the new view toView.requestFocus(); @@ -411,25 +390,19 @@ public class LauncherStateTransitionAnimation { }; toView.bringToFront(); toView.setVisibility(View.VISIBLE); + + animation.addListener(layerViews); toView.post(startAnimRunnable); mCurrentAnimation = animation; } else if (animType == PULLUP) { // We are animating the content view alpha, so ensure we have a layer for it - layerViews.put(contentView, BUILD_AND_SET_LAYER); + layerViews.addView(contentView); animation.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { dispatchOnLauncherTransitionEnd(fromView, animated, false); dispatchOnLauncherTransitionEnd(toView, animated, false); - - // Disable all necessary layers - for (View v : layerViews.keySet()) { - if (layerViews.get(v) == BUILD_AND_SET_LAYER) { - v.setLayerType(View.LAYER_TYPE_NONE, null); - } - } - cleanupAnimation(); pCb.onTransitionComplete(); } @@ -450,21 +423,12 @@ public class LauncherStateTransitionAnimation { dispatchOnLauncherTransitionStart(fromView, animated, false); dispatchOnLauncherTransitionStart(toView, animated, false); - // Enable all necessary layers - for (View v : layerViews.keySet()) { - if (layerViews.get(v) == BUILD_AND_SET_LAYER) { - v.setLayerType(View.LAYER_TYPE_HARDWARE, null); - } - if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { - v.buildLayer(); - } - } - toView.requestFocus(); stateAnimation.start(); } }; mCurrentAnimation = animation; + mCurrentAnimation.addListener(layerViews); if (shouldPost) { toView.post(startAnimRunnable); } else { @@ -479,7 +443,7 @@ public class LauncherStateTransitionAnimation { private void playCommonTransitionAnimations( Workspace.State toWorkspaceState, View fromView, View toView, boolean animated, boolean initialized, AnimatorSet animation, - HashMap<View, Integer> layerViews) { + AnimationLayerSet layerViews) { // Create the workspace animation. // NOTE: this call apparently also sets the state for the workspace if !animated Animator workspaceAnim = mLauncher.startWorkspaceStateChangeAnimation(toWorkspaceState, @@ -594,10 +558,8 @@ public class LauncherStateTransitionAnimation { final Workspace.State toWorkspaceState, final boolean animated, final Runnable onCompleteRunnable) { final View fromWorkspace = mLauncher.getWorkspace(); - final HashMap<View, Integer> layerViews = new HashMap<>(); + final AnimationLayerSet layerViews = new AnimationLayerSet(); final AnimatorSet animation = LauncherAnimUtils.createAnimatorSet(); - final int revealDuration = mLauncher.getResources() - .getInteger(R.integer.config_overlayRevealTime); // Cancel the current animation cancelAnimation(); @@ -622,16 +584,6 @@ public class LauncherStateTransitionAnimation { return; dispatchOnLauncherTransitionStart(fromWorkspace, animated, true); - - // Enable all necessary layers - for (View v : layerViews.keySet()) { - if (layerViews.get(v) == BUILD_AND_SET_LAYER) { - v.setLayerType(View.LAYER_TYPE_HARDWARE, null); - } - if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { - v.buildLayer(); - } - } stateAnimation.start(); } }; @@ -645,17 +597,11 @@ public class LauncherStateTransitionAnimation { onCompleteRunnable.run(); } - // Disable all necessary layers - for (View v : layerViews.keySet()) { - if (layerViews.get(v) == BUILD_AND_SET_LAYER) { - v.setLayerType(View.LAYER_TYPE_NONE, null); - } - } - // This can hold unnecessary references to views. cleanupAnimation(); } }); + stateAnimation.addListener(layerViews); fromWorkspace.post(startAnimRunnable); mCurrentAnimation = animation; } else /* if (!animated) */ { @@ -692,7 +638,7 @@ public class LauncherStateTransitionAnimation { final View revealView = fromView.getRevealView(); final View contentView = fromView.getContentView(); - final HashMap<View, Integer> layerViews = new HashMap<>(); + final AnimationLayerSet layerViews = new AnimationLayerSet(); // If for some reason our views aren't initialized, don't animate boolean initialized = buttonView != null; @@ -735,7 +681,7 @@ public class LauncherStateTransitionAnimation { revealView.setVisibility(View.VISIBLE); revealView.setAlpha(1f); revealView.setTranslationY(0); - layerViews.put(revealView, BUILD_AND_SET_LAYER); + layerViews.addView(revealView); // Calculate the final animation values final float revealViewToXDrift; @@ -783,7 +729,7 @@ public class LauncherStateTransitionAnimation { } // Setup the animation for the content view - layerViews.put(contentView, BUILD_AND_SET_LAYER); + layerViews.addView(contentView); // Create the individual animators ObjectAnimator pageDrift = ObjectAnimator.ofFloat(contentView, "translationY", @@ -843,13 +789,6 @@ public class LauncherStateTransitionAnimation { onCompleteRunnable.run(); } - // Disable all necessary layers - for (View v : layerViews.keySet()) { - if (layerViews.get(v) == BUILD_AND_SET_LAYER) { - v.setLayerType(View.LAYER_TYPE_NONE, null); - } - } - // Reset page transforms if (contentView != null) { contentView.setTranslationX(0); @@ -874,24 +813,15 @@ public class LauncherStateTransitionAnimation { dispatchOnLauncherTransitionStart(fromView, animated, false); dispatchOnLauncherTransitionStart(toView, animated, false); - - // Enable all necessary layers - for (View v : layerViews.keySet()) { - if (layerViews.get(v) == BUILD_AND_SET_LAYER) { - v.setLayerType(View.LAYER_TYPE_HARDWARE, null); - } - if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { - v.buildLayer(); - } - } stateAnimation.start(); } }; mCurrentAnimation = animation; + mCurrentAnimation.addListener(layerViews); fromView.post(startAnimRunnable); } else if (animType == PULLUP) { // We are animating the content view alpha, so ensure we have a layer for it - layerViews.put(contentView, BUILD_AND_SET_LAYER); + layerViews.addView(contentView); animation.addListener(new AnimatorListenerAdapter() { boolean canceled = false; @@ -910,13 +840,6 @@ public class LauncherStateTransitionAnimation { onCompleteRunnable.run(); } - // Disable all necessary layers - for (View v : layerViews.keySet()) { - if (layerViews.get(v) == BUILD_AND_SET_LAYER) { - v.setLayerType(View.LAYER_TYPE_NONE, null); - } - } - cleanupAnimation(); pCb.onTransitionComplete(); } @@ -939,22 +862,13 @@ public class LauncherStateTransitionAnimation { dispatchOnLauncherTransitionStart(fromView, animated, false); dispatchOnLauncherTransitionStart(toView, animated, false); - // Enable all necessary layers - for (View v : layerViews.keySet()) { - if (layerViews.get(v) == BUILD_AND_SET_LAYER) { - v.setLayerType(View.LAYER_TYPE_HARDWARE, null); - } - if (Utilities.ATLEAST_LOLLIPOP && v.isAttachedToWindow()) { - v.buildLayer(); - } - } - // Focus the new view toView.requestFocus(); stateAnimation.start(); } }; mCurrentAnimation = animation; + mCurrentAnimation.addListener(layerViews); if (shouldPost) { fromView.post(startAnimRunnable); } else { diff --git a/src/com/android/launcher3/PendingAppWidgetHostView.java b/src/com/android/launcher3/PendingAppWidgetHostView.java index f01c7f259..bf397744b 100644 --- a/src/com/android/launcher3/PendingAppWidgetHostView.java +++ b/src/com/android/launcher3/PendingAppWidgetHostView.java @@ -134,7 +134,7 @@ public class PendingAppWidgetHostView extends LauncherAppWidgetHostView implemen // 3) Setup icon in the center and app icon in the top right corner. if (mDisabledForSafeMode) { FastBitmapDrawable disabledIcon = mLauncher.createIconDrawable(mIcon); - disabledIcon.setState(FastBitmapDrawable.State.DISABLED); + disabledIcon.setIsDisabled(true); mCenterDrawable = disabledIcon; mSettingIconDrawable = null; } else if (isReadyForClickSetup()) { diff --git a/src/com/android/launcher3/PreloadIconDrawable.java b/src/com/android/launcher3/PreloadIconDrawable.java index 8295b45e8..efc0eaca2 100644 --- a/src/com/android/launcher3/PreloadIconDrawable.java +++ b/src/com/android/launcher3/PreloadIconDrawable.java @@ -177,10 +177,8 @@ public class PreloadIconDrawable extends Drawable { // Set the paint color only when the level changes, so that the dominant color // is only calculated when needed. mPaint.setColor(getIndicatorColor()); - } - if (mIcon instanceof FastBitmapDrawable) { - ((FastBitmapDrawable) mIcon).setState(level <= 0 ? - FastBitmapDrawable.State.DISABLED : FastBitmapDrawable.State.NORMAL); + } else if (mIcon instanceof FastBitmapDrawable) { + ((FastBitmapDrawable) mIcon).setIsDisabled(true); } invalidateSelf(); diff --git a/src/com/android/launcher3/ShortcutInfo.java b/src/com/android/launcher3/ShortcutInfo.java index 9a9287234..fc087363a 100644 --- a/src/com/android/launcher3/ShortcutInfo.java +++ b/src/com/android/launcher3/ShortcutInfo.java @@ -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; diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 7bb91ff8a..416ca8e3e 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -61,7 +61,6 @@ import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.IOException; import java.lang.reflect.Method; -import java.util.ArrayList; import java.util.Collection; import java.util.Locale; import java.util.Set; @@ -84,6 +83,15 @@ public final class Utilities { private static final int[] sLoc0 = new int[2]; private static final int[] sLoc1 = new int[2]; + private static final float[] sPoint = new float[2]; + private static final Matrix sMatrix = new Matrix(); + private static final Matrix sInverseMatrix = new Matrix(); + + public static boolean isAtLeastO() { + // TODO: Clean this up: b/32610406 + return !"REL".equals(Build.VERSION.CODENAME) + && "O".compareTo(Build.VERSION.CODENAME) <= 0; + } public static final boolean ATLEAST_NOUGAT_MR1 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1; @@ -162,68 +170,52 @@ public final class Utilities { */ public static float getDescendantCoordRelativeToAncestor( View descendant, View ancestor, int[] coord, boolean includeRootScroll) { - float[] pt = {coord[0], coord[1]}; + sPoint[0] = coord[0]; + sPoint[1] = coord[1]; + float scale = 1.0f; View v = descendant; while(v != ancestor && v != null) { // For TextViews, scroll has a meaning which relates to the text position // which is very strange... ignore the scroll. if (v != descendant || includeRootScroll) { - pt[0] -= v.getScrollX(); - pt[1] -= v.getScrollY(); + sPoint[0] -= v.getScrollX(); + sPoint[1] -= v.getScrollY(); } - v.getMatrix().mapPoints(pt); - pt[0] += v.getLeft(); - pt[1] += v.getTop(); + v.getMatrix().mapPoints(sPoint); + sPoint[0] += v.getLeft(); + sPoint[1] += v.getTop(); scale *= v.getScaleX(); v = (View) v.getParent(); } - coord[0] = Math.round(pt[0]); - coord[1] = Math.round(pt[1]); + coord[0] = Math.round(sPoint[0]); + coord[1] = Math.round(sPoint[1]); return scale; } /** * Inverse of {@link #getDescendantCoordRelativeToAncestor(View, View, int[], boolean)}. */ - public static float mapCoordInSelfToDescendent(View descendant, View root, - int[] coord) { - ArrayList<View> ancestorChain = new ArrayList<View>(); - - float[] pt = {coord[0], coord[1]}; - + public static void mapCoordInSelfToDescendant(View descendant, View root, int[] coord) { + sMatrix.reset(); View v = descendant; while(v != root) { - ancestorChain.add(v); + sMatrix.postTranslate(-v.getScrollX(), -v.getScrollY()); + sMatrix.postConcat(v.getMatrix()); + sMatrix.postTranslate(v.getLeft(), v.getTop()); v = (View) v.getParent(); } - ancestorChain.add(root); - - float scale = 1.0f; - Matrix inverse = new Matrix(); - int count = ancestorChain.size(); - for (int i = count - 1; i >= 0; i--) { - View ancestor = ancestorChain.get(i); - View next = i > 0 ? ancestorChain.get(i-1) : null; - - pt[0] += ancestor.getScrollX(); - pt[1] += ancestor.getScrollY(); - - if (next != null) { - pt[0] -= next.getLeft(); - pt[1] -= next.getTop(); - next.getMatrix().invert(inverse); - inverse.mapPoints(pt); - scale *= next.getScaleX(); - } - } - - coord[0] = (int) Math.round(pt[0]); - coord[1] = (int) Math.round(pt[1]); - return scale; + sMatrix.postTranslate(-v.getScrollX(), -v.getScrollY()); + sMatrix.invert(sInverseMatrix); + + sPoint[0] = coord[0]; + sPoint[1] = coord[1]; + sInverseMatrix.mapPoints(sPoint); + coord[0] = Math.round(sPoint[0]); + coord[1] = Math.round(sPoint[1]); } /** diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 173f4d10b..95a5ee29f 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -60,6 +60,7 @@ import com.android.launcher3.accessibility.AccessibleDragListenerAdapter; import com.android.launcher3.accessibility.OverviewAccessibilityDelegate; import com.android.launcher3.accessibility.OverviewScreenAccessibilityDelegate; import com.android.launcher3.accessibility.WorkspaceAccessibilityHelper; +import com.android.launcher3.anim.AnimationLayerSet; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.UserHandleCompat; import com.android.launcher3.config.FeatureFlags; @@ -86,7 +87,6 @@ import com.android.launcher3.widget.PendingAddShortcutInfo; import com.android.launcher3.widget.PendingAddWidgetInfo; import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; /** @@ -2029,7 +2029,7 @@ public class Workspace extends PagedView * to that new state. */ public Animator setStateWithAnimation(State toState, boolean animated, - HashMap<View, Integer> layerViews) { + AnimationLayerSet layerViews) { // Create the animation to the new state AnimatorSet workspaceAnim = mStateTransitionAnimation.getAnimationToState(mState, toState, animated, layerViews); diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java index 598ba741a..1cf4b39f5 100644 --- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java +++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java @@ -30,12 +30,11 @@ import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.animation.DecelerateInterpolator; +import com.android.launcher3.anim.AnimationLayerSet; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.dragndrop.DragLayer; import com.android.launcher3.util.Thunk; -import java.util.HashMap; - /** * A convenience class to update a view's visibility state after an alpha animation. */ @@ -226,7 +225,7 @@ public class WorkspaceStateTransitionAnimation { } public AnimatorSet getAnimationToState(Workspace.State fromState, Workspace.State toState, - boolean animated, HashMap<View, Integer> layerViews) { + boolean animated, AnimationLayerSet layerViews) { AccessibilityManager am = (AccessibilityManager) mLauncher.getSystemService(Context.ACCESSIBILITY_SERVICE); final boolean accessibilityEnabled = am.isEnabled(); @@ -262,8 +261,7 @@ public class WorkspaceStateTransitionAnimation { * Starts a transition animation for the workspace. */ private void animateWorkspace(final TransitionStates states, final boolean animated, - final int duration, final HashMap<View, Integer> layerViews, - final boolean accessibilityEnabled) { + final int duration, AnimationLayerSet layerViews, final boolean accessibilityEnabled) { // Cancel existing workspace animations and create a new animator set if requested cancelAnimation(); if (animated) { @@ -396,12 +394,10 @@ public class WorkspaceStateTransitionAnimation { // For animation optimization, we may need to provide the Launcher transition // with a set of views on which to force build and manage layers in certain scenarios. - layerViews.put(overviewPanel, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER); - layerViews.put(qsbContainer, LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER); - layerViews.put(mLauncher.getHotseat(), - LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER); - layerViews.put(mWorkspace.getPageIndicator(), - LauncherStateTransitionAnimation.BUILD_AND_SET_LAYER); + layerViews.addView(overviewPanel); + layerViews.addView(qsbContainer); + layerViews.addView(mLauncher.getHotseat()); + layerViews.addView(mWorkspace.getPageIndicator()); if (states.workspaceToOverview) { hotseatAlpha.setInterpolator(new DecelerateInterpolator(2)); diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java index b5aed0da9..e468d8d60 100644 --- a/src/com/android/launcher3/allapps/AllAppsContainerView.java +++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java @@ -75,6 +75,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc private AllAppsSearchBarController mSearchBarController; private View mSearchContainer; + private int mSearchContainerMinHeight; private ExtendedEditText mSearchInput; private HeaderElevationController mElevationController; @@ -100,6 +101,9 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc mApps.setAdapter(mAdapter); mLayoutManager = mAdapter.getLayoutManager(); mSearchQueryBuilder = new SpannableStringBuilder(); + mSearchContainerMinHeight + = getResources().getDimensionPixelSize(R.dimen.all_apps_search_bar_height); + Selection.setSelection(mSearchQueryBuilder, 0); } @@ -193,7 +197,7 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc int[] point = new int[2]; point[0] = (int) ev.getX(); point[1] = (int) ev.getY(); - Utilities.mapCoordInSelfToDescendent(mAppsRecyclerView, this, point); + Utilities.mapCoordInSelfToDescendant(mAppsRecyclerView, this, point); // IF the MotionEvent is inside the search box, and the container keeps on receiving // touch input, container should move down. @@ -315,7 +319,9 @@ public class AllAppsContainerView extends BaseContainerView implements DragSourc if (!grid.isVerticalBarLayout()) { MarginLayoutParams searchContainerLp = (MarginLayoutParams) mSearchContainer.getLayoutParams(); - searchContainerLp.height = grid.hotseatBarHeightPx; + + searchContainerLp.height = mLauncher.getDragLayer().getInsets().top + + mSearchContainerMinHeight; mSearchContainer.setLayoutParams(searchContainerLp); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); diff --git a/src/com/android/launcher3/allapps/HeaderElevationController.java b/src/com/android/launcher3/allapps/HeaderElevationController.java index ce9837c39..e79e5c762 100644 --- a/src/com/android/launcher3/allapps/HeaderElevationController.java +++ b/src/com/android/launcher3/allapps/HeaderElevationController.java @@ -14,6 +14,7 @@ import android.widget.FrameLayout; import com.android.launcher3.BaseRecyclerView; import com.android.launcher3.R; +import com.android.launcher3.Utilities; /** * Helper class for controlling the header elevation in response to RecyclerView scroll. @@ -84,7 +85,7 @@ public abstract class HeaderElevationController extends RecyclerView.OnScrollLis public ControllerVL(View header) { mHeader = header; - Resources res = mHeader.getContext().getResources(); + final Resources res = mHeader.getContext().getResources(); mMaxElevation = res.getDimension(R.dimen.all_apps_header_max_elevation); mScrollToElevation = res.getDimension(R.dimen.all_apps_header_scroll_to_elevation); @@ -101,11 +102,8 @@ public abstract class HeaderElevationController extends RecyclerView.OnScrollLis final int right = left + view.getWidth(); final int bottom = view.getBottom(); - outline.setRect( - left - (int) mMaxElevation, - top - (int) mMaxElevation, - right + (int) mMaxElevation, - bottom); + final int offset = Utilities.pxFromDp(mMaxElevation, res.getDisplayMetrics()); + outline.setRect(left - offset, top - offset, right + offset, bottom); } }; mHeader.setOutlineProvider(vop); diff --git a/src/com/android/launcher3/anim/AnimationLayerSet.java b/src/com/android/launcher3/anim/AnimationLayerSet.java new file mode 100644 index 000000000..42706ffa1 --- /dev/null +++ b/src/com/android/launcher3/anim/AnimationLayerSet.java @@ -0,0 +1,53 @@ +/* + * 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.anim; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.view.View; + +import java.util.HashSet; + +/** + * Helper class to automatically build view hardware layers for the duration of an animation. + */ +public class AnimationLayerSet extends AnimatorListenerAdapter { + + private final HashSet<View> mViews = new HashSet<>(); + + public void addView(View v) { + mViews.add(v); + } + + @Override + public void onAnimationStart(Animator animation) { + // Enable all necessary layers + for (View v : mViews) { + v.setLayerType(View.LAYER_TYPE_HARDWARE, null); + if (v.isAttachedToWindow() && v.getVisibility() == View.VISIBLE) { + v.buildLayer(); + } + } + } + + @Override + public void onAnimationEnd(Animator animation) { + for (View v : mViews) { + v.setLayerType(View.LAYER_TYPE_NONE, null); + } + } +} diff --git a/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java b/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java index 0bc9588aa..65af4eaea 100644 --- a/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java +++ b/src/com/android/launcher3/compat/LauncherActivityInfoCompat.java @@ -17,9 +17,7 @@ package com.android.launcher3.compat; import android.content.ComponentName; -import android.content.Context; import android.content.pm.ApplicationInfo; -import android.content.pm.ResolveInfo; import android.graphics.drawable.Drawable; public abstract class LauncherActivityInfoCompat { @@ -33,11 +31,4 @@ public abstract class LauncherActivityInfoCompat { public abstract Drawable getIcon(int density); public abstract ApplicationInfo getApplicationInfo(); public abstract long getFirstInstallTime(); - - /** - * Creates a LauncherActivityInfoCompat for the primary user. - */ - public static LauncherActivityInfoCompat fromResolveInfo(ResolveInfo info, Context context) { - return new LauncherActivityInfoCompatV16(context, info); - } } diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java index 961877911..9de44526d 100644 --- a/src/com/android/launcher3/dragndrop/DragLayer.java +++ b/src/com/android/launcher3/dragndrop/DragLayer.java @@ -480,8 +480,8 @@ public class DragLayer extends InsettableFrameLayout { /** * Inverse of {@link #getDescendantCoordRelativeToSelf(View, int[])}. */ - public float mapCoordInSelfToDescendant(View descendant, int[] coord) { - return Utilities.mapCoordInSelfToDescendent(descendant, this, coord); + public void mapCoordInSelfToDescendant(View descendant, int[] coord) { + Utilities.mapCoordInSelfToDescendant(descendant, this, coord); } public void getViewRectRelativeToSelf(View v, Rect r) { diff --git a/src/com/android/launcher3/dynamicui/ColorExtractionService.java b/src/com/android/launcher3/dynamicui/ColorExtractionService.java index 1369f609d..1a127dc8f 100644 --- a/src/com/android/launcher3/dynamicui/ColorExtractionService.java +++ b/src/com/android/launcher3/dynamicui/ColorExtractionService.java @@ -16,24 +16,35 @@ package com.android.launcher3.dynamicui; +import android.annotation.TargetApi; import android.app.IntentService; import android.app.WallpaperManager; import android.content.Intent; import android.graphics.Bitmap; +import android.graphics.BitmapRegionDecoder; +import android.graphics.Rect; import android.graphics.drawable.BitmapDrawable; +import android.os.Build; import android.os.Bundle; +import android.os.ParcelFileDescriptor; import android.support.v7.graphics.Palette; +import android.util.Log; import com.android.launcher3.LauncherProvider; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; +import java.io.IOException; + /** * Extracts colors from the wallpaper, and saves results to {@link LauncherProvider}. */ public class ColorExtractionService extends IntentService { + private static final String TAG = "ColorExtractionService"; + /** The fraction of the wallpaper to extract colors for use on the hotseat. */ private static final float HOTSEAT_FRACTION = 1f / 4; @@ -45,32 +56,18 @@ public class ColorExtractionService extends IntentService { protected void onHandleIntent(Intent intent) { WallpaperManager wallpaperManager = WallpaperManager.getInstance(this); int wallpaperId = ExtractionUtils.getWallpaperId(wallpaperManager); + ExtractedColors extractedColors = new ExtractedColors(); if (wallpaperManager.getWallpaperInfo() != null) { // We can't extract colors from live wallpapers, so just use the default color always. - extractedColors.updatePalette(null); extractedColors.updateHotseatPalette(null); } else { - Bitmap wallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap(); - Palette palette = Palette.from(wallpaper).generate(); - extractedColors.updatePalette(palette); // We extract colors for the hotseat and status bar separately, // since they only consider part of the wallpaper. - Palette hotseatPalette = Palette.from(wallpaper) - .setRegion(0, (int) (wallpaper.getHeight() * (1f - HOTSEAT_FRACTION)), - wallpaper.getWidth(), wallpaper.getHeight()) - .clearFilters() - .generate(); - extractedColors.updateHotseatPalette(hotseatPalette); + extractedColors.updateHotseatPalette(getHotseatPalette()); if (FeatureFlags.LIGHT_STATUS_BAR) { - int statusBarHeight = getResources() - .getDimensionPixelSize(R.dimen.status_bar_height); - Palette statusBarPalette = Palette.from(wallpaper) - .setRegion(0, 0, wallpaper.getWidth(), statusBarHeight) - .clearFilters() - .generate(); - extractedColors.updateStatusBarPalette(statusBarPalette); + extractedColors.updateStatusBarPalette(getStatusBarPalette()); } } @@ -84,4 +81,63 @@ public class ColorExtractionService extends IntentService { LauncherSettings.Settings.METHOD_SET_EXTRACTED_COLORS_AND_WALLPAPER_ID, null, extras); } + + @TargetApi(Build.VERSION_CODES.N) + private Palette getHotseatPalette() { + WallpaperManager wallpaperManager = WallpaperManager.getInstance(this); + if (Utilities.ATLEAST_NOUGAT) { + try (ParcelFileDescriptor fd = wallpaperManager + .getWallpaperFile(WallpaperManager.FLAG_SYSTEM)) { + BitmapRegionDecoder decoder = BitmapRegionDecoder + .newInstance(fd.getFileDescriptor(), false); + int height = decoder.getHeight(); + Rect decodeRegion = new Rect(0, (int) (height * (1f - HOTSEAT_FRACTION)), + decoder.getWidth(), height); + Bitmap bitmap = decoder.decodeRegion(decodeRegion, null); + decoder.recycle(); + if (bitmap != null) { + return Palette.from(bitmap).clearFilters().generate(); + } + } catch (IOException e) { + Log.e(TAG, "Fetching partial bitmap failed, trying old method", e); + } + } + + Bitmap wallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap(); + return Palette.from(wallpaper) + .setRegion(0, (int) (wallpaper.getHeight() * (1f - HOTSEAT_FRACTION)), + wallpaper.getWidth(), wallpaper.getHeight()) + .clearFilters() + .generate(); + } + + @TargetApi(Build.VERSION_CODES.N) + private Palette getStatusBarPalette() { + WallpaperManager wallpaperManager = WallpaperManager.getInstance(this); + int statusBarHeight = getResources() + .getDimensionPixelSize(R.dimen.status_bar_height); + + if (Utilities.ATLEAST_NOUGAT) { + try (ParcelFileDescriptor fd = wallpaperManager + .getWallpaperFile(WallpaperManager.FLAG_SYSTEM)) { + BitmapRegionDecoder decoder = BitmapRegionDecoder + .newInstance(fd.getFileDescriptor(), false); + Rect decodeRegion = new Rect(0, 0, + decoder.getWidth(), statusBarHeight); + Bitmap bitmap = decoder.decodeRegion(decodeRegion, null); + decoder.recycle(); + if (bitmap != null) { + return Palette.from(bitmap).clearFilters().generate(); + } + } catch (IOException e) { + Log.e(TAG, "Fetching partial bitmap failed, trying old method", e); + } + } + + Bitmap wallpaper = ((BitmapDrawable) wallpaperManager.getDrawable()).getBitmap(); + return Palette.from(wallpaper) + .setRegion(0, 0, wallpaper.getWidth(), statusBarHeight) + .clearFilters() + .generate(); + } } diff --git a/src/com/android/launcher3/dynamicui/ExtractedColors.java b/src/com/android/launcher3/dynamicui/ExtractedColors.java index 6a3011d3b..711508ea5 100644 --- a/src/com/android/launcher3/dynamicui/ExtractedColors.java +++ b/src/com/android/launcher3/dynamicui/ExtractedColors.java @@ -113,34 +113,6 @@ public class ExtractedColors { } /** - * Updates colors based on the palette. - * If the palette is null, the default color is used in all cases. - */ - public void updatePalette(Palette palette) { - if (palette == null) { - for (int i = 0; i < NUM_COLOR_PROFILES; i++) { - setColorAtIndex(i, ExtractedColors.DEFAULT_COLOR); - } - } else { - // We currently don't use any of the colors defined by the Palette API, - // but this is how we would add them if we ever need them. - - // setColorAtIndex(ExtractedColors.VIBRANT_INDEX, - // palette.getVibrantColor(ExtractedColors.DEFAULT_COLOR)); - // setColorAtIndex(ExtractedColors.VIBRANT_DARK_INDEX, - // palette.getDarkVibrantColor(ExtractedColors.DEFAULT_DARK)); - // setColorAtIndex(ExtractedColors.VIBRANT_LIGHT_INDEX, - // palette.getLightVibrantColor(ExtractedColors.DEFAULT_LIGHT)); - // setColorAtIndex(ExtractedColors.MUTED_INDEX, - // palette.getMutedColor(DEFAULT_COLOR)); - // setColorAtIndex(ExtractedColors.MUTED_DARK_INDEX, - // palette.getDarkMutedColor(ExtractedColors.DEFAULT_DARK)); - // setColorAtIndex(ExtractedColors.MUTED_LIGHT_INDEX, - // palette.getLightVibrantColor(ExtractedColors.DEFAULT_LIGHT)); - } - } - - /** * The hotseat's color is defined as follows: * - 12% black for super light wallpaper * - 18% white for super dark diff --git a/src/com/android/launcher3/model/AddWorkspaceItemsTask.java b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java new file mode 100644 index 000000000..986e163e6 --- /dev/null +++ b/src/com/android/launcher3/model/AddWorkspaceItemsTask.java @@ -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); + } + +} diff --git a/src/com/android/launcher3/model/CacheDataUpdatedTask.java b/src/com/android/launcher3/model/CacheDataUpdatedTask.java new file mode 100644 index 000000000..9f24e9035 --- /dev/null +++ b/src/com/android/launcher3/model/CacheDataUpdatedTask.java @@ -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; + } + } +} diff --git a/src/com/android/launcher3/model/ExtendedModelTask.java b/src/com/android/launcher3/model/ExtendedModelTask.java new file mode 100644 index 000000000..ccc600768 --- /dev/null +++ b/src/com/android/launcher3/model/ExtendedModelTask.java @@ -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); + } + }); + } +} diff --git a/src/com/android/launcher3/model/PackageInstallStateChangedTask.java b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java new file mode 100644 index 000000000..5d04325e8 --- /dev/null +++ b/src/com/android/launcher3/model/PackageInstallStateChangedTask.java @@ -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); + } + }); + } + } + } +} diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java new file mode 100644 index 000000000..7286bf51f --- /dev/null +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -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(); + } + }); + } + } +} diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java new file mode 100644 index 000000000..8f7c21db0 --- /dev/null +++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java @@ -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); + } + } +} diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java new file mode 100644 index 000000000..b7b52a448 --- /dev/null +++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java @@ -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); + } +} diff --git a/src/com/android/launcher3/util/ManagedProfileHeuristic.java b/src/com/android/launcher3/util/ManagedProfileHeuristic.java index 6661429c1..78b7a3eee 100644 --- a/src/com/android/launcher3/util/ManagedProfileHeuristic.java +++ b/src/com/android/launcher3/util/ManagedProfileHeuristic.java @@ -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. */ |