diff --git a/quickstep/libs/sysui_shared.jar b/quickstep/libs/sysui_shared.jar index 17ff858c47..7ff664d41c 100644 Binary files a/quickstep/libs/sysui_shared.jar and b/quickstep/libs/sysui_shared.jar differ diff --git a/quickstep/src/com/android/launcher3/uioverrides/StatusBarTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/StatusBarTouchController.java index 4d567865e6..35f46cfc7f 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/StatusBarTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/StatusBarTouchController.java @@ -84,7 +84,8 @@ public class StatusBarTouchController implements TouchController { } if (action == ACTION_MOVE) { float dy = ev.getY() - mTranslator.getDownY(); - if (dy > mTouchSlop) { + float dx = ev.getX() - mTranslator.getDownX(); + if (dy > mTouchSlop && dy > Math.abs(dx)) { mTranslator.dispatchDownEvents(ev); mTranslator.processMotionEvent(ev); return true; diff --git a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java index 88f2315757..54269f09fa 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java +++ b/quickstep/src/com/android/launcher3/uioverrides/TaskViewTouchController.java @@ -220,7 +220,7 @@ public abstract class TaskViewTouchController } @Override - public boolean onDrag(float displacement, float velocity) { + public boolean onDrag(float displacement) { float totalDisplacement = displacement + mDisplacementShift; boolean isGoingUp = totalDisplacement == 0 ? mCurrentAnimationIsGoingUp : totalDisplacement < 0; diff --git a/quickstep/src/com/android/quickstep/ActivityControlHelper.java b/quickstep/src/com/android/quickstep/ActivityControlHelper.java index f39a0072c7..206c8be4b4 100644 --- a/quickstep/src/com/android/quickstep/ActivityControlHelper.java +++ b/quickstep/src/com/android/quickstep/ActivityControlHelper.java @@ -91,7 +91,7 @@ public interface ActivityControlHelper { * Updates the UI to indicate quick interaction. */ void onQuickInteractionStart(T activity, @Nullable RunningTaskInfo taskInfo, - boolean activityVisible); + boolean activityVisible, TouchInteractionLog touchInteractionLog); float getTranslationYForQuickScrub(TransformedRect targetRect, DeviceProfile dp, Context context); @@ -153,13 +153,14 @@ public interface ActivityControlHelper { @Override public void onQuickInteractionStart(Launcher activity, RunningTaskInfo taskInfo, - boolean activityVisible) { + boolean activityVisible, TouchInteractionLog touchInteractionLog) { LauncherState fromState = activity.getStateManager().getState(); activity.getStateManager().goToState(FAST_OVERVIEW, activityVisible); QuickScrubController controller = activity.getOverviewPanel() .getQuickScrubController(); - controller.onQuickScrubStart(activityVisible && !fromState.overviewUi, this); + controller.onQuickScrubStart(activityVisible && !fromState.overviewUi, this, + touchInteractionLog); if (!activityVisible) { // For the duration of the gesture, lock the screen orientation to ensure that we @@ -425,14 +426,14 @@ public interface ActivityControlHelper { @Override public void onQuickInteractionStart(RecentsActivity activity, RunningTaskInfo taskInfo, - boolean activityVisible) { + boolean activityVisible, TouchInteractionLog touchInteractionLog) { QuickScrubController controller = activity.getOverviewPanel() .getQuickScrubController(); // TODO: match user is as well boolean startingFromHome = !activityVisible && (taskInfo == null || Objects.equals(taskInfo.topActivity, mHomeComponent)); - controller.onQuickScrubStart(startingFromHome, this); + controller.onQuickScrubStart(startingFromHome, this, touchInteractionLog); if (activityVisible) { mUiHandler.postDelayed(controller::onFinishedTransitionToQuickScrub, OVERVIEW_TRANSITION_MS); diff --git a/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java b/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java index 8e83bd0792..5996df78cf 100644 --- a/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java +++ b/quickstep/src/com/android/quickstep/DeferredTouchConsumer.java @@ -49,8 +49,8 @@ public class DeferredTouchConsumer implements TouchConsumer { } @Override - public void updateTouchTracking(int interactionType) { - mTarget.updateTouchTracking(interactionType); + public void onQuickScrubStart() { + mTarget.onQuickScrubStart(); } @Override diff --git a/quickstep/src/com/android/quickstep/MotionEventQueue.java b/quickstep/src/com/android/quickstep/MotionEventQueue.java index f73be6cba9..8a598a3a84 100644 --- a/quickstep/src/com/android/quickstep/MotionEventQueue.java +++ b/quickstep/src/com/android/quickstep/MotionEventQueue.java @@ -146,7 +146,7 @@ public class MotionEventQueue { if (event.getActionMasked() == ACTION_VIRTUAL) { switch (event.getAction()) { case ACTION_QUICK_SCRUB_START: - mConsumer.updateTouchTracking(INTERACTION_QUICK_SCRUB); + mConsumer.onQuickScrubStart(); break; case ACTION_QUICK_SCRUB_PROGRESS: mConsumer.onQuickScrubProgress(event.getX()); @@ -162,7 +162,7 @@ public class MotionEventQueue { break; case ACTION_SHOW_OVERVIEW_FROM_ALT_TAB: mConsumer.onShowOverviewFromAltTab(); - mConsumer.updateTouchTracking(INTERACTION_QUICK_SCRUB); + mConsumer.onQuickScrubStart(); break; case ACTION_QUICK_STEP: mConsumer.onQuickStep(event); diff --git a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java b/quickstep/src/com/android/quickstep/NormalizedIconLoader.java index 8f7dbdd366..655776194f 100644 --- a/quickstep/src/com/android/quickstep/NormalizedIconLoader.java +++ b/quickstep/src/com/android/quickstep/NormalizedIconLoader.java @@ -28,9 +28,9 @@ import android.util.LruCache; import android.util.SparseArray; import com.android.launcher3.FastBitmapDrawable; -import com.android.launcher3.graphics.BitmapInfo; +import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.graphics.DrawableFactory; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.LauncherIcons; import com.android.systemui.shared.recents.model.IconLoader; import com.android.systemui.shared.recents.model.TaskKeyLruCache; diff --git a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java index 0e811f771f..4417a3da77 100644 --- a/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java +++ b/quickstep/src/com/android/quickstep/OtherActivityTouchConsumer.java @@ -79,6 +79,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC private final Choreographer mBackgroundThreadChoreographer; private final OverviewCallbacks mOverviewCallbacks; private final TaskOverlayFactory mTaskOverlayFactory; + private final TouchInteractionLog mTouchInteractionLog; private final boolean mIsDeferredDownTarget; private final PointF mDownPos = new PointF(); @@ -100,7 +101,8 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC RecentsModel recentsModel, Intent homeIntent, ActivityControlHelper activityControl, MainThreadExecutor mainThreadExecutor, Choreographer backgroundThreadChoreographer, @HitTarget int downHitTarget, OverviewCallbacks overviewCallbacks, - TaskOverlayFactory taskOverlayFactory, VelocityTracker velocityTracker) { + TaskOverlayFactory taskOverlayFactory, VelocityTracker velocityTracker, + TouchInteractionLog touchInteractionLog) { super(base); mRunningTask = runningTaskInfo; @@ -113,6 +115,8 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC mIsDeferredDownTarget = activityControl.deferStartingActivity(downHitTarget); mOverviewCallbacks = overviewCallbacks; mTaskOverlayFactory = taskOverlayFactory; + mTouchInteractionLog = touchInteractionLog; + mTouchInteractionLog.setTouchConsumer(this); } @Override @@ -125,6 +129,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC if (mVelocityTracker == null) { return; } + mTouchInteractionLog.addMotionEvent(ev); switch (ev.getActionMasked()) { case ACTION_DOWN: { TraceHelper.beginSection("TouchInt"); @@ -215,10 +220,13 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC } private void startTouchTrackingForWindowAnimation(long touchTimeMs) { + mTouchInteractionLog.startRecentsAnimation(); + // Create the shared handler RecentsAnimationState animationState = new RecentsAnimationState(); final WindowTransformSwipeHandler handler = new WindowTransformSwipeHandler( - animationState.id, mRunningTask, this, touchTimeMs, mActivityControlHelper); + animationState.id, mRunningTask, this, touchTimeMs, mActivityControlHelper, + mTouchInteractionLog); // Preload the plan mRecentsModel.loadTasks(mRunningTask.id, null); @@ -315,7 +323,13 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC } @Override - public void updateTouchTracking(int interactionType) { + public Choreographer getIntrimChoreographer(MotionEventQueue queue) { + mEventQueue = queue; + return mBackgroundThreadChoreographer; + } + + @Override + public void onQuickScrubStart() { if (!mPassedInitialSlop && mIsDeferredDownTarget && mInteractionHandler == null) { // If we deferred starting the window animation on touch down, then // start tracking now @@ -323,20 +337,16 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC mPassedInitialSlop = true; } + mTouchInteractionLog.startQuickScrub(); if (mInteractionHandler != null) { - mInteractionHandler.updateInteractionType(interactionType); + mInteractionHandler.onQuickScrubStart(); } notifyGestureStarted(); } - @Override - public Choreographer getIntrimChoreographer(MotionEventQueue queue) { - mEventQueue = queue; - return mBackgroundThreadChoreographer; - } - @Override public void onQuickScrubEnd() { + mTouchInteractionLog.endQuickScrub("onQuickScrubEnd"); if (mInteractionHandler != null) { mInteractionHandler.onQuickScrubEnd(); } @@ -344,6 +354,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC @Override public void onQuickScrubProgress(float progress) { + mTouchInteractionLog.setQuickScrubProgress(progress); if (mInteractionHandler != null) { mInteractionHandler.onQuickScrubProgress(progress); } @@ -351,6 +362,7 @@ public class OtherActivityTouchConsumer extends ContextWrapper implements TouchC @Override public void onQuickStep(MotionEvent ev) { + mTouchInteractionLog.startQuickStep(); if (mIsDeferredDownTarget) { // Deferred gesture, start the animation and gesture tracking once we pass the actual // touch slop diff --git a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java index 1a9915c414..d9626c4bdf 100644 --- a/quickstep/src/com/android/quickstep/OverviewCommandHelper.java +++ b/quickstep/src/com/android/quickstep/OverviewCommandHelper.java @@ -280,7 +280,6 @@ public class OverviewCommandHelper { factory.createActivityController(RECENTS_LAUNCH_DURATION, INTERACTION_NORMAL); mActivity = activity; mRecentsView = mActivity.getOverviewPanel(); - mRecentsView.setRunningTaskIconScaledDown(true); if (!mUserEventLogged) { activity.getUserEventDispatcher().logActionCommand(Action.Command.RECENTS_BUTTON, mHelper.getContainerType(), ContainerType.TASKSWITCHER); @@ -298,6 +297,9 @@ public class OverviewCommandHelper { if (mListener != null) { mListener.unregister(); } + if (mRecentsView != null) { + mRecentsView.setRunningTaskIconScaledDown(true); + } AnimatorSet anim = new AnimatorSet(); anim.addListener(new AnimationSuccessListener() { @Override diff --git a/quickstep/src/com/android/quickstep/QuickScrubController.java b/quickstep/src/com/android/quickstep/QuickScrubController.java index cbc7a6793d..34207670e8 100644 --- a/quickstep/src/com/android/quickstep/QuickScrubController.java +++ b/quickstep/src/com/android/quickstep/QuickScrubController.java @@ -67,8 +67,10 @@ public class QuickScrubController implements OnAlarmListener { private int mQuickScrubSection; private boolean mStartedFromHome; private boolean mFinishedTransitionToQuickScrub; + private int mLaunchingTaskId; private Runnable mOnFinishedTransitionToQuickScrubRunnable; private ActivityControlHelper mActivityControlHelper; + private TouchInteractionLog mTouchInteractionLog; public QuickScrubController(BaseActivity activity, RecentsView recentsView) { mActivity = activity; @@ -79,13 +81,15 @@ public class QuickScrubController implements OnAlarmListener { } } - public void onQuickScrubStart(boolean startingFromHome, ActivityControlHelper controlHelper) { + public void onQuickScrubStart(boolean startingFromHome, ActivityControlHelper controlHelper, + TouchInteractionLog touchInteractionLog) { prepareQuickScrub(TAG); mInQuickScrub = true; mStartedFromHome = startingFromHome; mQuickScrubSection = 0; mFinishedTransitionToQuickScrub = false; mActivityControlHelper = controlHelper; + mTouchInteractionLog = touchInteractionLog; snapToNextTaskIfAvailable(); mActivity.getUserEventDispatcher().resetActionDurationMillis(); @@ -101,7 +105,10 @@ public class QuickScrubController implements OnAlarmListener { TaskView taskView = mRecentsView.getTaskViewAt(page); if (taskView != null) { mWaitingForTaskLaunch = true; + mTouchInteractionLog.launchTaskStart(); + mLaunchingTaskId = taskView.getTask().key.id; taskView.launchTask(true, (result) -> { + mTouchInteractionLog.launchTaskEnd(result); if (!result) { taskView.notifyTaskLaunchFailed(TAG); breakOutOfQuickScrub(); @@ -141,6 +148,7 @@ public class QuickScrubController implements OnAlarmListener { mActivityControlHelper = null; mOnFinishedTransitionToQuickScrubRunnable = null; mRecentsView.setNextPageSwitchRunnable(null); + mLaunchingTaskId = 0; } /** @@ -206,6 +214,18 @@ public class QuickScrubController implements OnAlarmListener { } } + public void onTaskRemoved(int taskId) { + if (mLaunchingTaskId == taskId) { + // The task has been removed mid-launch, break out of quickscrub and return the user + // to where they were before (and notify the launch failed) + TaskView taskView = mRecentsView.getTaskView(taskId); + if (taskView != null) { + taskView.notifyTaskLaunchFailed(TAG); + } + breakOutOfQuickScrub(); + } + } + public void snapToNextTaskIfAvailable() { if (mInQuickScrub && mRecentsView.getChildCount() > 0) { int duration = mStartedFromHome ? QUICK_SCRUB_FROM_HOME_START_DURATION diff --git a/quickstep/src/com/android/quickstep/TouchConsumer.java b/quickstep/src/com/android/quickstep/TouchConsumer.java index 646fc57f25..225d29bd37 100644 --- a/quickstep/src/com/android/quickstep/TouchConsumer.java +++ b/quickstep/src/com/android/quickstep/TouchConsumer.java @@ -43,7 +43,7 @@ public interface TouchConsumer extends Consumer { default void reset() { } - default void updateTouchTracking(@InteractionType int interactionType) { } + default void onQuickScrubStart() { } default void onQuickScrubEnd() { } diff --git a/quickstep/src/com/android/quickstep/TouchInteractionLog.java b/quickstep/src/com/android/quickstep/TouchInteractionLog.java new file mode 100644 index 0000000000..053efbb65b --- /dev/null +++ b/quickstep/src/com/android/quickstep/TouchInteractionLog.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.quickstep; + +import android.view.MotionEvent; +import java.io.PrintWriter; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.LinkedList; + +/** + * Keeps track of debugging logs for a particular quickstep/scrub gesture. + */ +public class TouchInteractionLog { + + // The number of gestures to log + private static final int MAX_NUM_LOG_GESTURES = 5; + + private final Calendar mCalendar = Calendar.getInstance(); + private final SimpleDateFormat mDateFormat = new SimpleDateFormat("MMM dd - kk:mm:ss:SSS"); + private final LinkedList> mGestureLogs = new LinkedList<>(); + + public void prepareForNewGesture() { + mGestureLogs.add(new ArrayList<>()); + while (mGestureLogs.size() > MAX_NUM_LOG_GESTURES) { + mGestureLogs.pop(); + } + getCurrentLog().add("[" + mDateFormat.format(mCalendar.getTime()) + "]"); + } + + public void setTouchConsumer(TouchConsumer consumer) { + getCurrentLog().add("tc=" + consumer.getClass().getSimpleName()); + } + + public void addMotionEvent(MotionEvent event) { + getCurrentLog().add("ev=" + event.getActionMasked()); + } + + public void startQuickStep() { + getCurrentLog().add("qstStart"); + } + + public void startQuickScrub() { + getCurrentLog().add("qsStart"); + } + + public void setQuickScrubProgress(float progress) { + getCurrentLog().add("qsP=" + progress); + } + + public void endQuickScrub(String reason) { + getCurrentLog().add("qsEnd=" + reason); + } + + public void startRecentsAnimation() { + getCurrentLog().add("raStart"); + } + + public void startRecentsAnimationCallback(int numTargets) { + getCurrentLog().add("raStartCb=" + numTargets); + } + + public void cancelRecentsAnimation() { + getCurrentLog().add("raCancel"); + } + + public void finishRecentsAnimation(boolean toHome) { + getCurrentLog().add("raFinish=" + toHome); + } + + public void launchTaskStart() { + getCurrentLog().add("launchStart"); + } + + public void launchTaskEnd(boolean result) { + getCurrentLog().add("launchEnd=" + result); + } + + public void dump(PrintWriter pw) { + pw.println("TouchInteractionLog {"); + for (ArrayList gesture : mGestureLogs) { + pw.print(" "); + for (String log : gesture) { + pw.print(log + " "); + } + pw.println(); + } + pw.println("}"); + } + + private ArrayList getCurrentLog() { + return mGestureLogs.getLast(); + } +} diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index bd79301113..b9f95ccfa4 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -52,6 +52,8 @@ import com.android.systemui.shared.recents.ISystemUiProxy; import com.android.systemui.shared.system.ActivityManagerWrapper; import com.android.systemui.shared.system.ChoreographerCompat; import com.android.systemui.shared.system.NavigationBarCompat.HitTarget; +import java.io.FileDescriptor; +import java.io.PrintWriter; /** * Service connected by system-UI for handling touch interaction. @@ -81,6 +83,8 @@ public class TouchInteractionService extends Service { @Override public void onPreMotionEvent(@HitTarget int downHitTarget) { + mTouchInteractionLog.prepareForNewGesture(); + TraceHelper.beginSection("SysUiBinder"); setupTouchConsumer(downHitTarget); TraceHelper.partitionSection("SysUiBinder", "Down target " + downHitTarget); @@ -179,6 +183,7 @@ public class TouchInteractionService extends Service { private OverviewInteractionState mOverviewInteractionState; private OverviewCallbacks mOverviewCallbacks; private TaskOverlayFactory mTaskOverlayFactory; + private TouchInteractionLog mTouchInteractionLog; private Choreographer mMainThreadChoreographer; private Choreographer mBackgroundThreadChoreographer; @@ -196,6 +201,7 @@ public class TouchInteractionService extends Service { mOverviewInteractionState = OverviewInteractionState.INSTANCE.get(this); mOverviewCallbacks = OverviewCallbacks.get(this); mTaskOverlayFactory = TaskOverlayFactory.get(this); + mTouchInteractionLog = new TouchInteractionLog(); sConnected = true; @@ -240,7 +246,7 @@ public class TouchInteractionService extends Service { } else if (forceToLauncher || runningTaskInfo.topActivity.equals(mOverviewCommandHelper.overviewComponent)) { return OverviewTouchConsumer.newInstance( - mOverviewCommandHelper.getActivityControlHelper(), false); + mOverviewCommandHelper.getActivityControlHelper(), false, mTouchInteractionLog); } else { if (tracker == null) { tracker = VelocityTracker.obtain(); @@ -249,7 +255,7 @@ public class TouchInteractionService extends Service { mOverviewCommandHelper.overviewIntent, mOverviewCommandHelper.getActivityControlHelper(), mMainThreadExecutor, mBackgroundThreadChoreographer, downHitTarget, mOverviewCallbacks, - mTaskOverlayFactory, tracker); + mTaskOverlayFactory, tracker, mTouchInteractionLog); } } @@ -262,6 +268,11 @@ public class TouchInteractionService extends Service { mBackgroundThreadChoreographer = ChoreographerCompat.getSfInstance()); } + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + mTouchInteractionLog.dump(pw); + } + public static class OverviewTouchConsumer implements TouchConsumer { @@ -272,6 +283,7 @@ public class TouchInteractionService extends Service { private final PointF mDownPos = new PointF(); private final int mTouchSlop; private final QuickScrubController mQuickScrubController; + private final TouchInteractionLog mTouchInteractionLog; private final boolean mStartingInActivityBounds; @@ -283,7 +295,7 @@ public class TouchInteractionService extends Service { private boolean mEndPending = false; OverviewTouchConsumer(ActivityControlHelper activityHelper, T activity, - boolean startingInActivityBounds) { + boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog) { mActivityHelper = activityHelper; mActivity = activity; mTarget = activity.getDragLayer(); @@ -292,6 +304,8 @@ public class TouchInteractionService extends Service { mQuickScrubController = mActivity.getOverviewPanel() .getQuickScrubController(); + mTouchInteractionLog = touchInteractionLog; + mTouchInteractionLog.setTouchConsumer(this); } @Override @@ -299,6 +313,7 @@ public class TouchInteractionService extends Service { if (mInvalidated) { return; } + mTouchInteractionLog.addMotionEvent(ev); int action = ev.getActionMasked(); if (action == ACTION_DOWN) { if (mStartingInActivityBounds) { @@ -372,44 +387,48 @@ public class TouchInteractionService extends Service { OverviewCallbacks.get(mActivity).closeAllWindows(); ActivityManagerWrapper.getInstance() .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); + mTouchInteractionLog.startQuickStep(); } @Override - public void updateTouchTracking(int interactionType) { + public void onQuickScrubStart() { if (mInvalidated) { return; } - if (interactionType == INTERACTION_QUICK_SCRUB) { + mTouchInteractionLog.startQuickScrub(); + if (!mQuickScrubController.prepareQuickScrub(TAG)) { + mInvalidated = true; + mTouchInteractionLog.endQuickScrub("onQuickScrubStart"); + return; + } + OverviewCallbacks.get(mActivity).closeAllWindows(); + ActivityManagerWrapper.getInstance() + .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); + + mStartPending = true; + Runnable action = () -> { if (!mQuickScrubController.prepareQuickScrub(TAG)) { mInvalidated = true; + mTouchInteractionLog.endQuickScrub("onQuickScrubStart"); return; } - OverviewCallbacks.get(mActivity).closeAllWindows(); - ActivityManagerWrapper.getInstance() - .closeSystemWindows(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); + mActivityHelper.onQuickInteractionStart(mActivity, null, true, + mTouchInteractionLog); + mQuickScrubController.onQuickScrubProgress(mLastProgress); + mStartPending = false; - mStartPending = true; - Runnable action = () -> { - if (!mQuickScrubController.prepareQuickScrub(TAG)) { - mInvalidated = true; - return; - } - mActivityHelper.onQuickInteractionStart(mActivity, null, true); - mQuickScrubController.onQuickScrubProgress(mLastProgress); - mStartPending = false; + if (mEndPending) { + mQuickScrubController.onQuickScrubEnd(); + mEndPending = false; + } + }; - if (mEndPending) { - mQuickScrubController.onQuickScrubEnd(); - mEndPending = false; - } - }; - - mActivityHelper.executeOnWindowAvailable(mActivity, action); - } + mActivityHelper.executeOnWindowAvailable(mActivity, action); } @Override public void onQuickScrubEnd() { + mTouchInteractionLog.endQuickScrub("onQuickScrubEnd"); if (mInvalidated) { return; } @@ -422,6 +441,7 @@ public class TouchInteractionService extends Service { @Override public void onQuickScrubProgress(float progress) { + mTouchInteractionLog.setQuickScrubProgress(progress); mLastProgress = progress; if (mInvalidated || mStartPending) { return; @@ -429,13 +449,14 @@ public class TouchInteractionService extends Service { mQuickScrubController.onQuickScrubProgress(progress); } - public static TouchConsumer newInstance( - ActivityControlHelper activityHelper, boolean startingInActivityBounds) { + public static TouchConsumer newInstance(ActivityControlHelper activityHelper, + boolean startingInActivityBounds, TouchInteractionLog touchInteractionLog) { BaseDraggingActivity activity = activityHelper.getCreatedActivity(); if (activity == null) { return TouchConsumer.NO_OP; } - return new OverviewTouchConsumer(activityHelper, activity, startingInActivityBounds); + return new OverviewTouchConsumer(activityHelper, activity, startingInActivityBounds, + touchInteractionLog); } } } diff --git a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java index a2e9af841b..9991552987 100644 --- a/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java +++ b/quickstep/src/com/android/quickstep/WindowTransformSwipeHandler.java @@ -198,6 +198,7 @@ public class WindowTransformSwipeHandler { private final Context mContext; private final ActivityControlHelper mActivityControlHelper; private final ActivityInitListener mActivityInitListener; + private final TouchInteractionLog mTouchInteractionLog; private final int mRunningTaskId; private final RunningTaskInfo mRunningTaskInfo; @@ -239,7 +240,8 @@ public class WindowTransformSwipeHandler { private Bundle mAssistData; WindowTransformSwipeHandler(int id, RunningTaskInfo runningTaskInfo, Context context, - long touchTimeMs, ActivityControlHelper controller) { + long touchTimeMs, ActivityControlHelper controller, + TouchInteractionLog touchInteractionLog) { this.id = id; mContext = context; mRunningTaskInfo = runningTaskInfo; @@ -248,6 +250,7 @@ public class WindowTransformSwipeHandler { mActivityControlHelper = controller; mActivityInitListener = mActivityControlHelper .createActivityInitListener(this::onActivityInit); + mTouchInteractionLog = touchInteractionLog; initStateCallbacks(); } @@ -319,7 +322,7 @@ public class WindowTransformSwipeHandler { this::notifyTransitionCancelled); mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_QUICK_SCRUB_START - | STATE_APP_CONTROLLER_RECEIVED, this::onQuickScrubStart); + | STATE_APP_CONTROLLER_RECEIVED, this::onQuickScrubStartUi); mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_QUICK_SCRUB_START | STATE_SCALED_CONTROLLER_RECENTS, this::onFinishedTransitionToQuickScrub); mStateCallback.addCallback(STATE_LAUNCHER_STARTED | STATE_CURRENT_TASK_FINISHED @@ -503,25 +506,6 @@ public class WindowTransformSwipeHandler { .getHighResThumbnailLoader().setVisible(true); } - public void updateInteractionType(@InteractionType int interactionType) { - if (mInteractionType != INTERACTION_NORMAL) { - throw new IllegalArgumentException( - "Can't change interaction type from " + mInteractionType); - } - if (interactionType != INTERACTION_QUICK_SCRUB) { - throw new IllegalArgumentException( - "Can't change interaction type to " + interactionType); - } - mInteractionType = interactionType; - mRecentsAnimationWrapper.runOnInit(this::shiftAnimationDestinationForQuickscrub); - - setStateOnUiThread(STATE_QUICK_SCRUB_START | STATE_GESTURE_COMPLETED); - - // Start the window animation without waiting for launcher. - animateToProgress(mCurrentShift.value, 1f, QUICK_SCRUB_FROM_APP_START_DURATION, LINEAR, - true /* goingToHome */); - } - private void shiftAnimationDestinationForQuickscrub() { TransformedRect tempRect = new TransformedRect(); mActivityControlHelper @@ -669,6 +653,7 @@ public class WindowTransformSwipeHandler { initTransitionEndpoints(dp); mRecentsAnimationWrapper.setController(controller, targets); + mTouchInteractionLog.startRecentsAnimationCallback(targets.apps.length); setStateOnUiThread(STATE_APP_CONTROLLER_RECEIVED); mPassedOverviewThreshold = false; @@ -678,6 +663,7 @@ public class WindowTransformSwipeHandler { mRecentsAnimationWrapper.setController(null, null); mActivityInitListener.unregister(); setStateOnUiThread(STATE_GESTURE_CANCELLED | STATE_HANDLER_INVALIDATED); + mTouchInteractionLog.cancelRecentsAnimation(); } public void onGestureStarted() { @@ -729,7 +715,8 @@ public class WindowTransformSwipeHandler { // Hide the task view, if not already hidden setTargetAlphaProvider(WindowTransformSwipeHandler::getHiddenTargetAlpha); - return OverviewTouchConsumer.newInstance(mActivityControlHelper, true); + return OverviewTouchConsumer.newInstance(mActivityControlHelper, true, + mTouchInteractionLog); } private void handleNormalGestureEnd(float endVelocity, boolean isFling) { @@ -854,6 +841,7 @@ public class WindowTransformSwipeHandler { @UiThread private void resumeLastTask() { mRecentsAnimationWrapper.finish(false /* toHome */, null); + mTouchInteractionLog.finishRecentsAnimation(false); } public void reset() { @@ -950,6 +938,7 @@ public class WindowTransformSwipeHandler { mRecentsAnimationWrapper.finish(true /* toHome */, () -> setStateOnUiThread(STATE_CURRENT_TASK_FINISHED)); } + mTouchInteractionLog.finishRecentsAnimation(true); } private void setupLauncherUiAfterSwipeUpAnimation() { @@ -969,7 +958,22 @@ public class WindowTransformSwipeHandler { reset(); } - private void onQuickScrubStart() { + public void onQuickScrubStart() { + if (mInteractionType != INTERACTION_NORMAL) { + throw new IllegalArgumentException( + "Can't change interaction type from " + mInteractionType); + } + mInteractionType = INTERACTION_QUICK_SCRUB; + mRecentsAnimationWrapper.runOnInit(this::shiftAnimationDestinationForQuickscrub); + + setStateOnUiThread(STATE_QUICK_SCRUB_START | STATE_GESTURE_COMPLETED); + + // Start the window animation without waiting for launcher. + animateToProgress(mCurrentShift.value, 1f, QUICK_SCRUB_FROM_APP_START_DURATION, LINEAR, + true /* goingToHome */); + } + + private void onQuickScrubStartUi() { if (!mQuickScrubController.prepareQuickScrub(TAG)) { mQuickScrubBlocked = true; setStateOnUiThread(STATE_RESUME_LAST_TASK | STATE_HANDLER_INVALIDATED); @@ -980,7 +984,8 @@ public class WindowTransformSwipeHandler { mLauncherTransitionController = null; } - mActivityControlHelper.onQuickInteractionStart(mActivity, mRunningTaskInfo, false); + mActivityControlHelper.onQuickInteractionStart(mActivity, mRunningTaskInfo, false, + mTouchInteractionLog); // Inform the last progress in case we skipped before. mQuickScrubController.onQuickScrubProgress(mCurrentQuickScrubProgress); diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index f3dec9a814..8ad3cee6f2 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -183,6 +183,10 @@ public abstract class RecentsView extends PagedView impl if (!mHandleTaskStackChanges) { return; } + + // Notify the quick scrub controller that a particular task has been removed + mQuickScrubController.onTaskRemoved(taskId); + BackgroundExecutor.get().submit(() -> { TaskView taskView = getTaskView(taskId); if (taskView == null) { diff --git a/quickstep/src/com/android/quickstep/views/TaskView.java b/quickstep/src/com/android/quickstep/views/TaskView.java index ee542d57ed..a0615f502d 100644 --- a/quickstep/src/com/android/quickstep/views/TaskView.java +++ b/quickstep/src/com/android/quickstep/views/TaskView.java @@ -57,6 +57,7 @@ import com.android.systemui.shared.recents.model.Task.TaskCallbacks; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.ActivityOptionsCompat; import java.util.function.Consumer; /** @@ -204,11 +205,26 @@ public class TaskView extends FrameLayout implements TaskCallbacks, PageCallback if (animate) { opts = ((BaseDraggingActivity) fromContext(getContext())) .getActivityLaunchOptions(this); + ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(mTask.key, + opts, resultCallback, resultCallbackHandler); } else { - opts = ActivityOptions.makeCustomAnimation(getContext(), 0, 0); + opts = ActivityOptionsCompat.makeCustomAnimation(getContext(), 0, 0, () -> { + if (resultCallback != null) { + // Only post the animation start after the system has indicated that the + // transition has started + resultCallbackHandler.post(() -> resultCallback.accept(true)); + } + }, resultCallbackHandler); + ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(mTask.key, + opts, (success) -> { + if (resultCallback != null && !success) { + // If the call to start activity failed, then post the result + // immediately, otherwise, wait for the animation start callback + // from the activity options above + resultCallbackHandler.post(() -> resultCallback.accept(false)); + } + }, resultCallbackHandler); } - ActivityManagerWrapper.getInstance().startActivityFromRecentsAsync(mTask.key, - opts, resultCallback, resultCallbackHandler); } } diff --git a/res/drawable-v26/adaptive_icon_drawable_wrapper.xml b/res/drawable-v26/adaptive_icon_drawable_wrapper.xml index 2d78b699f4..9f13cf5719 100644 --- a/res/drawable-v26/adaptive_icon_drawable_wrapper.xml +++ b/res/drawable-v26/adaptive_icon_drawable_wrapper.xml @@ -17,6 +17,6 @@ - + diff --git a/src/com/android/launcher3/AutoInstallsLayout.java b/src/com/android/launcher3/AutoInstallsLayout.java index 6b0a90a78a..4c34071a92 100644 --- a/src/com/android/launcher3/AutoInstallsLayout.java +++ b/src/com/android/launcher3/AutoInstallsLayout.java @@ -39,7 +39,7 @@ import android.util.Patterns; import com.android.launcher3.LauncherProvider.SqlArguments; import com.android.launcher3.LauncherSettings.Favorites; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.util.Thunk; import org.xmlpull.v1.XmlPullParser; diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 8e6703cc5a..8513d63a78 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -272,8 +272,8 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, /** * Overrides the default long press timeout. */ - public void setLongPressTimeout(int longPressTimeout) { - mLongPressHelper.setLongPressTimeout(longPressTimeout); + public void setLongPressTimeoutFactor(float longPressTimeoutFactor) { + mLongPressHelper.setLongPressTimeoutFactor(longPressTimeoutFactor); } @Override diff --git a/src/com/android/launcher3/CheckLongPressHelper.java b/src/com/android/launcher3/CheckLongPressHelper.java index dde733cd16..639c173dcc 100644 --- a/src/com/android/launcher3/CheckLongPressHelper.java +++ b/src/com/android/launcher3/CheckLongPressHelper.java @@ -17,17 +17,18 @@ package com.android.launcher3; import android.view.View; +import android.view.ViewConfiguration; import com.android.launcher3.util.Thunk; public class CheckLongPressHelper { - public static final int DEFAULT_LONG_PRESS_TIMEOUT = 300; + public static final float DEFAULT_LONG_PRESS_TIMEOUT_FACTOR = 0.75f; @Thunk View mView; @Thunk View.OnLongClickListener mListener; @Thunk boolean mHasPerformedLongPress; - private int mLongPressTimeout = DEFAULT_LONG_PRESS_TIMEOUT; + private float mLongPressTimeoutFactor = DEFAULT_LONG_PRESS_TIMEOUT_FACTOR; private CheckForLongPress mPendingCheckForLongPress; class CheckForLongPress implements Runnable { @@ -60,8 +61,8 @@ public class CheckLongPressHelper { /** * Overrides the default long press timeout. */ - public void setLongPressTimeout(int longPressTimeout) { - mLongPressTimeout = longPressTimeout; + public void setLongPressTimeoutFactor(float longPressTimeoutFactor) { + mLongPressTimeoutFactor = longPressTimeoutFactor; } public void postCheckForLongPress() { @@ -70,7 +71,8 @@ public class CheckLongPressHelper { if (mPendingCheckForLongPress == null) { mPendingCheckForLongPress = new CheckForLongPress(); } - mView.postDelayed(mPendingCheckForLongPress, mLongPressTimeout); + mView.postDelayed(mPendingCheckForLongPress, + (long) (ViewConfiguration.getLongPressTimeout() * mLongPressTimeoutFactor)); } public void cancelLongPress() { diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index 9839c12dc1..256fd6c80c 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -30,7 +30,7 @@ import android.view.WindowManager; import com.android.launcher3.CellLayout.ContainerType; import com.android.launcher3.badge.BadgeRenderer; -import com.android.launcher3.graphics.IconNormalizer; +import com.android.launcher3.icons.IconNormalizer; public class DeviceProfile { @@ -373,7 +373,7 @@ public class DeviceProfile { updateFolderCellSize(1f, dm, res); // Don't let the folder get too close to the edges of the screen. - int folderMargin = edgeMarginPx; + int folderMargin = edgeMarginPx * 2; Point totalWorkspacePadding = getTotalWorkspacePadding(); // Check if the icons fit within the available height. diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java index 7efb6ec945..daf587a736 100644 --- a/src/com/android/launcher3/FastBitmapDrawable.java +++ b/src/com/android/launcher3/FastBitmapDrawable.java @@ -34,7 +34,7 @@ import android.graphics.drawable.Drawable; import android.util.Property; import android.util.SparseArray; -import com.android.launcher3.graphics.BitmapInfo; +import com.android.launcher3.icons.BitmapInfo; public class FastBitmapDrawable extends Drawable { diff --git a/src/com/android/launcher3/InstallShortcutReceiver.java b/src/com/android/launcher3/InstallShortcutReceiver.java index b9d45fb02e..7190f12779 100644 --- a/src/com/android/launcher3/InstallShortcutReceiver.java +++ b/src/com/android/launcher3/InstallShortcutReceiver.java @@ -40,8 +40,8 @@ import android.util.Pair; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.graphics.BitmapInfo; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.shortcuts.ShortcutKey; diff --git a/src/com/android/launcher3/ItemInfoWithIcon.java b/src/com/android/launcher3/ItemInfoWithIcon.java index 2ceb0dd71a..6d453c9b38 100644 --- a/src/com/android/launcher3/ItemInfoWithIcon.java +++ b/src/com/android/launcher3/ItemInfoWithIcon.java @@ -16,7 +16,7 @@ package com.android.launcher3; -import static com.android.launcher3.graphics.BitmapInfo.LOW_RES_ICON; +import static com.android.launcher3.icons.BitmapInfo.LOW_RES_ICON; import android.graphics.Bitmap; diff --git a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java index 80758c99b9..b2b05b17bb 100644 --- a/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java +++ b/src/com/android/launcher3/LauncherAppWidgetProviderInfo.java @@ -2,11 +2,15 @@ package com.android.launcher3; import android.appwidget.AppWidgetHostView; import android.appwidget.AppWidgetProviderInfo; +import android.content.ComponentName; import android.content.Context; import android.content.pm.PackageManager; import android.graphics.Point; import android.graphics.Rect; import android.os.Parcel; +import android.os.UserHandle; + +import com.android.launcher3.icons.ComponentWithLabel; /** * This class is a thin wrapper around the framework AppWidgetProviderInfo class. This class affords @@ -14,7 +18,8 @@ import android.os.Parcel; * (who's implementation is owned by the launcher). This object represents a widget type / class, * as opposed to a widget instance, and so should not be confused with {@link LauncherAppWidgetInfo} */ -public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo { +public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo + implements ComponentWithLabel { public static final String CLS_CUSTOM_WIDGET_PREFIX = "#custom-widget-"; @@ -102,4 +107,14 @@ public class LauncherAppWidgetProviderInfo extends AppWidgetProviderInfo { return 0; } } - } + + @Override + public final ComponentName getComponent() { + return provider; + } + + @Override + public final UserHandle getUser() { + return getProfile(); + } +} diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java index df1c693382..5e09f8adc5 100644 --- a/src/com/android/launcher3/LauncherModel.java +++ b/src/com/android/launcher3/LauncherModel.java @@ -38,7 +38,7 @@ import android.util.Pair; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.PackageInstallerCompat.PackageInstallInfo; import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.AddWorkspaceItemsTask; import com.android.launcher3.model.BaseModelUpdateTask; @@ -599,6 +599,19 @@ public class LauncherModel extends BroadcastReceiver CacheDataUpdatedTask.OP_CACHE_UPDATE, user, updatedPackages)); } + /** + * Called when the labels for the widgets has updated in the icon cache. + */ + public void onWidgetLabelsUpdated(HashSet updatedPackages, UserHandle user) { + enqueueModelUpdateTask(new BaseModelUpdateTask() { + @Override + public void execute(LauncherAppState app, BgDataModel dataModel, AllAppsList apps) { + dataModel.widgetsModel.onPackageIconsUpdated(updatedPackages, user, app); + bindUpdatedWidgets(dataModel); + } + }); + } + public void enqueueModelUpdateTask(ModelUpdateTask task) { task.init(mApp, this, sBgDataModel, mBgAllAppsList, mUiExecutor); runOnWorkerThread(task); diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java index cf497fd876..d47dcee92c 100644 --- a/src/com/android/launcher3/WidgetPreviewLoader.java +++ b/src/com/android/launcher3/WidgetPreviewLoader.java @@ -31,8 +31,8 @@ import android.util.LongSparseArray; import com.android.launcher3.compat.AppWidgetManagerCompat; import com.android.launcher3.compat.ShortcutConfigActivityInfo; import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.graphics.LauncherIcons; -import com.android.launcher3.graphics.ShadowGenerator; +import com.android.launcher3.icons.LauncherIcons; +import com.android.launcher3.icons.ShadowGenerator; import com.android.launcher3.icons.IconCache; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.util.ComponentKey; diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java index 0f13550199..69b4bdb1df 100644 --- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java +++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java @@ -22,7 +22,6 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnFocusChangeListener; -import android.view.ViewConfiguration; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.widget.TextView; @@ -252,7 +251,7 @@ public class AllAppsGridAdapter extends RecyclerView.Adapter new WidgetItem(shortcutInfo, mApp.getIconCache(), getPackageManager())); } private boolean setupWidget() { @@ -207,18 +209,32 @@ public class AddItemActivity extends BaseActivity implements OnLongClickListener mAppWidgetManager = AppWidgetManagerCompat.getInstance(this); mAppWidgetHost = new LauncherAppWidgetHost(this); - mPendingWidgetInfo = new PendingAddWidgetInfo(widgetInfo); - mPendingWidgetInfo.spanX = Math.min(mIdp.numColumns, widgetInfo.spanX); - mPendingWidgetInfo.spanY = Math.min(mIdp.numRows, widgetInfo.spanY); - mWidgetOptions = WidgetHostViewLoader.getDefaultOptionsForWidget(this, mPendingWidgetInfo); + PendingAddWidgetInfo pendingInfo = new PendingAddWidgetInfo(widgetInfo); + pendingInfo.spanX = Math.min(mIdp.numColumns, widgetInfo.spanX); + pendingInfo.spanY = Math.min(mIdp.numRows, widgetInfo.spanY); + mWidgetOptions = WidgetHostViewLoader.getDefaultOptionsForWidget(this, pendingInfo); + mWidgetCell.getWidgetView().setTag(pendingInfo); - WidgetItem item = new WidgetItem(widgetInfo, getPackageManager(), mIdp); - mWidgetCell.getWidgetView().setTag(mPendingWidgetInfo); - mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache()); - mWidgetCell.ensurePreview(); + applyWidgetItemAsync(() -> new WidgetItem(widgetInfo, mIdp, mApp.getIconCache())); return true; } + private void applyWidgetItemAsync(final Provider itemProvider) { + new AsyncTask() { + @Override + protected WidgetItem doInBackground(Void... voids) { + return itemProvider.get(); + } + + @Override + protected void onPostExecute(WidgetItem item) { + mWidgetCell.applyFromCellItem(item, mApp.getWidgetCache()); + mWidgetCell.ensurePreview(); + } + }.executeOnExecutor(new LooperExecutor(LauncherModel.getWorkerLooper())); + // TODO: Create a worker looper executor and reuse that everywhere. + } + /** * Called when the cancel button is clicked. */ diff --git a/src/com/android/launcher3/dragndrop/DragView.java b/src/com/android/launcher3/dragndrop/DragView.java index 6ac56ebed3..6d1efd5879 100644 --- a/src/com/android/launcher3/dragndrop/DragView.java +++ b/src/com/android/launcher3/dragndrop/DragView.java @@ -56,7 +56,7 @@ import com.android.launcher3.FirstFrameAnimatorHelper; import com.android.launcher3.anim.Interpolators; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.ShortcutConfigActivityInfo; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.shortcuts.ShortcutKey; diff --git a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java index bd919bcbcb..64655cc056 100644 --- a/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java +++ b/src/com/android/launcher3/dragndrop/PinShortcutRequestActivityInfo.java @@ -22,6 +22,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.pm.LauncherApps; import android.content.pm.LauncherApps.PinItemRequest; +import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; import android.graphics.drawable.Drawable; import android.os.Build; @@ -65,7 +66,7 @@ class PinShortcutRequestActivityInfo extends ShortcutConfigActivityInfo { } @Override - public CharSequence getLabel() { + public CharSequence getLabel(PackageManager pm) { return mInfo.getShortLabel(); } diff --git a/src/com/android/launcher3/graphics/DrawableFactory.java b/src/com/android/launcher3/graphics/DrawableFactory.java index 8377adff89..ce83a177a8 100644 --- a/src/com/android/launcher3/graphics/DrawableFactory.java +++ b/src/com/android/launcher3/graphics/DrawableFactory.java @@ -16,8 +16,6 @@ package com.android.launcher3.graphics; -import static com.android.launcher3.graphics.BitmapInfo.LOW_RES_ICON; - import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Resources; @@ -37,6 +35,7 @@ import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.R; import com.android.launcher3.Utilities; +import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.util.MainThreadInitializedObject; import com.android.launcher3.util.ResourceBasedOverride; @@ -66,7 +65,7 @@ public class DrawableFactory implements ResourceBasedOverride { * Returns a FastBitmapDrawable with the icon. */ public FastBitmapDrawable newIcon(Context context, ItemInfoWithIcon info) { - FastBitmapDrawable drawable = info.iconBitmap == LOW_RES_ICON + FastBitmapDrawable drawable = info.usingLowResIcon() ? new PlaceHolderIconDrawable(info, getPreloadProgressPath(), context) : new FastBitmapDrawable(info); drawable.setIsDisabled(info.isDisabled()); @@ -74,7 +73,7 @@ public class DrawableFactory implements ResourceBasedOverride { } public FastBitmapDrawable newIcon(Context context, BitmapInfo info, ActivityInfo target) { - return info.icon == LOW_RES_ICON + return info.isLowRes() ? new PlaceHolderIconDrawable(info, getPreloadProgressPath(), context) : new FastBitmapDrawable(info); } diff --git a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java index 18efd47b6e..5f2fb596af 100644 --- a/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java +++ b/src/com/android/launcher3/graphics/PlaceHolderIconDrawable.java @@ -24,6 +24,7 @@ import android.graphics.Rect; import com.android.launcher3.FastBitmapDrawable; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.R; +import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.util.Themes; /** diff --git a/src/com/android/launcher3/icons/BaseIconCache.java b/src/com/android/launcher3/icons/BaseIconCache.java new file mode 100644 index 0000000000..6433103f94 --- /dev/null +++ b/src/com/android/launcher3/icons/BaseIconCache.java @@ -0,0 +1,538 @@ +/* + * Copyright (C) 2018 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.icons; + +import static com.android.launcher3.icons.BitmapInfo.LOW_RES_ICON; + +import android.content.ComponentName; +import android.content.ContentValues; +import android.content.Context; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.LauncherActivityInfo; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; +import android.os.Build.VERSION; +import android.os.Handler; +import android.os.Process; +import android.os.UserHandle; +import android.text.TextUtils; +import android.util.Log; + +import com.android.launcher3.IconProvider; +import com.android.launcher3.ItemInfoWithIcon; +import com.android.launcher3.LauncherFiles; +import com.android.launcher3.LauncherModel; +import com.android.launcher3.MainThreadExecutor; +import com.android.launcher3.Utilities; +import com.android.launcher3.compat.LauncherAppsCompat; +import com.android.launcher3.compat.UserManagerCompat; +import com.android.launcher3.graphics.BitmapRenderer; +import com.android.launcher3.model.PackageItemInfo; +import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.InstantAppResolver; +import com.android.launcher3.util.Preconditions; +import com.android.launcher3.util.Provider; +import com.android.launcher3.util.SQLiteCacheHelper; + +import java.util.HashMap; +import java.util.HashSet; + +import androidx.annotation.NonNull; +import androidx.core.graphics.ColorUtils; + +public class BaseIconCache { + + private static final String TAG = "BaseIconCache"; + private static final boolean DEBUG = false; + private static final boolean DEBUG_IGNORE_CACHE = false; + + private static final int INITIAL_ICON_CACHE_CAPACITY = 50; + + // Empty class name is used for storing package default entry. + public static final String EMPTY_CLASS_NAME = "."; + + public static class CacheEntry extends BitmapInfo { + public CharSequence title = ""; + public CharSequence contentDescription = ""; + } + + private final HashMap mDefaultIcons = new HashMap<>(); + + final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor(); + final Context mContext; + final PackageManager mPackageManager; + final IconProvider mIconProvider; + final UserManagerCompat mUserManager; + final LauncherAppsCompat mLauncherApps; + + private final HashMap mCache = + new HashMap<>(INITIAL_ICON_CACHE_CAPACITY); + private final InstantAppResolver mInstantAppResolver; + final int mIconDpi; + + final IconDB mIconDb; + final Handler mWorkerHandler; + + private final BitmapFactory.Options mDecodeOptions; + + public BaseIconCache(Context context, int iconDpi, int iconPixelSize) { + mContext = context; + mPackageManager = context.getPackageManager(); + mUserManager = UserManagerCompat.getInstance(mContext); + mLauncherApps = LauncherAppsCompat.getInstance(mContext); + mInstantAppResolver = InstantAppResolver.newInstance(mContext); + mIconDpi = iconDpi; + mIconDb = new IconDB(context, iconPixelSize); + + mIconProvider = IconProvider.newInstance(context); + mWorkerHandler = new Handler(LauncherModel.getWorkerLooper()); + + if (BitmapRenderer.USE_HARDWARE_BITMAP) { + mDecodeOptions = new BitmapFactory.Options(); + mDecodeOptions.inPreferredConfig = Bitmap.Config.HARDWARE; + } else { + mDecodeOptions = null; + } + } + + private Drawable getFullResDefaultActivityIcon() { + return Resources.getSystem().getDrawableForDensity(Utilities.ATLEAST_OREO + ? android.R.drawable.sym_def_app_icon : android.R.mipmap.sym_def_app_icon, + mIconDpi); + } + + private Drawable getFullResIcon(Resources resources, int iconId) { + if (resources != null && iconId != 0) { + try { + return resources.getDrawableForDensity(iconId, mIconDpi); + } catch (Resources.NotFoundException e) { } + } + return getFullResDefaultActivityIcon(); + } + + public Drawable getFullResIcon(String packageName, int iconId) { + try { + return getFullResIcon(mPackageManager.getResourcesForApplication(packageName), iconId); + } catch (PackageManager.NameNotFoundException e) { } + return getFullResDefaultActivityIcon(); + } + + public Drawable getFullResIcon(ActivityInfo info) { + try { + return getFullResIcon(mPackageManager.getResourcesForApplication(info.applicationInfo), + info.getIconResource()); + } catch (PackageManager.NameNotFoundException e) { } + return getFullResDefaultActivityIcon(); + } + + public Drawable getFullResIcon(LauncherActivityInfo info) { + return getFullResIcon(info, true); + } + + public Drawable getFullResIcon(LauncherActivityInfo info, boolean flattenDrawable) { + return mIconProvider.getIcon(info, mIconDpi, flattenDrawable); + } + + protected BitmapInfo makeDefaultIcon(UserHandle user) { + try (LauncherIcons li = LauncherIcons.obtain(mContext)) { + return li.createBadgedIconBitmap( + getFullResDefaultActivityIcon(), user, VERSION.SDK_INT); + } + } + + /** + * Remove any records for the supplied ComponentName. + */ + public synchronized void remove(ComponentName componentName, UserHandle user) { + mCache.remove(new ComponentKey(componentName, user)); + } + + /** + * Remove any records for the supplied package name from memory. + */ + private void removeFromMemCacheLocked(String packageName, UserHandle user) { + HashSet forDeletion = new HashSet<>(); + for (ComponentKey key: mCache.keySet()) { + if (key.componentName.getPackageName().equals(packageName) + && key.user.equals(user)) { + forDeletion.add(key); + } + } + for (ComponentKey condemned: forDeletion) { + mCache.remove(condemned); + } + } + + /** + * Removes the entries related to the given package in memory and persistent DB. + */ + public synchronized void removeIconsForPkg(String packageName, UserHandle user) { + removeFromMemCacheLocked(packageName, user); + long userSerial = mUserManager.getSerialNumberForUser(user); + mIconDb.delete( + IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?", + new String[]{packageName + "/%", Long.toString(userSerial)}); + } + + public IconCacheUpdateHandler getUpdateHandler() { + mIconProvider.updateSystemStateString(mContext); + return new IconCacheUpdateHandler(this); + } + + /** + * Adds an entry into the DB and the in-memory cache. + * @param replaceExisting if true, it will recreate the bitmap even if it already exists in + * the memory. This is useful then the previous bitmap was created using + * old data. + * package private + */ + synchronized void addIconToDBAndMemCache(T object, CachingLogic cachingLogic, + PackageInfo info, long userSerial, boolean replaceExisting) { + UserHandle user = cachingLogic.getUser(object); + ComponentName componentName = cachingLogic.getComponent(object); + + final ComponentKey key = new ComponentKey(componentName, user); + CacheEntry entry = null; + if (!replaceExisting) { + entry = mCache.get(key); + // We can't reuse the entry if the high-res icon is not present. + if (entry == null || entry.icon == null || entry.isLowRes()) { + entry = null; + } + } + if (entry == null) { + entry = new CacheEntry(); + cachingLogic.loadIcon(mContext, this, object, entry); + } + entry.title = cachingLogic.getLabel(object, mPackageManager); + entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user); + mCache.put(key, entry); + + ContentValues values = newContentValues(entry, entry.title.toString(), + componentName.getPackageName()); + addIconToDB(values, componentName, info, userSerial); + } + + /** + * Updates {@param values} to contain versioning information and adds it to the DB. + * @param values {@link ContentValues} containing icon & title + */ + private void addIconToDB(ContentValues values, ComponentName key, + PackageInfo info, long userSerial) { + values.put(IconDB.COLUMN_COMPONENT, key.flattenToString()); + values.put(IconDB.COLUMN_USER, userSerial); + values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime); + values.put(IconDB.COLUMN_VERSION, info.versionCode); + mIconDb.insertOrReplace(values); + } + + /** + * Fill in {@param infoInOut} with the corresponding icon and label. + */ + public synchronized void getTitleAndIconForApp( + PackageItemInfo infoInOut, boolean useLowResIcon) { + CacheEntry entry = getEntryForPackageLocked( + infoInOut.packageName, infoInOut.user, useLowResIcon); + applyCacheEntry(entry, infoInOut); + } + + protected void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) { + info.title = Utilities.trim(entry.title); + info.contentDescription = entry.contentDescription; + ((entry.icon == null) ? getDefaultIcon(info.user) : entry).applyTo(info); + } + + public synchronized BitmapInfo getDefaultIcon(UserHandle user) { + if (!mDefaultIcons.containsKey(user)) { + mDefaultIcons.put(user, makeDefaultIcon(user)); + } + return mDefaultIcons.get(user); + } + + public boolean isDefaultIcon(Bitmap icon, UserHandle user) { + return getDefaultIcon(user).icon == icon; + } + + /** + * 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. + */ + protected CacheEntry cacheLocked( + @NonNull ComponentName componentName, @NonNull UserHandle user, + @NonNull Provider infoProvider, @NonNull CachingLogic cachingLogic, + boolean usePackageIcon, boolean useLowResIcon) { + return cacheLocked(componentName, user, infoProvider, cachingLogic, usePackageIcon, + useLowResIcon, true); + } + + protected CacheEntry cacheLocked( + @NonNull ComponentName componentName, @NonNull UserHandle user, + @NonNull Provider infoProvider, @NonNull CachingLogic cachingLogic, + boolean usePackageIcon, boolean useLowResIcon, boolean addToMemCache) { + Preconditions.assertWorkerThread(); + ComponentKey cacheKey = new ComponentKey(componentName, user); + CacheEntry entry = mCache.get(cacheKey); + if (entry == null || (entry.isLowRes() && !useLowResIcon)) { + entry = new CacheEntry(); + if (addToMemCache) { + mCache.put(cacheKey, entry); + } + + // Check the DB first. + T object = null; + boolean providerFetchedOnce = false; + + if (!getEntryFromDB(cacheKey, entry, useLowResIcon) || DEBUG_IGNORE_CACHE) { + object = infoProvider.get(); + providerFetchedOnce = true; + + if (object != null) { + cachingLogic.loadIcon(mContext, this, object, entry); + } else { + if (usePackageIcon) { + CacheEntry packageEntry = getEntryForPackageLocked( + componentName.getPackageName(), user, false); + if (packageEntry != null) { + if (DEBUG) Log.d(TAG, "using package default icon for " + + componentName.toShortString()); + packageEntry.applyTo(entry); + entry.title = packageEntry.title; + entry.contentDescription = packageEntry.contentDescription; + } + } + if (entry.icon == null) { + if (DEBUG) Log.d(TAG, "using default icon for " + + componentName.toShortString()); + getDefaultIcon(user).applyTo(entry); + } + } + } + + if (TextUtils.isEmpty(entry.title)) { + if (object == null && !providerFetchedOnce) { + object = infoProvider.get(); + providerFetchedOnce = true; + } + if (object != null) { + entry.title = cachingLogic.getLabel(object, mPackageManager); + entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user); + } + } + } + return entry; + } + + public synchronized void clear() { + Preconditions.assertWorkerThread(); + mIconDb.clear(); + } + + /** + * Adds a default package entry in the cache. This entry is not persisted and will be removed + * when the cache is flushed. + */ + public synchronized void cachePackageInstallInfo(String packageName, UserHandle user, + Bitmap icon, CharSequence title) { + removeFromMemCacheLocked(packageName, user); + + ComponentKey cacheKey = getPackageKey(packageName, user); + CacheEntry entry = mCache.get(cacheKey); + + // For icon caching, do not go through DB. Just update the in-memory entry. + if (entry == null) { + entry = new CacheEntry(); + } + if (!TextUtils.isEmpty(title)) { + entry.title = title; + } + if (icon != null) { + LauncherIcons li = LauncherIcons.obtain(mContext); + li.createIconBitmap(icon).applyTo(entry); + li.recycle(); + } + if (!TextUtils.isEmpty(title) && entry.icon != null) { + mCache.put(cacheKey, entry); + } + } + + private static ComponentKey getPackageKey(String packageName, UserHandle user) { + ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME); + return new ComponentKey(cn, user); + } + + /** + * Gets an entry for the package, which can be used as a fallback entry for various components. + * This method is not thread safe, it must be called from a synchronized method. + */ + private CacheEntry getEntryForPackageLocked(String packageName, UserHandle user, + boolean useLowResIcon) { + Preconditions.assertWorkerThread(); + ComponentKey cacheKey = getPackageKey(packageName, user); + CacheEntry entry = mCache.get(cacheKey); + + if (entry == null || (entry.isLowRes() && !useLowResIcon)) { + entry = new CacheEntry(); + boolean entryUpdated = true; + + // Check the DB first. + if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) { + try { + int flags = Process.myUserHandle().equals(user) ? 0 : + PackageManager.GET_UNINSTALLED_PACKAGES; + PackageInfo info = mPackageManager.getPackageInfo(packageName, flags); + ApplicationInfo appInfo = info.applicationInfo; + if (appInfo == null) { + throw new NameNotFoundException("ApplicationInfo is null"); + } + + LauncherIcons li = LauncherIcons.obtain(mContext); + // Load the full res icon for the application, but if useLowResIcon is set, then + // only keep the low resolution icon instead of the larger full-sized icon + BitmapInfo iconInfo = li.createBadgedIconBitmap( + appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion, + mInstantAppResolver.isInstantApp(appInfo)); + li.recycle(); + + entry.title = appInfo.loadLabel(mPackageManager); + entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user); + entry.icon = useLowResIcon ? LOW_RES_ICON : iconInfo.icon; + entry.color = iconInfo.color; + + // Add the icon in the DB here, since these do not get written during + // package updates. + ContentValues values = newContentValues( + iconInfo, entry.title.toString(), packageName); + addIconToDB(values, cacheKey.componentName, info, + mUserManager.getSerialNumberForUser(user)); + + } catch (NameNotFoundException e) { + if (DEBUG) Log.d(TAG, "Application not installed " + packageName); + entryUpdated = false; + } + } + + // Only add a filled-out entry to the cache + if (entryUpdated) { + mCache.put(cacheKey, entry); + } + } + return entry; + } + + private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) { + Cursor c = null; + try { + c = mIconDb.query( + lowRes ? IconDB.COLUMNS_LOW_RES : IconDB.COLUMNS_HIGH_RES, + IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?", + new String[]{ + cacheKey.componentName.flattenToString(), + Long.toString(mUserManager.getSerialNumberForUser(cacheKey.user))}); + if (c.moveToNext()) { + // Set the alpha to be 255, so that we never have a wrong color + entry.color = ColorUtils.setAlphaComponent(c.getInt(0), 255); + entry.title = c.getString(1); + if (entry.title == null) { + entry.title = ""; + entry.contentDescription = ""; + } else { + entry.contentDescription = mUserManager.getBadgedLabelForUser( + entry.title, cacheKey.user); + } + + if (lowRes) { + entry.icon = LOW_RES_ICON; + } else { + byte[] data = c.getBlob(2); + try { + entry.icon = BitmapFactory.decodeByteArray(data, 0, data.length, + mDecodeOptions); + } catch (Exception e) { } + } + return true; + } + } catch (SQLiteException e) { + Log.d(TAG, "Error reading icon cache", e); + } finally { + if (c != null) { + c.close(); + } + } + return false; + } + + static final class IconDB extends SQLiteCacheHelper { + private final static int RELEASE_VERSION = 25; + + public final static String TABLE_NAME = "icons"; + public final static String COLUMN_ROWID = "rowid"; + public final static String COLUMN_COMPONENT = "componentName"; + public final static String COLUMN_USER = "profileId"; + public final static String COLUMN_LAST_UPDATED = "lastUpdated"; + public final static String COLUMN_VERSION = "version"; + public final static String COLUMN_ICON = "icon"; + public final static String COLUMN_ICON_COLOR = "icon_color"; + public final static String COLUMN_LABEL = "label"; + public final static String COLUMN_SYSTEM_STATE = "system_state"; + + public final static String[] COLUMNS_HIGH_RES = new String[] { + IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL, IconDB.COLUMN_ICON }; + public final static String[] COLUMNS_LOW_RES = new String[] { + IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL }; + + public IconDB(Context context, int iconPixelSize) { + super(context, LauncherFiles.APP_ICONS_DB, + (RELEASE_VERSION << 16) + iconPixelSize, + TABLE_NAME); + } + + @Override + protected void onCreateTable(SQLiteDatabase db) { + db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" + + COLUMN_COMPONENT + " TEXT NOT NULL, " + + COLUMN_USER + " INTEGER NOT NULL, " + + COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " + + COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " + + COLUMN_ICON + " BLOB, " + + COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, " + + COLUMN_LABEL + " TEXT, " + + COLUMN_SYSTEM_STATE + " TEXT, " + + "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " + + ");"); + } + } + + private ContentValues newContentValues(BitmapInfo bitmapInfo, String label, String packageName) { + ContentValues values = new ContentValues(); + values.put(IconDB.COLUMN_ICON, + bitmapInfo.isLowRes() ? null : Utilities.flattenBitmap(bitmapInfo.icon)); + values.put(IconDB.COLUMN_ICON_COLOR, bitmapInfo.color); + + values.put(IconDB.COLUMN_LABEL, label); + values.put(IconDB.COLUMN_SYSTEM_STATE, mIconProvider.getIconSystemState(packageName)); + + return values; + } +} diff --git a/src/com/android/launcher3/graphics/BitmapInfo.java b/src/com/android/launcher3/icons/BitmapInfo.java similarity index 92% rename from src/com/android/launcher3/graphics/BitmapInfo.java rename to src/com/android/launcher3/icons/BitmapInfo.java index 69c068412a..ebe05113e8 100644 --- a/src/com/android/launcher3/graphics/BitmapInfo.java +++ b/src/com/android/launcher3/icons/BitmapInfo.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.launcher3.graphics; +package com.android.launcher3.icons; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; @@ -37,6 +37,10 @@ public class BitmapInfo { info.color = color; } + public final boolean isLowRes() { + return LOW_RES_ICON == icon; + } + public static BitmapInfo fromBitmap(Bitmap bitmap) { return fromBitmap(bitmap, null); } diff --git a/src/com/android/launcher3/icons/CachingLogic.java b/src/com/android/launcher3/icons/CachingLogic.java new file mode 100644 index 0000000000..24186ef990 --- /dev/null +++ b/src/com/android/launcher3/icons/CachingLogic.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2018 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.icons; + +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.LauncherActivityInfo; +import android.content.pm.PackageManager; +import android.os.UserHandle; + +public interface CachingLogic { + + ComponentName getComponent(T object); + + UserHandle getUser(T object); + + CharSequence getLabel(T object, PackageManager pm); + + void loadIcon(Context context, BaseIconCache cache, T object, BitmapInfo target); + + CachingLogic LAUNCHER_ACTIVITY_INFO = + new CachingLogic() { + + @Override + public ComponentName getComponent(LauncherActivityInfo object) { + return object.getComponentName(); + } + + @Override + public UserHandle getUser(LauncherActivityInfo object) { + return object.getUser(); + } + + @Override + public CharSequence getLabel(LauncherActivityInfo object, PackageManager pm) { + return object.getLabel(); + } + + @Override + public void loadIcon(Context context, BaseIconCache cache, LauncherActivityInfo object, + BitmapInfo target) { + LauncherIcons li = LauncherIcons.obtain(context); + li.createBadgedIconBitmap(cache.getFullResIcon(object), object.getUser(), + object.getApplicationInfo().targetSdkVersion).applyTo(target); + li.recycle(); + } + }; + + CachingLogic COMPONENT_WITH_LABEL = + new CachingLogic() { + + @Override + public ComponentName getComponent(ComponentWithLabel object) { + return object.getComponent(); + } + + @Override + public UserHandle getUser(ComponentWithLabel object) { + return object.getUser(); + } + + @Override + public CharSequence getLabel(ComponentWithLabel object, PackageManager pm) { + return object.getLabel(pm); + } + + @Override + public void loadIcon(Context context, BaseIconCache cache, + ComponentWithLabel object, BitmapInfo target) { + // Do not load icon. + target.icon = BitmapInfo.LOW_RES_ICON; + } + }; +} diff --git a/src/com/android/launcher3/graphics/ColorExtractor.java b/src/com/android/launcher3/icons/ColorExtractor.java similarity index 99% rename from src/com/android/launcher3/graphics/ColorExtractor.java rename to src/com/android/launcher3/icons/ColorExtractor.java index da5da9cea6..87bda825cc 100644 --- a/src/com/android/launcher3/graphics/ColorExtractor.java +++ b/src/com/android/launcher3/icons/ColorExtractor.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.android.launcher3.graphics; +package com.android.launcher3.icons; import android.graphics.Bitmap; import android.graphics.Color; diff --git a/src/com/android/launcher3/icons/ComponentWithLabel.java b/src/com/android/launcher3/icons/ComponentWithLabel.java new file mode 100644 index 0000000000..2badb4c373 --- /dev/null +++ b/src/com/android/launcher3/icons/ComponentWithLabel.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2018 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.icons; + +import android.content.ComponentName; +import android.content.pm.PackageManager; +import android.os.UserHandle; + +public interface ComponentWithLabel { + + ComponentName getComponent(); + + UserHandle getUser(); + + CharSequence getLabel(PackageManager pm); +} diff --git a/src/com/android/launcher3/graphics/FixedScaleDrawable.java b/src/com/android/launcher3/icons/FixedScaleDrawable.java similarity index 97% rename from src/com/android/launcher3/graphics/FixedScaleDrawable.java rename to src/com/android/launcher3/icons/FixedScaleDrawable.java index 0f0e42428d..e594f477eb 100644 --- a/src/com/android/launcher3/graphics/FixedScaleDrawable.java +++ b/src/com/android/launcher3/icons/FixedScaleDrawable.java @@ -1,4 +1,4 @@ -package com.android.launcher3.graphics; +package com.android.launcher3.icons; import android.annotation.TargetApi; import android.content.res.Resources; diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java index 2035d0e57d..434945550c 100644 --- a/src/com/android/launcher3/icons/IconCache.java +++ b/src/com/android/launcher3/icons/IconCache.java @@ -16,209 +16,43 @@ package com.android.launcher3.icons; -import static com.android.launcher3.graphics.BitmapInfo.LOW_RES_ICON; +import static com.android.launcher3.icons.CachingLogic.COMPONENT_WITH_LABEL; +import static com.android.launcher3.icons.CachingLogic.LAUNCHER_ACTIVITY_INFO; -import android.content.ComponentName; -import android.content.ContentValues; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; import android.content.pm.LauncherActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Resources; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteException; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.Drawable; -import android.os.Build.VERSION; import android.os.Handler; import android.os.Process; import android.os.UserHandle; -import android.text.TextUtils; import android.util.Log; import com.android.launcher3.AppInfo; -import com.android.launcher3.IconProvider; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfoWithIcon; -import com.android.launcher3.LauncherFiles; import com.android.launcher3.LauncherModel; -import com.android.launcher3.MainThreadExecutor; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; -import com.android.launcher3.compat.LauncherAppsCompat; -import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.graphics.BitmapInfo; -import com.android.launcher3.graphics.BitmapRenderer; -import com.android.launcher3.graphics.LauncherIcons; import com.android.launcher3.model.PackageItemInfo; -import com.android.launcher3.util.ComponentKey; -import com.android.launcher3.util.InstantAppResolver; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.Provider; -import com.android.launcher3.util.SQLiteCacheHelper; - -import java.util.HashMap; -import java.util.HashSet; import androidx.annotation.NonNull; -import androidx.core.graphics.ColorUtils; /** * Cache of application icons. Icons can be made from any thread. */ -public class IconCache { +public class IconCache extends BaseIconCache { private static final String TAG = "Launcher.IconCache"; - private static final int INITIAL_ICON_CACHE_CAPACITY = 50; - - // Empty class name is used for storing package default entry. - public static final String EMPTY_CLASS_NAME = "."; - - private static final boolean DEBUG = false; - private static final boolean DEBUG_IGNORE_CACHE = false; - - public static class CacheEntry extends BitmapInfo { - public CharSequence title = ""; - public CharSequence contentDescription = ""; - - public boolean isLowRes() { - return LOW_RES_ICON == icon; - } - } - - private final HashMap mDefaultIcons = new HashMap<>(); - - final MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor(); - final Context mContext; - final PackageManager mPackageManager; - final IconProvider mIconProvider; - final UserManagerCompat mUserManager; - final LauncherAppsCompat mLauncherApps; - - private final HashMap mCache = - new HashMap<>(INITIAL_ICON_CACHE_CAPACITY); - private final InstantAppResolver mInstantAppResolver; - private final int mIconDpi; - - final IconDB mIconDb; - final Handler mWorkerHandler; - - private final BitmapFactory.Options mDecodeOptions; - private int mPendingIconRequestCount = 0; public IconCache(Context context, InvariantDeviceProfile inv) { - mContext = context; - mPackageManager = context.getPackageManager(); - mUserManager = UserManagerCompat.getInstance(mContext); - mLauncherApps = LauncherAppsCompat.getInstance(mContext); - mInstantAppResolver = InstantAppResolver.newInstance(mContext); - mIconDpi = inv.fillResIconDpi; - mIconDb = new IconDB(context, inv.iconBitmapSize); - - mIconProvider = IconProvider.newInstance(context); - mWorkerHandler = new Handler(LauncherModel.getWorkerLooper()); - - if (BitmapRenderer.USE_HARDWARE_BITMAP) { - mDecodeOptions = new BitmapFactory.Options(); - mDecodeOptions.inPreferredConfig = Bitmap.Config.HARDWARE; - } else { - mDecodeOptions = null; - } - } - - private Drawable getFullResDefaultActivityIcon() { - return getFullResIcon(Resources.getSystem(), Utilities.ATLEAST_OREO ? - android.R.drawable.sym_def_app_icon : android.R.mipmap.sym_def_app_icon); - } - - private Drawable getFullResIcon(Resources resources, int iconId) { - Drawable d; - try { - d = resources.getDrawableForDensity(iconId, mIconDpi); - } catch (Resources.NotFoundException e) { - d = null; - } - - return (d != null) ? d : getFullResDefaultActivityIcon(); - } - - public Drawable getFullResIcon(String packageName, int iconId) { - Resources resources; - try { - resources = mPackageManager.getResourcesForApplication(packageName); - } catch (PackageManager.NameNotFoundException e) { - resources = null; - } - if (resources != null) { - if (iconId != 0) { - return getFullResIcon(resources, iconId); - } - } - return getFullResDefaultActivityIcon(); - } - - public Drawable getFullResIcon(ActivityInfo info) { - Resources resources; - try { - resources = mPackageManager.getResourcesForApplication( - info.applicationInfo); - } catch (PackageManager.NameNotFoundException e) { - resources = null; - } - if (resources != null) { - int iconId = info.getIconResource(); - if (iconId != 0) { - return getFullResIcon(resources, iconId); - } - } - - return getFullResDefaultActivityIcon(); - } - - public Drawable getFullResIcon(LauncherActivityInfo info) { - return getFullResIcon(info, true); - } - - public Drawable getFullResIcon(LauncherActivityInfo info, boolean flattenDrawable) { - return mIconProvider.getIcon(info, mIconDpi, flattenDrawable); - } - - protected BitmapInfo makeDefaultIcon(UserHandle user) { - try (LauncherIcons li = LauncherIcons.obtain(mContext)) { - return li.createBadgedIconBitmap( - getFullResDefaultActivityIcon(), user, VERSION.SDK_INT); - } - } - - /** - * Remove any records for the supplied ComponentName. - */ - public synchronized void remove(ComponentName componentName, UserHandle user) { - mCache.remove(new ComponentKey(componentName, user)); - } - - /** - * Remove any records for the supplied package name from memory. - */ - private void removeFromMemCacheLocked(String packageName, UserHandle user) { - HashSet forDeletion = new HashSet<>(); - for (ComponentKey key: mCache.keySet()) { - if (key.componentName.getPackageName().equals(packageName) - && key.user.equals(user)) { - forDeletion.add(key); - } - } - for (ComponentKey condemned: forDeletion) { - mCache.remove(condemned); - } + super(context, inv.fillResIconDpi, inv.iconBitmapSize); } /** @@ -231,76 +65,14 @@ public class IconCache { PackageManager.GET_UNINSTALLED_PACKAGES); long userSerial = mUserManager.getSerialNumberForUser(user); for (LauncherActivityInfo app : mLauncherApps.getActivityList(packageName, user)) { - addIconToDBAndMemCache(app, info, userSerial, false /*replace existing*/); + addIconToDBAndMemCache(app, LAUNCHER_ACTIVITY_INFO, info, userSerial, + false /*replace existing*/); } } catch (NameNotFoundException e) { Log.d(TAG, "Package not found", e); } } - /** - * Removes the entries related to the given package in memory and persistent DB. - */ - public synchronized void removeIconsForPkg(String packageName, UserHandle user) { - removeFromMemCacheLocked(packageName, user); - long userSerial = mUserManager.getSerialNumberForUser(user); - mIconDb.delete( - IconDB.COLUMN_COMPONENT + " LIKE ? AND " + IconDB.COLUMN_USER + " = ?", - new String[]{packageName + "/%", Long.toString(userSerial)}); - } - - public IconCacheUpdateHandler getUpdateHandler() { - mIconProvider.updateSystemStateString(mContext); - return new IconCacheUpdateHandler(this); - } - - /** - * Adds an entry into the DB and the in-memory cache. - * @param replaceExisting if true, it will recreate the bitmap even if it already exists in - * the memory. This is useful then the previous bitmap was created using - * old data. - * package private - */ - synchronized void addIconToDBAndMemCache(LauncherActivityInfo app, - PackageInfo info, long userSerial, boolean replaceExisting) { - final ComponentKey key = new ComponentKey(app.getComponentName(), app.getUser()); - CacheEntry entry = null; - if (!replaceExisting) { - entry = mCache.get(key); - // We can't reuse the entry if the high-res icon is not present. - if (entry == null || entry.icon == null || entry.isLowRes()) { - entry = null; - } - } - if (entry == null) { - entry = new CacheEntry(); - LauncherIcons li = LauncherIcons.obtain(mContext); - li.createBadgedIconBitmap(getFullResIcon(app), app.getUser(), - app.getApplicationInfo().targetSdkVersion).applyTo(entry); - li.recycle(); - } - entry.title = app.getLabel(); - entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, app.getUser()); - mCache.put(key, entry); - - ContentValues values = newContentValues(entry.icon, entry.color, - entry.title.toString(), app.getApplicationInfo().packageName); - addIconToDB(values, app.getComponentName(), info, userSerial); - } - - /** - * Updates {@param values} to contain versioning information and adds it to the DB. - * @param values {@link ContentValues} containing icon & title - */ - private void addIconToDB(ContentValues values, ComponentName key, - PackageInfo info, long userSerial) { - values.put(IconDB.COLUMN_COMPONENT, key.flattenToString()); - values.put(IconDB.COLUMN_USER, userSerial); - values.put(IconDB.COLUMN_LAST_UPDATED, info.lastUpdateTime); - values.put(IconDB.COLUMN_VERSION, info.versionCode); - mIconDb.insertOrReplace(values); - } - /** * Fetches high-res icon for the provided ItemInfo and updates the caller when done. * @return a request ID that can be used to cancel the request. @@ -343,8 +115,8 @@ public class IconCache { */ public synchronized void updateTitleAndIcon(AppInfo application) { CacheEntry entry = cacheLocked(application.componentName, - Provider.of(null), - application.user, false, application.usingLowResIcon()); + application.user, Provider.of(null), LAUNCHER_ACTIVITY_INFO, + false, application.usingLowResIcon()); if (entry.icon != null && !isDefaultIcon(entry.icon, application.user)) { applyCacheEntry(entry, application); } @@ -377,6 +149,13 @@ public class IconCache { } } + public synchronized String getTitleNoCache(ComponentWithLabel info) { + CacheEntry entry = cacheLocked(info.getComponent(), info.getUser(), Provider.of(info), + COMPONENT_WITH_LABEL, false /* usePackageIcon */, true /* useLowResIcon */, + false /* addToMemCache */); + return Utilities.trim(entry.title); + } + /** * Fill in {@param shortcutInfo} with the icon and label for {@param info} */ @@ -384,239 +163,12 @@ public class IconCache { @NonNull ItemInfoWithIcon infoInOut, @NonNull Provider activityInfoProvider, boolean usePkgIcon, boolean useLowResIcon) { - CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), activityInfoProvider, - infoInOut.user, usePkgIcon, useLowResIcon); + CacheEntry entry = cacheLocked(infoInOut.getTargetComponent(), infoInOut.user, + activityInfoProvider, + LAUNCHER_ACTIVITY_INFO, usePkgIcon, useLowResIcon); applyCacheEntry(entry, infoInOut); } - /** - * Fill in {@param infoInOut} with the corresponding icon and label. - */ - public synchronized void getTitleAndIconForApp( - PackageItemInfo infoInOut, boolean useLowResIcon) { - CacheEntry entry = getEntryForPackageLocked( - infoInOut.packageName, infoInOut.user, useLowResIcon); - applyCacheEntry(entry, infoInOut); - } - - private void applyCacheEntry(CacheEntry entry, ItemInfoWithIcon info) { - info.title = Utilities.trim(entry.title); - info.contentDescription = entry.contentDescription; - ((entry.icon == null) ? getDefaultIcon(info.user) : entry).applyTo(info); - } - - public synchronized BitmapInfo getDefaultIcon(UserHandle user) { - if (!mDefaultIcons.containsKey(user)) { - mDefaultIcons.put(user, makeDefaultIcon(user)); - } - return mDefaultIcons.get(user); - } - - public boolean isDefaultIcon(Bitmap icon, UserHandle user) { - return getDefaultIcon(user).icon == icon; - } - - /** - * 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. - */ - protected CacheEntry cacheLocked( - @NonNull ComponentName componentName, - @NonNull Provider infoProvider, - UserHandle user, boolean usePackageIcon, boolean useLowResIcon) { - Preconditions.assertWorkerThread(); - ComponentKey cacheKey = new ComponentKey(componentName, user); - CacheEntry entry = mCache.get(cacheKey); - if (entry == null || (entry.isLowRes() && !useLowResIcon)) { - entry = new CacheEntry(); - mCache.put(cacheKey, entry); - - // Check the DB first. - LauncherActivityInfo info = null; - boolean providerFetchedOnce = false; - - if (!getEntryFromDB(cacheKey, entry, useLowResIcon) || DEBUG_IGNORE_CACHE) { - info = infoProvider.get(); - providerFetchedOnce = true; - - if (info != null) { - LauncherIcons li = LauncherIcons.obtain(mContext); - li.createBadgedIconBitmap(getFullResIcon(info), info.getUser(), - info.getApplicationInfo().targetSdkVersion).applyTo(entry); - li.recycle(); - } else { - if (usePackageIcon) { - CacheEntry packageEntry = getEntryForPackageLocked( - componentName.getPackageName(), user, false); - if (packageEntry != null) { - if (DEBUG) Log.d(TAG, "using package default icon for " + - componentName.toShortString()); - packageEntry.applyTo(entry); - entry.title = packageEntry.title; - entry.contentDescription = packageEntry.contentDescription; - } - } - if (entry.icon == null) { - if (DEBUG) Log.d(TAG, "using default icon for " + - componentName.toShortString()); - getDefaultIcon(user).applyTo(entry); - } - } - } - - if (TextUtils.isEmpty(entry.title)) { - if (info == null && !providerFetchedOnce) { - info = infoProvider.get(); - providerFetchedOnce = true; - } - if (info != null) { - entry.title = info.getLabel(); - entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user); - } - } - } - return entry; - } - - public synchronized void clear() { - Preconditions.assertWorkerThread(); - mIconDb.clear(); - } - - /** - * Adds a default package entry in the cache. This entry is not persisted and will be removed - * when the cache is flushed. - */ - public synchronized void cachePackageInstallInfo(String packageName, UserHandle user, - Bitmap icon, CharSequence title) { - removeFromMemCacheLocked(packageName, user); - - ComponentKey cacheKey = getPackageKey(packageName, user); - CacheEntry entry = mCache.get(cacheKey); - - // For icon caching, do not go through DB. Just update the in-memory entry. - if (entry == null) { - entry = new CacheEntry(); - } - if (!TextUtils.isEmpty(title)) { - entry.title = title; - } - if (icon != null) { - LauncherIcons li = LauncherIcons.obtain(mContext); - li.createIconBitmap(icon).applyTo(entry); - li.recycle(); - } - if (!TextUtils.isEmpty(title) && entry.icon != null) { - mCache.put(cacheKey, entry); - } - } - - private static ComponentKey getPackageKey(String packageName, UserHandle user) { - ComponentName cn = new ComponentName(packageName, packageName + EMPTY_CLASS_NAME); - return new ComponentKey(cn, user); - } - - /** - * Gets an entry for the package, which can be used as a fallback entry for various components. - * This method is not thread safe, it must be called from a synchronized method. - */ - private CacheEntry getEntryForPackageLocked(String packageName, UserHandle user, - boolean useLowResIcon) { - Preconditions.assertWorkerThread(); - ComponentKey cacheKey = getPackageKey(packageName, user); - CacheEntry entry = mCache.get(cacheKey); - - if (entry == null || (entry.isLowRes() && !useLowResIcon)) { - entry = new CacheEntry(); - boolean entryUpdated = true; - - // Check the DB first. - if (!getEntryFromDB(cacheKey, entry, useLowResIcon)) { - try { - int flags = Process.myUserHandle().equals(user) ? 0 : - PackageManager.GET_UNINSTALLED_PACKAGES; - PackageInfo info = mPackageManager.getPackageInfo(packageName, flags); - ApplicationInfo appInfo = info.applicationInfo; - if (appInfo == null) { - throw new NameNotFoundException("ApplicationInfo is null"); - } - - LauncherIcons li = LauncherIcons.obtain(mContext); - // Load the full res icon for the application, but if useLowResIcon is set, then - // only keep the low resolution icon instead of the larger full-sized icon - BitmapInfo iconInfo = li.createBadgedIconBitmap( - appInfo.loadIcon(mPackageManager), user, appInfo.targetSdkVersion, - mInstantAppResolver.isInstantApp(appInfo)); - li.recycle(); - - entry.title = appInfo.loadLabel(mPackageManager); - entry.contentDescription = mUserManager.getBadgedLabelForUser(entry.title, user); - entry.icon = useLowResIcon ? LOW_RES_ICON : iconInfo.icon; - entry.color = iconInfo.color; - - // Add the icon in the DB here, since these do not get written during - // package updates. - ContentValues values = newContentValues(iconInfo.icon, entry.color, - entry.title.toString(), packageName); - addIconToDB(values, cacheKey.componentName, info, - mUserManager.getSerialNumberForUser(user)); - - } catch (NameNotFoundException e) { - if (DEBUG) Log.d(TAG, "Application not installed " + packageName); - entryUpdated = false; - } - } - - // Only add a filled-out entry to the cache - if (entryUpdated) { - mCache.put(cacheKey, entry); - } - } - return entry; - } - - private boolean getEntryFromDB(ComponentKey cacheKey, CacheEntry entry, boolean lowRes) { - Cursor c = null; - try { - c = mIconDb.query( - lowRes ? IconDB.COLUMNS_LOW_RES : IconDB.COLUMNS_HIGH_RES, - IconDB.COLUMN_COMPONENT + " = ? AND " + IconDB.COLUMN_USER + " = ?", - new String[]{ - cacheKey.componentName.flattenToString(), - Long.toString(mUserManager.getSerialNumberForUser(cacheKey.user))}); - if (c.moveToNext()) { - // Set the alpha to be 255, so that we never have a wrong color - entry.color = ColorUtils.setAlphaComponent(c.getInt(0), 255); - entry.title = c.getString(1); - if (entry.title == null) { - entry.title = ""; - entry.contentDescription = ""; - } else { - entry.contentDescription = mUserManager.getBadgedLabelForUser( - entry.title, cacheKey.user); - } - - if (lowRes) { - entry.icon = LOW_RES_ICON; - } else { - byte[] data = c.getBlob(2); - try { - entry.icon = BitmapFactory.decodeByteArray(data, 0, data.length, - mDecodeOptions); - } catch (Exception e) { } - } - return true; - } - } catch (SQLiteException e) { - Log.d(TAG, "Error reading icon cache", e); - } finally { - if (c != null) { - c.close(); - } - } - return false; - } - public static abstract class IconLoadRequest implements Runnable { private final Handler mHandler; private final Runnable mEndRunnable; @@ -641,59 +193,6 @@ public class IconCache { } } - static final class IconDB extends SQLiteCacheHelper { - private final static int RELEASE_VERSION = 25; - - public final static String TABLE_NAME = "icons"; - public final static String COLUMN_ROWID = "rowid"; - public final static String COLUMN_COMPONENT = "componentName"; - public final static String COLUMN_USER = "profileId"; - public final static String COLUMN_LAST_UPDATED = "lastUpdated"; - public final static String COLUMN_VERSION = "version"; - public final static String COLUMN_ICON = "icon"; - public final static String COLUMN_ICON_COLOR = "icon_color"; - public final static String COLUMN_LABEL = "label"; - public final static String COLUMN_SYSTEM_STATE = "system_state"; - - public final static String[] COLUMNS_HIGH_RES = new String[] { - IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL, IconDB.COLUMN_ICON }; - public final static String[] COLUMNS_LOW_RES = new String[] { - IconDB.COLUMN_ICON_COLOR, IconDB.COLUMN_LABEL }; - - public IconDB(Context context, int iconPixelSize) { - super(context, LauncherFiles.APP_ICONS_DB, - (RELEASE_VERSION << 16) + iconPixelSize, - TABLE_NAME); - } - - @Override - protected void onCreateTable(SQLiteDatabase db) { - db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" + - COLUMN_COMPONENT + " TEXT NOT NULL, " + - COLUMN_USER + " INTEGER NOT NULL, " + - COLUMN_LAST_UPDATED + " INTEGER NOT NULL DEFAULT 0, " + - COLUMN_VERSION + " INTEGER NOT NULL DEFAULT 0, " + - COLUMN_ICON + " BLOB, " + - COLUMN_ICON_COLOR + " INTEGER NOT NULL DEFAULT 0, " + - COLUMN_LABEL + " TEXT, " + - COLUMN_SYSTEM_STATE + " TEXT, " + - "PRIMARY KEY (" + COLUMN_COMPONENT + ", " + COLUMN_USER + ") " + - ");"); - } - } - - private ContentValues newContentValues(Bitmap icon, int iconColor, String label, - String packageName) { - ContentValues values = new ContentValues(); - values.put(IconDB.COLUMN_ICON, Utilities.flattenBitmap(icon)); - values.put(IconDB.COLUMN_ICON_COLOR, iconColor); - - values.put(IconDB.COLUMN_LABEL, label); - values.put(IconDB.COLUMN_SYSTEM_STATE, mIconProvider.getIconSystemState(packageName)); - - return values; - } - /** * Interface for receiving itemInfo with high-res icon. */ diff --git a/src/com/android/launcher3/icons/IconCacheUpdateHandler.java b/src/com/android/launcher3/icons/IconCacheUpdateHandler.java index 04e29013ab..8b3af01042 100644 --- a/src/com/android/launcher3/icons/IconCacheUpdateHandler.java +++ b/src/com/android/launcher3/icons/IconCacheUpdateHandler.java @@ -17,7 +17,6 @@ package com.android.launcher3.icons; import android.content.ComponentName; import android.content.pm.ApplicationInfo; -import android.content.pm.LauncherActivityInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.database.Cursor; @@ -26,15 +25,17 @@ import android.os.SystemClock; import android.os.UserHandle; import android.text.TextUtils; import android.util.Log; +import android.util.SparseBooleanArray; -import com.android.launcher3.LauncherAppState; import com.android.launcher3.Utilities; -import com.android.launcher3.icons.IconCache.IconDB; +import com.android.launcher3.icons.BaseIconCache.IconDB; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map.Entry; import java.util.Set; import java.util.Stack; @@ -45,13 +46,29 @@ public class IconCacheUpdateHandler { private static final String TAG = "IconCacheUpdateHandler"; + /** + * In this mode, all invalid icons are marked as to-be-deleted in {@link #mItemsToDelete}. + * This mode is used for the first run. + */ + private static final boolean MODE_SET_INVALID_ITEMS = true; + + /** + * In this mode, any valid icon is removed from {@link #mItemsToDelete}. This is used for all + * subsequent runs, which essentially acts as set-union of all valid items. + */ + private static final boolean MODE_CLEAR_VALID_ITEMS = false; + private static final Object ICON_UPDATE_TOKEN = new Object(); private final HashMap mPkgInfoMap; - private final IconCache mIconCache; + private final BaseIconCache mIconCache; + private final HashMap> mPackagesToIgnore = new HashMap<>(); - IconCacheUpdateHandler(IconCache cache) { + private final SparseBooleanArray mItemsToDelete = new SparseBooleanArray(); + private boolean mFilterMode = MODE_SET_INVALID_ITEMS; + + IconCacheUpdateHandler(BaseIconCache cache) { mIconCache = cache; mPkgInfoMap = new HashMap<>(); @@ -68,9 +85,8 @@ public class IconCacheUpdateHandler { private void createPackageInfoMap() { PackageManager pm = mIconCache.mPackageManager; - HashMap pkgInfoMap = new HashMap<>(); for (PackageInfo info : pm.getInstalledPackages(PackageManager.GET_UNINSTALLED_PACKAGES)) { - pkgInfoMap.put(info.packageName, info); + mPkgInfoMap.put(info.packageName, info); } } @@ -79,25 +95,44 @@ public class IconCacheUpdateHandler { * the DB and are updated. * @return The set of packages for which icons have updated. */ - public void updateIcons(List apps) { - if (apps.isEmpty()) { - return; + public void updateIcons(List apps, CachingLogic cachingLogic, + OnUpdateCallback onUpdateCallback) { + // Filter the list per user + HashMap> userComponentMap = new HashMap<>(); + int count = apps.size(); + for (int i = 0; i < count; i++) { + T app = apps.get(i); + UserHandle userHandle = cachingLogic.getUser(app); + HashMap componentMap = userComponentMap.get(userHandle); + if (componentMap == null) { + componentMap = new HashMap<>(); + userComponentMap.put(userHandle, componentMap); + } + componentMap.put(cachingLogic.getComponent(app), app); } - UserHandle user = apps.get(0).getUser(); + for (Entry> entry : userComponentMap.entrySet()) { + updateIconsPerUser(entry.getKey(), entry.getValue(), cachingLogic, onUpdateCallback); + } + + // From now on, clear every valid item from the global valid map. + mFilterMode = MODE_CLEAR_VALID_ITEMS; + } + + /** + * Updates the persistent DB, such that only entries corresponding to {@param apps} remain in + * the DB and are updated. + * @return The set of packages for which icons have updated. + */ + private void updateIconsPerUser(UserHandle user, HashMap componentMap, + CachingLogic cachingLogic, OnUpdateCallback onUpdateCallback) { Set ignorePackages = mPackagesToIgnore.get(user); if (ignorePackages == null) { ignorePackages = Collections.emptySet(); } - long userSerial = mIconCache.mUserManager.getSerialNumberForUser(user); - HashMap componentMap = new HashMap<>(); - for (LauncherActivityInfo app : apps) { - componentMap.put(app.getComponentName(), app); - } - HashSet itemsToRemove = new HashSet<>(); - Stack appsToUpdate = new Stack<>(); + Stack appsToUpdate = new Stack<>(); try (Cursor c = mIconCache.mIconDb.query( new String[]{IconDB.COLUMN_ROWID, IconDB.COLUMN_COMPONENT, @@ -116,10 +151,15 @@ public class IconCacheUpdateHandler { String cn = c.getString(indexComponent); ComponentName component = ComponentName.unflattenFromString(cn); PackageInfo info = mPkgInfoMap.get(component.getPackageName()); + + int rowId = c.getInt(rowIndex); if (info == null) { if (!ignorePackages.contains(component.getPackageName())) { - mIconCache.remove(component, user); - itemsToRemove.add(c.getInt(rowIndex)); + + if (mFilterMode == MODE_SET_INVALID_ITEMS) { + mIconCache.remove(component, user); + mItemsToDelete.put(rowId, true); + } } continue; } @@ -130,15 +170,21 @@ public class IconCacheUpdateHandler { long updateTime = c.getLong(indexLastUpdate); int version = c.getInt(indexVersion); - LauncherActivityInfo app = componentMap.remove(component); + T app = componentMap.remove(component); if (version == info.versionCode && updateTime == info.lastUpdateTime && TextUtils.equals(c.getString(systemStateIndex), mIconCache.mIconProvider.getIconSystemState(info.packageName))) { + + if (mFilterMode == MODE_CLEAR_VALID_ITEMS) { + mItemsToDelete.put(rowId, false); + } continue; } if (app == null) { - mIconCache.remove(component, user); - itemsToRemove.add(c.getInt(rowIndex)); + if (mFilterMode == MODE_SET_INVALID_ITEMS) { + mIconCache.remove(component, user); + mItemsToDelete.put(rowId, true); + } } else { appsToUpdate.add(app); } @@ -147,16 +193,28 @@ public class IconCacheUpdateHandler { Log.d(TAG, "Error reading icon cache", e); // Continue updating whatever we have read so far } - if (!itemsToRemove.isEmpty()) { - mIconCache.mIconDb.delete( - Utilities.createDbSelectionQuery(IconDB.COLUMN_ROWID, itemsToRemove), null); - } // Insert remaining apps. if (!componentMap.isEmpty() || !appsToUpdate.isEmpty()) { - Stack appsToAdd = new Stack<>(); + Stack appsToAdd = new Stack<>(); appsToAdd.addAll(componentMap.values()); - new SerializedIconUpdateTask(userSerial, user, appsToAdd, appsToUpdate).scheduleNext(); + new SerializedIconUpdateTask(userSerial, user, appsToAdd, appsToUpdate, cachingLogic, + onUpdateCallback).scheduleNext(); + } + } + + public void finish() { + // Commit all deletes + ArrayList deleteIds = new ArrayList<>(); + int count = mItemsToDelete.size(); + for (int i = 0; i < count; i++) { + if (mItemsToDelete.valueAt(i)) { + deleteIds.add(mItemsToDelete.keyAt(i)); + } + } + if (!deleteIds.isEmpty()) { + mIconCache.mIconDb.delete( + Utilities.createDbSelectionQuery(IconDB.COLUMN_ROWID, deleteIds), null); } } @@ -166,47 +224,51 @@ public class IconCacheUpdateHandler { * LauncherActivityInfo list. Items are updated/added one at a time, so that the * worker thread doesn't get blocked. */ - private class SerializedIconUpdateTask implements Runnable { + private class SerializedIconUpdateTask implements Runnable { private final long mUserSerial; private final UserHandle mUserHandle; - private final Stack mAppsToAdd; - private final Stack mAppsToUpdate; + private final Stack mAppsToAdd; + private final Stack mAppsToUpdate; + private final CachingLogic mCachingLogic; private final HashSet mUpdatedPackages = new HashSet<>(); + private final OnUpdateCallback mOnUpdateCallback; SerializedIconUpdateTask(long userSerial, UserHandle userHandle, - Stack appsToAdd, Stack appsToUpdate) { + Stack appsToAdd, Stack appsToUpdate, CachingLogic cachingLogic, + OnUpdateCallback onUpdateCallback) { mUserHandle = userHandle; mUserSerial = userSerial; mAppsToAdd = appsToAdd; mAppsToUpdate = appsToUpdate; + mCachingLogic = cachingLogic; + mOnUpdateCallback = onUpdateCallback; } @Override public void run() { if (!mAppsToUpdate.isEmpty()) { - LauncherActivityInfo app = mAppsToUpdate.pop(); - String pkg = app.getComponentName().getPackageName(); + T app = mAppsToUpdate.pop(); + String pkg = mCachingLogic.getComponent(app).getPackageName(); PackageInfo info = mPkgInfoMap.get(pkg); mIconCache.addIconToDBAndMemCache( - app, info, mUserSerial, true /*replace existing*/); + app, mCachingLogic, info, mUserSerial, true /*replace existing*/); mUpdatedPackages.add(pkg); if (mAppsToUpdate.isEmpty() && !mUpdatedPackages.isEmpty()) { - // No more app to update. Notify model. - LauncherAppState.getInstance(mIconCache.mContext).getModel() - .onPackageIconsUpdated(mUpdatedPackages, mUserHandle); + // No more app to update. Notify callback. + mOnUpdateCallback.onPackageIconsUpdated(mUpdatedPackages, mUserHandle); } // Let it run one more time. scheduleNext(); } else if (!mAppsToAdd.isEmpty()) { - LauncherActivityInfo app = mAppsToAdd.pop(); - PackageInfo info = mPkgInfoMap.get(app.getComponentName().getPackageName()); + T app = mAppsToAdd.pop(); + PackageInfo info = mPkgInfoMap.get(mCachingLogic.getComponent(app).getPackageName()); // We do not check the mPkgInfoMap when generating the mAppsToAdd. Although every // app should have package info, this is not guaranteed by the api if (info != null) { - mIconCache.addIconToDBAndMemCache( - app, info, mUserSerial, false /*replace existing*/); + mIconCache.addIconToDBAndMemCache(app, mCachingLogic, info, + mUserSerial, false /*replace existing*/); } if (!mAppsToAdd.isEmpty()) { @@ -220,4 +282,9 @@ public class IconCacheUpdateHandler { SystemClock.uptimeMillis() + 1); } } + + public interface OnUpdateCallback { + + void onPackageIconsUpdated(HashSet updatedPackages, UserHandle user); + } } diff --git a/src/com/android/launcher3/graphics/IconNormalizer.java b/src/com/android/launcher3/icons/IconNormalizer.java similarity index 99% rename from src/com/android/launcher3/graphics/IconNormalizer.java rename to src/com/android/launcher3/icons/IconNormalizer.java index df00815c07..73177825bd 100644 --- a/src/com/android/launcher3/graphics/IconNormalizer.java +++ b/src/com/android/launcher3/icons/IconNormalizer.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.launcher3.graphics; +package com.android.launcher3.icons; import android.content.Context; import android.graphics.Bitmap; diff --git a/src/com/android/launcher3/graphics/LauncherIcons.java b/src/com/android/launcher3/icons/LauncherIcons.java similarity index 99% rename from src/com/android/launcher3/graphics/LauncherIcons.java rename to src/com/android/launcher3/icons/LauncherIcons.java index 7db67c9828..b4cbf65304 100644 --- a/src/com/android/launcher3/graphics/LauncherIcons.java +++ b/src/com/android/launcher3/icons/LauncherIcons.java @@ -14,12 +14,12 @@ * limitations under the License. */ -package com.android.launcher3.graphics; +package com.android.launcher3.icons; import static android.graphics.Paint.DITHER_FLAG; import static android.graphics.Paint.FILTER_BITMAP_FLAG; -import static com.android.launcher3.graphics.ShadowGenerator.BLUR_FACTOR; +import static com.android.launcher3.icons.ShadowGenerator.BLUR_FACTOR; import android.content.ComponentName; import android.content.Context; @@ -44,7 +44,7 @@ import android.os.UserHandle; import com.android.launcher3.AppInfo; import com.android.launcher3.FastBitmapDrawable; -import com.android.launcher3.icons.IconCache; +import com.android.launcher3.graphics.BitmapRenderer; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfoWithIcon; import com.android.launcher3.LauncherAppState; diff --git a/src/com/android/launcher3/graphics/ShadowGenerator.java b/src/com/android/launcher3/icons/ShadowGenerator.java similarity index 99% rename from src/com/android/launcher3/graphics/ShadowGenerator.java rename to src/com/android/launcher3/icons/ShadowGenerator.java index d2d1699bf7..57d463a806 100644 --- a/src/com/android/launcher3/graphics/ShadowGenerator.java +++ b/src/com/android/launcher3/icons/ShadowGenerator.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.android.launcher3.graphics; +package com.android.launcher3.icons; import android.content.Context; import android.graphics.Bitmap; diff --git a/src/com/android/launcher3/model/LoaderCursor.java b/src/com/android/launcher3/model/LoaderCursor.java index 77e9721bce..87aef02f76 100644 --- a/src/com/android/launcher3/model/LoaderCursor.java +++ b/src/com/android/launcher3/model/LoaderCursor.java @@ -43,8 +43,8 @@ import com.android.launcher3.Workspace; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.graphics.BitmapInfo; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.logging.FileLog; import com.android.launcher3.util.ContentWriter; import com.android.launcher3.util.GridOccupancy; diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java index 4ccb8d8d6d..e0da6b172b 100644 --- a/src/com/android/launcher3/model/LoaderTask.java +++ b/src/com/android/launcher3/model/LoaderTask.java @@ -20,6 +20,8 @@ import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_LOCKED_USER; import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE; import static com.android.launcher3.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED; import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW; +import static com.android.launcher3.icons.CachingLogic.COMPONENT_WITH_LABEL; +import static com.android.launcher3.icons.CachingLogic.LAUNCHER_ACTIVITY_INFO; import static com.android.launcher3.model.LoaderResults.filterCurrentWorkspaceItems; import android.appwidget.AppWidgetProviderInfo; @@ -43,6 +45,7 @@ import android.util.MutableInt; import com.android.launcher3.AllAppsList; import com.android.launcher3.AppInfo; import com.android.launcher3.FolderInfo; +import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.IconCacheUpdateHandler; import com.android.launcher3.icons.IconCache; import com.android.launcher3.InstallShortcutReceiver; @@ -60,7 +63,7 @@ import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.Folder; import com.android.launcher3.folder.FolderIconPreviewVerifier; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.logging.FileLog; import com.android.launcher3.provider.ImportDataTask; import com.android.launcher3.shortcuts.DeepShortcutManager; @@ -183,7 +186,7 @@ public class LoaderTask implements Runnable { // second step TraceHelper.partitionSection(TAG, "step 2.1: loading all apps"); - List> activityListPerUser = loadAllApps(); + List allActivityList = loadAllApps(); TraceHelper.partitionSection(TAG, "step 2.2: Binding all apps"); verifyNotStopped(); @@ -193,7 +196,8 @@ public class LoaderTask implements Runnable { TraceHelper.partitionSection(TAG, "step 2.3: Update icon cache"); IconCacheUpdateHandler updateHandler = mIconCache.getUpdateHandler(); setIgnorePackages(updateHandler); - updateIconCacheForApps(updateHandler, activityListPerUser); + updateHandler.updateIcons(allActivityList, LAUNCHER_ACTIVITY_INFO, + mApp.getModel()::onPackageIconsUpdated); // Take a break TraceHelper.partitionSection(TAG, "step 2 completed, wait for idle"); @@ -215,12 +219,21 @@ public class LoaderTask implements Runnable { // fourth step TraceHelper.partitionSection(TAG, "step 4.1: loading widgets"); - mBgDataModel.widgetsModel.update(mApp, null); + List allWidgetsList = mBgDataModel.widgetsModel.update(mApp, null); verifyNotStopped(); TraceHelper.partitionSection(TAG, "step 4.2: Binding widgets"); mResults.bindWidgets(); + verifyNotStopped(); + TraceHelper.partitionSection(TAG, "step 4.3: Update icon cache"); + updateHandler.updateIcons(allWidgetsList, COMPONENT_WITH_LABEL, + mApp.getModel()::onWidgetLabelsUpdated); + + verifyNotStopped(); + TraceHelper.partitionSection(TAG, "step 5: Finish icon cache update"); + updateHandler.finish(); + transaction.commit(); } catch (CancellationException e) { // Loader stopped, ignore @@ -798,17 +811,9 @@ public class LoaderTask implements Runnable { updateHandler.setPackagesToIgnore(Process.myUserHandle(), packagesToIgnore); } - private void updateIconCacheForApps(IconCacheUpdateHandler updateHandler, - List> activityListPerUser) { - int userCount = activityListPerUser.size(); - for (int i = 0; i < userCount; i++) { - updateHandler.updateIcons(activityListPerUser.get(i)); - } - } - - private List> loadAllApps() { + private List loadAllApps() { final List profiles = mUserManager.getUserProfiles(); - List> activityListPerUser = new ArrayList<>(); + List allActivityList = new ArrayList<>(); // Clear the list of apps mBgAllAppsList.clear(); for (UserHandle user : profiles) { @@ -817,7 +822,7 @@ public class LoaderTask implements Runnable { // Fail if we don't have any apps // TODO: Fix this. Only fail for the current user. if (apps == null || apps.isEmpty()) { - return activityListPerUser; + return allActivityList; } boolean quietMode = mUserManager.isQuietModeEnabled(user); // Create the ApplicationInfos @@ -826,7 +831,7 @@ public class LoaderTask implements Runnable { // This builds the icon bitmaps. mBgAllAppsList.add(new AppInfo(app, user, quietMode), app); } - activityListPerUser.add(apps); + allActivityList.addAll(apps); } if (FeatureFlags.LAUNCHER3_PROMISE_APPS_IN_ALL_APPS) { @@ -839,7 +844,7 @@ public class LoaderTask implements Runnable { } mBgAllAppsList.added = new ArrayList<>(); - return activityListPerUser; + return allActivityList; } private void loadDeepShortcuts() { diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java index 0af53118d3..c004e217b1 100644 --- a/src/com/android/launcher3/model/PackageUpdatedTask.java +++ b/src/com/android/launcher3/model/PackageUpdatedTask.java @@ -38,8 +38,8 @@ import com.android.launcher3.Utilities; import com.android.launcher3.compat.LauncherAppsCompat; import com.android.launcher3.compat.UserManagerCompat; import com.android.launcher3.config.FeatureFlags; -import com.android.launcher3.graphics.BitmapInfo; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.logging.FileLog; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutInfoCompat; diff --git a/src/com/android/launcher3/model/ShortcutsChangedTask.java b/src/com/android/launcher3/model/ShortcutsChangedTask.java index 59f3d1c60a..020bc41822 100644 --- a/src/com/android/launcher3/model/ShortcutsChangedTask.java +++ b/src/com/android/launcher3/model/ShortcutsChangedTask.java @@ -23,7 +23,7 @@ import com.android.launcher3.ItemInfo; import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; import com.android.launcher3.ShortcutInfo; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.shortcuts.ShortcutKey; diff --git a/src/com/android/launcher3/model/UserLockStateChangedTask.java b/src/com/android/launcher3/model/UserLockStateChangedTask.java index 9521a9e5b5..9f02d4fdad 100644 --- a/src/com/android/launcher3/model/UserLockStateChangedTask.java +++ b/src/com/android/launcher3/model/UserLockStateChangedTask.java @@ -26,7 +26,7 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.LauncherSettings; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.compat.UserManagerCompat; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.shortcuts.DeepShortcutManager; import com.android.launcher3.shortcuts.ShortcutInfoCompat; import com.android.launcher3.shortcuts.ShortcutKey; diff --git a/src/com/android/launcher3/model/WidgetItem.java b/src/com/android/launcher3/model/WidgetItem.java index 1e96dec20c..e38529b3df 100644 --- a/src/com/android/launcher3/model/WidgetItem.java +++ b/src/com/android/launcher3/model/WidgetItem.java @@ -9,6 +9,7 @@ import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppWidgetProviderInfo; import com.android.launcher3.Utilities; import com.android.launcher3.compat.ShortcutConfigActivityInfo; +import com.android.launcher3.icons.IconCache; import com.android.launcher3.util.ComponentKey; import java.text.Collator; @@ -29,11 +30,11 @@ public class WidgetItem extends ComponentKey implements Comparable { public final String label; public final int spanX, spanY; - public WidgetItem(LauncherAppWidgetProviderInfo info, PackageManager pm, - InvariantDeviceProfile idp) { + public WidgetItem(LauncherAppWidgetProviderInfo info, + InvariantDeviceProfile idp, IconCache iconCache) { super(info.provider, info.getProfile()); - label = Utilities.trim(info.getLabel(pm)); + label = iconCache.getTitleNoCache(info); widgetInfo = info; activityInfo = null; @@ -41,9 +42,10 @@ public class WidgetItem extends ComponentKey implements Comparable { spanY = Math.min(info.spanY, idp.numRows); } - public WidgetItem(ShortcutConfigActivityInfo info) { + public WidgetItem(ShortcutConfigActivityInfo info, IconCache iconCache, PackageManager pm) { super(info.getComponent(), info.getUser()); - label = Utilities.trim(info.getLabel()); + label = info.isPersistable() ? iconCache.getTitleNoCache(info) : + Utilities.trim(info.getLabel(pm)); widgetInfo = null; activityInfo = info; spanX = spanY = 1; diff --git a/src/com/android/launcher3/model/WidgetsModel.java b/src/com/android/launcher3/model/WidgetsModel.java index 82f4fe1894..9a17ec65cb 100644 --- a/src/com/android/launcher3/model/WidgetsModel.java +++ b/src/com/android/launcher3/model/WidgetsModel.java @@ -11,6 +11,7 @@ import android.os.UserHandle; import android.util.Log; import com.android.launcher3.AppFilter; +import com.android.launcher3.icons.ComponentWithLabel; import com.android.launcher3.icons.IconCache; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; @@ -31,7 +32,10 @@ import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; import androidx.annotation.Nullable; @@ -76,26 +80,32 @@ public class WidgetsModel { * @param packageUser If null, all widgets and shortcuts are updated and returned, otherwise * only widgets and shortcuts associated with the package/user are. */ - public void update(LauncherAppState app, @Nullable PackageUserKey packageUser) { + public List update(LauncherAppState app, @Nullable PackageUserKey packageUser) { Preconditions.assertWorkerThread(); Context context = app.getContext(); final ArrayList widgetsAndShortcuts = new ArrayList<>(); + List updatedItems = new ArrayList<>(); try { - PackageManager pm = context.getPackageManager(); InvariantDeviceProfile idp = app.getInvariantDeviceProfile(); + PackageManager pm = app.getContext().getPackageManager(); // Widgets AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(context); for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(packageUser)) { - widgetsAndShortcuts.add(new WidgetItem(LauncherAppWidgetProviderInfo - .fromProviderInfo(context, widgetInfo), pm, idp)); + LauncherAppWidgetProviderInfo launcherWidgetInfo = + LauncherAppWidgetProviderInfo.fromProviderInfo(context, widgetInfo); + + widgetsAndShortcuts.add(new WidgetItem( + launcherWidgetInfo, idp, app.getIconCache())); + updatedItems.add(launcherWidgetInfo); } // Shortcuts for (ShortcutConfigActivityInfo info : LauncherAppsCompat.getInstance(context) .getCustomShortcutActivityList(packageUser)) { - widgetsAndShortcuts.add(new WidgetItem(info)); + widgetsAndShortcuts.add(new WidgetItem(info, app.getIconCache(), pm)); + updatedItems.add(info); } setWidgetsAndShortcuts(widgetsAndShortcuts, app, packageUser); } catch (Exception e) { @@ -110,6 +120,7 @@ public class WidgetsModel { } app.getWidgetCache().removeObsoletePreviews(widgetsAndShortcuts, packageUser); + return updatedItems; } private synchronized void setWidgetsAndShortcuts(ArrayList rawWidgetsShortcuts, @@ -204,4 +215,26 @@ public class WidgetsModel { iconCache.getTitleAndIconForApp(p, true /* userLowResIcon */); } } + + public void onPackageIconsUpdated(Set packageNames, UserHandle user, + LauncherAppState app) { + for (Entry> entry : mWidgetsList.entrySet()) { + if (packageNames.contains(entry.getKey().packageName)) { + ArrayList items = entry.getValue(); + int count = items.size(); + for (int i = 0; i < count; i++) { + WidgetItem item = items.get(i); + if (item.user.equals(user)) { + if (item.activityInfo != null) { + items.set(i, new WidgetItem(item.activityInfo, app.getIconCache(), + app.getContext().getPackageManager())); + } else { + items.set(i, new WidgetItem(item.widgetInfo, + app.getInvariantDeviceProfile(), app.getIconCache())); + } + } + } + } + } + } } \ No newline at end of file diff --git a/src/com/android/launcher3/notification/NotificationMainView.java b/src/com/android/launcher3/notification/NotificationMainView.java index 5c0e259414..78627ecc10 100644 --- a/src/com/android/launcher3/notification/NotificationMainView.java +++ b/src/com/android/launcher3/notification/NotificationMainView.java @@ -179,7 +179,7 @@ public class NotificationMainView extends FrameLayout implements SwipeDetector.L @Override - public boolean onDrag(float displacement, float velocity) { + public boolean onDrag(float displacement) { setContentTranslation(canChildBeDismissed() ? displacement : OverScroll.dampedScroll(displacement, getWidth())); mContentTranslateAnimator.cancel(); diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java index 450062961a..c14c00eb87 100644 --- a/src/com/android/launcher3/popup/PopupPopulator.java +++ b/src/com/android/launcher3/popup/PopupPopulator.java @@ -24,7 +24,7 @@ import android.service.notification.StatusBarNotification; import com.android.launcher3.ItemInfo; import com.android.launcher3.Launcher; import com.android.launcher3.ShortcutInfo; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.LauncherIcons; import com.android.launcher3.notification.NotificationInfo; import com.android.launcher3.notification.NotificationKeyData; import com.android.launcher3.shortcuts.DeepShortcutManager; diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java index 7b09bcce71..ce1cc89dd1 100644 --- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java +++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java @@ -256,7 +256,7 @@ public abstract class AbstractStateChangeTouchController } @Override - public boolean onDrag(float displacement, float velocity) { + public boolean onDrag(float displacement) { float deltaProgress = mProgressMultiplier * (displacement - mDisplacementShift); float progress = deltaProgress + mStartProgress; updateProgress(progress); diff --git a/src/com/android/launcher3/touch/SwipeDetector.java b/src/com/android/launcher3/touch/SwipeDetector.java index 6ffc0efae7..a0a410e0dc 100644 --- a/src/com/android/launcher3/touch/SwipeDetector.java +++ b/src/com/android/launcher3/touch/SwipeDetector.java @@ -21,6 +21,7 @@ import android.content.Context; import android.graphics.PointF; import android.util.Log; import android.view.MotionEvent; +import android.view.VelocityTracker; import android.view.ViewConfiguration; import androidx.annotation.NonNull; @@ -52,12 +53,6 @@ public class SwipeDetector { */ public static final float RELEASE_VELOCITY_PX_MS = 1.0f; - /** - * The time constant used to calculate dampening in the low-pass filter of scroll velocity. - * Cutoff frequency is set at 10 Hz. - */ - public static final float SCROLL_VELOCITY_DAMPENING_RC = 1000f / (2f * (float) Math.PI * 10); - /* Scroll state, this is set to true during dragging and animation. */ private ScrollState mState = ScrollState.IDLE; @@ -75,6 +70,8 @@ public class SwipeDetector { * Distance in pixels a touch can wander before we think the user is scrolling. */ abstract float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos); + + abstract float getVelocity(VelocityTracker tracker); } public static final Direction VERTICAL = new Direction() { @@ -88,6 +85,11 @@ public class SwipeDetector { float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) { return Math.abs(ev.getX(pointerIndex) - downPos.x); } + + @Override + float getVelocity(VelocityTracker tracker) { + return tracker.getYVelocity(); + } }; public static final Direction HORIZONTAL = new Direction() { @@ -101,6 +103,11 @@ public class SwipeDetector { float getActiveTouchSlop(MotionEvent ev, int pointerIndex, PointF downPos) { return Math.abs(ev.getY(pointerIndex) - downPos.y); } + + @Override + float getVelocity(VelocityTracker tracker) { + return tracker.getXVelocity(); + } }; //------------------- ScrollState transition diagram ----------------------------------- @@ -151,16 +158,16 @@ public class SwipeDetector { private final PointF mDownPos = new PointF(); private final PointF mLastPos = new PointF(); - private Direction mDir; + private final Direction mDir; private final float mTouchSlop; + private final float mMaxVelocity; /* Client of this gesture detector can register a callback. */ private final Listener mListener; - private long mCurrentMillis; + private VelocityTracker mVelocityTracker; - private float mVelocity; private float mLastDisplacement; private float mDisplacement; @@ -170,24 +177,22 @@ public class SwipeDetector { public interface Listener { void onDragStart(boolean start); - boolean onDrag(float displacement, float velocity); + boolean onDrag(float displacement); void onDragEnd(float velocity, boolean fling); } public SwipeDetector(@NonNull Context context, @NonNull Listener l, @NonNull Direction dir) { - this(ViewConfiguration.get(context).getScaledTouchSlop(), l, dir); + this(ViewConfiguration.get(context), l, dir); } @VisibleForTesting - protected SwipeDetector(float touchSlope, @NonNull Listener l, @NonNull Direction dir) { - mTouchSlop = touchSlope; + protected SwipeDetector(@NonNull ViewConfiguration config, @NonNull Listener l, + @NonNull Direction dir) { mListener = l; mDir = dir; - } - - public void updateDirection(Direction dir) { - mDir = dir; + mTouchSlop = config.getScaledTouchSlop(); + mMaxVelocity = config.getScaledMaximumFlingVelocity(); } public void setDetectableScrollConditions(int scrollDirectionFlags, boolean ignoreSlop) { @@ -215,14 +220,22 @@ public class SwipeDetector { } public boolean onTouchEvent(MotionEvent ev) { - switch (ev.getActionMasked()) { + int actionMasked = ev.getActionMasked(); + if (actionMasked == MotionEvent.ACTION_DOWN && mVelocityTracker != null) { + mVelocityTracker.clear(); + } + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain(); + } + mVelocityTracker.addMovement(ev); + + switch (actionMasked) { case MotionEvent.ACTION_DOWN: mActivePointerId = ev.getPointerId(0); mDownPos.set(ev.getX(), ev.getY()); mLastPos.set(mDownPos); mLastDisplacement = 0; mDisplacement = 0; - mVelocity = 0; if (mState == ScrollState.SETTLING && mIgnoreSlopWhenSettling) { setState(ScrollState.DRAGGING); @@ -247,8 +260,6 @@ public class SwipeDetector { break; } mDisplacement = mDir.getDisplacement(ev, pointerIndex, mDownPos); - computeVelocity(mDir.getDisplacement(ev, pointerIndex, mLastPos), - ev.getEventTime()); // handle state and listener calls. if (mState != ScrollState.DRAGGING && shouldScrollStart(ev, pointerIndex)) { @@ -265,6 +276,8 @@ public class SwipeDetector { if (mState == ScrollState.DRAGGING) { setState(ScrollState.SETTLING); } + mVelocityTracker.recycle(); + mVelocityTracker = null; break; default: break; @@ -308,55 +321,24 @@ public class SwipeDetector { private boolean reportDragging() { if (mDisplacement != mLastDisplacement) { if (DBG) { - Log.d(TAG, String.format("onDrag disp=%.1f, velocity=%.1f", - mDisplacement, mVelocity)); + Log.d(TAG, String.format("onDrag disp=%.1f", mDisplacement)); } mLastDisplacement = mDisplacement; - return mListener.onDrag(mDisplacement - mSubtractDisplacement, mVelocity); + return mListener.onDrag(mDisplacement - mSubtractDisplacement); } return true; } private void reportDragEnd() { + mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity); + float velocity = mDir.getVelocity(mVelocityTracker) / 1000; if (DBG) { Log.d(TAG, String.format("onScrollEnd disp=%.1f, velocity=%.1f", - mDisplacement, mVelocity)); + mDisplacement, velocity)); } - mListener.onDragEnd(mVelocity, Math.abs(mVelocity) > RELEASE_VELOCITY_PX_MS); - } - - /** - * Computes the damped velocity. - */ - public float computeVelocity(float delta, long currentMillis) { - long previousMillis = mCurrentMillis; - mCurrentMillis = currentMillis; - - float deltaTimeMillis = mCurrentMillis - previousMillis; - float velocity = (deltaTimeMillis > 0) ? (delta / deltaTimeMillis) : 0; - if (Math.abs(mVelocity) < 0.001f) { - mVelocity = velocity; - } else { - float alpha = computeDampeningFactor(deltaTimeMillis); - mVelocity = interpolate(mVelocity, velocity, alpha); - } - return mVelocity; - } - - /** - * Returns a time-dependent dampening factor using delta time. - */ - private static float computeDampeningFactor(float deltaTime) { - return deltaTime / (SCROLL_VELOCITY_DAMPENING_RC + deltaTime); - } - - /** - * Returns the linear interpolation between two values - */ - public static float interpolate(float from, float to, float alpha) { - return (1.0f - alpha) * from + alpha * to; + mListener.onDragEnd(velocity, Math.abs(velocity) > RELEASE_VELOCITY_PX_MS); } public static long calculateDuration(float velocity, float progressNeeded) { diff --git a/src/com/android/launcher3/touch/TouchEventTranslator.java b/src/com/android/launcher3/touch/TouchEventTranslator.java index 8a5c93264c..8333d32468 100644 --- a/src/com/android/launcher3/touch/TouchEventTranslator.java +++ b/src/com/android/launcher3/touch/TouchEventTranslator.java @@ -19,7 +19,6 @@ import android.graphics.PointF; import android.util.Log; import android.util.Pair; import android.util.SparseArray; -import android.util.SparseLongArray; import android.view.MotionEvent; import android.view.MotionEvent.PointerCoords; import android.view.MotionEvent.PointerProperties; @@ -37,13 +36,15 @@ public class TouchEventTranslator { private class DownState { long timeStamp; + float downX; float downY; - public DownState(long timeStamp, float downY) { + public DownState(long timeStamp, float downX, float downY) { this.timeStamp = timeStamp; + this.downX = downX; this.downY = downY; } }; - private final DownState ZERO = new DownState(0, 0f); + private final DownState ZERO = new DownState(0, 0f, 0f); private final Consumer mListener; @@ -65,12 +66,16 @@ public class TouchEventTranslator { mFingers.clear(); } + public float getDownX() { + return mDownEvents.get(0).downX; + } + public float getDownY() { return mDownEvents.get(0).downY; } public void setDownParameters(int idx, MotionEvent e) { - DownState ev = new DownState(e.getEventTime(), e.getY(idx)); + DownState ev = new DownState(e.getEventTime(), e.getX(idx), e.getY(idx)); mDownEvents.append(idx, ev); } diff --git a/src/com/android/launcher3/views/AbstractSlideInView.java b/src/com/android/launcher3/views/AbstractSlideInView.java index 26c8f243b5..f948beb8d2 100644 --- a/src/com/android/launcher3/views/AbstractSlideInView.java +++ b/src/com/android/launcher3/views/AbstractSlideInView.java @@ -128,7 +128,7 @@ public abstract class AbstractSlideInView extends AbstractFloatingView public void onDragStart(boolean start) { } @Override - public boolean onDrag(float displacement, float velocity) { + public boolean onDrag(float displacement) { float range = mContent.getHeight(); displacement = Utilities.boundToRange(displacement, 0, range); setTranslationShift(displacement / range); diff --git a/src/com/android/launcher3/views/RecyclerViewFastScroller.java b/src/com/android/launcher3/views/RecyclerViewFastScroller.java index f0d6de20f6..883cbeed9c 100644 --- a/src/com/android/launcher3/views/RecyclerViewFastScroller.java +++ b/src/com/android/launcher3/views/RecyclerViewFastScroller.java @@ -175,6 +175,7 @@ public class RecyclerViewFastScroller extends View { if (mThumbOffsetY == y) { return; } + updatePopupY((int) y); mThumbOffsetY = y; invalidate(); } @@ -281,7 +282,6 @@ public class RecyclerViewFastScroller extends View { mPopupView.setText(sectionName); } animatePopupVisibility(!sectionName.isEmpty()); - updatePopupY(lastY); mLastTouchY = boundedY; setThumbOffsetY((int) mLastTouchY); } @@ -299,11 +299,14 @@ public class RecyclerViewFastScroller extends View { canvas.translate(0, mThumbOffsetY); halfW += mThumbPadding; - float r = mWidth + mThumbPadding + mThumbPadding; + float r = getScrollThumbRadius(); canvas.drawRoundRect(-halfW, 0, halfW, mThumbHeight, r, r, mThumbPaint); canvas.restoreToCount(saveCount); } + private float getScrollThumbRadius() { + return mWidth + mThumbPadding + mThumbPadding; + } /** * Animates the width of the scrollbar. @@ -352,11 +355,15 @@ public class RecyclerViewFastScroller extends View { } private void updatePopupY(int lastTouchY) { + if (!mPopupVisible) { + return; + } int height = mPopupView.getHeight(); - float top = lastTouchY - (FAST_SCROLL_OVERLAY_Y_OFFSET_FACTOR * height) - + mRv.getScrollBarTop(); - top = Utilities.boundToRange(top, - mMaxWidth, mRv.getScrollbarTrackHeight() - mMaxWidth - height); + // Aligns the rounded corner of the pop up with the top of the thumb. + float top = mRv.getScrollBarTop() + lastTouchY + (getScrollThumbRadius() / 2f) + - (height / 2f); + top = Utilities.boundToRange(top, 0, + getTop() + mRv.getScrollBarTop() + mRv.getScrollbarTrackHeight() - height); mPopupView.setTranslationY(top); } diff --git a/src/com/android/launcher3/widget/PendingItemDragHelper.java b/src/com/android/launcher3/widget/PendingItemDragHelper.java index 74ab14f55d..8ea9bd479c 100644 --- a/src/com/android/launcher3/widget/PendingItemDragHelper.java +++ b/src/com/android/launcher3/widget/PendingItemDragHelper.java @@ -34,7 +34,7 @@ import com.android.launcher3.R; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.dragndrop.LivePreviewWidgetCell; import com.android.launcher3.graphics.DragPreviewProvider; -import com.android.launcher3.graphics.LauncherIcons; +import com.android.launcher3.icons.LauncherIcons; /** * Extension of {@link DragPreviewProvider} with logic specific to pending widgets/shortcuts diff --git a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVL.java b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVL.java index 7883442546..6808859438 100644 --- a/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVL.java +++ b/src_ui_overrides/com/android/launcher3/uioverrides/dynamicui/WallpaperManagerCompatVL.java @@ -45,7 +45,7 @@ import android.util.Log; import android.util.Pair; import com.android.launcher3.Utilities; -import com.android.launcher3.graphics.ColorExtractor; +import com.android.launcher3.icons.ColorExtractor; import java.io.IOException; import java.util.ArrayList; diff --git a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java index 59b2da036f..8503547771 100644 --- a/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java +++ b/tests/src/com/android/launcher3/model/BaseModelUpdateTaskTestCase.java @@ -11,7 +11,6 @@ import android.content.ContentResolver; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; -import android.content.pm.LauncherActivityInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; @@ -24,6 +23,7 @@ import androidx.test.rule.provider.ProviderTestRule; import com.android.launcher3.AllAppsList; import com.android.launcher3.AppFilter; import com.android.launcher3.AppInfo; +import com.android.launcher3.icons.CachingLogic; import com.android.launcher3.icons.IconCache; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.ItemInfo; @@ -32,7 +32,7 @@ import com.android.launcher3.LauncherModel; import com.android.launcher3.LauncherModel.Callbacks; import com.android.launcher3.LauncherModel.ModelUpdateTask; import com.android.launcher3.LauncherProvider; -import com.android.launcher3.graphics.BitmapInfo; +import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.util.ComponentKey; import com.android.launcher3.util.Provider; import com.android.launcher3.util.TestLauncherProvider; @@ -203,10 +203,11 @@ public class BaseModelUpdateTaskTestCase { } @Override - protected CacheEntry cacheLocked( + protected CacheEntry cacheLocked( @NonNull ComponentName componentName, - @NonNull Provider infoProvider, - UserHandle user, boolean usePackageIcon, boolean useLowResIcon) { + UserHandle user, @NonNull Provider infoProvider, + @NonNull CachingLogic cachingLogic, + boolean usePackageIcon, boolean useLowResIcon) { CacheEntry entry = mCache.get(new ComponentKey(componentName, user)); if (entry == null) { entry = new CacheEntry(); diff --git a/tests/src/com/android/launcher3/model/LoaderCursorTest.java b/tests/src/com/android/launcher3/model/LoaderCursorTest.java index 42a2764010..a21f861771 100644 --- a/tests/src/com/android/launcher3/model/LoaderCursorTest.java +++ b/tests/src/com/android/launcher3/model/LoaderCursorTest.java @@ -17,7 +17,7 @@ import com.android.launcher3.LauncherAppState; import com.android.launcher3.ShortcutInfo; import com.android.launcher3.Utilities; import com.android.launcher3.compat.LauncherAppsCompat; -import com.android.launcher3.graphics.BitmapInfo; +import com.android.launcher3.icons.BitmapInfo; import org.junit.Before; import org.junit.Test; diff --git a/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java b/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java index 73b6dafd1e..b600473d4b 100644 --- a/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java +++ b/tests/src/com/android/launcher3/touch/SwipeDetectorTest.java @@ -19,7 +19,6 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import android.util.Log; -import android.view.MotionEvent; import android.view.ViewConfiguration; import com.android.launcher3.testcomponent.TouchEventGenerator; @@ -32,6 +31,7 @@ import org.mockito.MockitoAnnotations; import static org.mockito.Matchers.anyBoolean; import static org.mockito.Matchers.anyFloat; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -51,25 +51,28 @@ public class SwipeDetectorTest { @Mock private SwipeDetector.Listener mMockListener; + @Mock + private ViewConfiguration mMockConfig; + @Before public void setup() { MockitoAnnotations.initMocks(this); - mGenerator = new TouchEventGenerator(new TouchEventGenerator.Listener() { - @Override - public void onTouchEvent(MotionEvent event) { - mDetector.onTouchEvent(event); - } - }); + mGenerator = new TouchEventGenerator((ev) -> mDetector.onTouchEvent(ev)); + ViewConfiguration orgConfig = ViewConfiguration + .get(InstrumentationRegistry.getTargetContext()); + doReturn(orgConfig.getScaledMaximumFlingVelocity()).when(mMockConfig) + .getScaledMaximumFlingVelocity(); - mDetector = new SwipeDetector(mTouchSlop, mMockListener, SwipeDetector.VERTICAL); + mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.VERTICAL); mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false); - mTouchSlop = ViewConfiguration.get(InstrumentationRegistry.getTargetContext()) - .getScaledTouchSlop(); + mTouchSlop = orgConfig.getScaledTouchSlop(); + doReturn(mTouchSlop).when(mMockConfig).getScaledTouchSlop(); + L("mTouchSlop=", mTouchSlop); } @Test - public void testDragStart_vertical() throws Exception { + public void testDragStart_vertical() { mGenerator.put(0, 100, 100); mGenerator.move(0, 100, 100 + mTouchSlop); // TODO: actually calculate the following parameters and do exact value checks. @@ -77,7 +80,7 @@ public class SwipeDetectorTest { } @Test - public void testDragStart_failed() throws Exception { + public void testDragStart_failed() { mGenerator.put(0, 100, 100); mGenerator.move(0, 100 + mTouchSlop, 100); // TODO: actually calculate the following parameters and do exact value checks. @@ -85,8 +88,8 @@ public class SwipeDetectorTest { } @Test - public void testDragStart_horizontal() throws Exception { - mDetector = new SwipeDetector(mTouchSlop, mMockListener, SwipeDetector.HORIZONTAL); + public void testDragStart_horizontal() { + mDetector = new SwipeDetector(mMockConfig, mMockListener, SwipeDetector.HORIZONTAL); mDetector.setDetectableScrollConditions(SwipeDetector.DIRECTION_BOTH, false); mGenerator.put(0, 100, 100); @@ -96,15 +99,15 @@ public class SwipeDetectorTest { } @Test - public void testDrag() throws Exception { + public void testDrag() { mGenerator.put(0, 100, 100); mGenerator.move(0, 100, 100 + mTouchSlop); // TODO: actually calculate the following parameters and do exact value checks. - verify(mMockListener).onDrag(anyFloat(), anyFloat()); + verify(mMockListener).onDrag(anyFloat()); } @Test - public void testDragEnd() throws Exception { + public void testDragEnd() { mGenerator.put(0, 100, 100); mGenerator.move(0, 100, 100 + mTouchSlop); mGenerator.move(0, 100, 100 + mTouchSlop * 2); diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java index ba7d9c5237..9cbab5e183 100644 --- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java +++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java @@ -17,6 +17,7 @@ package com.android.launcher3.ui; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; import android.app.Instrumentation; import android.content.BroadcastReceiver; @@ -74,7 +75,7 @@ public abstract class AbstractLauncherUiTest { public static final long DEFAULT_BROADCAST_TIMEOUT_SECS = 5; public static final long SHORT_UI_TIMEOUT= 300; - public static final long DEFAULT_UI_TIMEOUT = 3000; + public static final long DEFAULT_UI_TIMEOUT = 10000; public static final long DEFAULT_WORKER_TIMEOUT_SECS = 5; protected MainThreadExecutor mMainThreadExecutor = new MainThreadExecutor(); @@ -82,12 +83,17 @@ public abstract class AbstractLauncherUiTest { protected final LauncherInstrumentation mLauncher; protected Context mTargetContext; protected String mTargetPackage; + protected final boolean mIsInLauncherProcess; private static final String TAG = "AbstractLauncherUiTest"; protected AbstractLauncherUiTest() { - mDevice = UiDevice.getInstance(getInstrumentation()); - mLauncher = new LauncherInstrumentation(getInstrumentation()); + final Instrumentation instrumentation = getInstrumentation(); + mDevice = UiDevice.getInstance(instrumentation); + mLauncher = new LauncherInstrumentation(instrumentation); + + mIsInLauncherProcess = instrumentation.getTargetContext().getPackageName().equals( + mDevice.getLauncherPackageName()); } @Rule @@ -146,16 +152,20 @@ public abstract class AbstractLauncherUiTest { * @return the matching object. */ protected UiObject2 scrollAndFind(UiObject2 container, BySelector condition) { - do { + container.setGestureMargins(0, 0, 0, 200); + + int i = 0; + for (; ; ) { // findObject can only execute after spring settles. mDevice.wait(Until.findObject(condition), SHORT_UI_TIMEOUT); UiObject2 widget = container.findObject(condition); if (widget != null && widget.getVisibleBounds().intersects( - 0, 0, mDevice.getDisplayWidth(), mDevice.getDisplayHeight())) { + 0, 0, mDevice.getDisplayWidth(), mDevice.getDisplayHeight() - 200)) { return widget; } - } while (container.scroll(Direction.DOWN, 1f)); - return container.findObject(condition); + if (++i > 40) fail("Too many attempts"); + container.scroll(Direction.DOWN, 1f); + } } /** @@ -261,6 +271,7 @@ public abstract class AbstractLauncherUiTest { } protected T getFromLauncher(Function f) { + if (!mIsInLauncherProcess) return null; return getOnUiThread(() -> f.apply(mActivityMonitor.getActivity())); } @@ -287,6 +298,7 @@ public abstract class AbstractLauncherUiTest { // flakiness. protected boolean waitForLauncherCondition( Function condition, long timeout) { + if (!mIsInLauncherProcess) return true; return Wait.atMost(() -> getFromLauncher(condition), timeout); } diff --git a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java index 23560e881a..a50a8b1a1e 100644 --- a/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java +++ b/tests/src/com/android/launcher3/ui/AllAppsIconToHomeTest.java @@ -42,7 +42,8 @@ public class AllAppsIconToHomeTest extends AbstractLauncherUiTest { LauncherActivityInfo settingsApp = getSettingsApp(); clearHomescreen(); - mActivityMonitor.startLauncher(); + mDevice.pressHome(); + mDevice.waitForIdle(); // Open all apps and wait for load complete. final UiObject2 appsContainer = openAllApps(); diff --git a/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java index ff1dcf3d6c..793bd8f46b 100644 --- a/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java +++ b/tests/src/com/android/launcher3/ui/ShortcutsToHomeTest.java @@ -17,6 +17,7 @@ import com.android.launcher3.util.Condition; import com.android.launcher3.util.Wait; import com.android.launcher3.util.rule.ShellCommandRule; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -31,12 +32,14 @@ public class ShortcutsToHomeTest extends AbstractLauncherUiTest { @Rule public ShellCommandRule mDefaultLauncherRule = ShellCommandRule.setDefaultLauncher(); @Test + @Ignore public void testDragIcon_portrait() throws Throwable { lockRotation(true); performTest(); } @Test + @Ignore public void testDragIcon_landscape() throws Throwable { lockRotation(false); performTest(); diff --git a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java index 050f046d00..d9fef8171f 100644 --- a/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java +++ b/tests/src/com/android/launcher3/ui/widget/AddWidgetTest.java @@ -33,6 +33,7 @@ import com.android.launcher3.util.Wait; import com.android.launcher3.util.rule.ShellCommandRule; import com.android.launcher3.widget.WidgetCell; +import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -47,12 +48,14 @@ public class AddWidgetTest extends AbstractLauncherUiTest { @Rule public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grandWidgetBind(); @Test + @Ignore public void testDragIcon_portrait() throws Throwable { lockRotation(true); performTest(); } @Test + @Ignore public void testDragIcon_landscape() throws Throwable { lockRotation(false); performTest(); diff --git a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java index 307a53eefd..a31d8a6561 100644 --- a/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java +++ b/tests/src/com/android/launcher3/widget/WidgetsListAdapterTest.java @@ -22,7 +22,6 @@ import static org.mockito.Mockito.verify; import android.appwidget.AppWidgetProviderInfo; import android.content.Context; -import android.content.pm.PackageManager; import android.graphics.Bitmap; import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; @@ -129,11 +128,10 @@ public class WidgetsListAdapterTest { if (num <= 0) return result; MultiHashMap newMap = new MultiHashMap(); - PackageManager pm = mContext.getPackageManager(); AppWidgetManagerCompat widgetManager = AppWidgetManagerCompat.getInstance(mContext); for (AppWidgetProviderInfo widgetInfo : widgetManager.getAllProviders(null)) { WidgetItem wi = new WidgetItem(LauncherAppWidgetProviderInfo - .fromProviderInfo(mContext, widgetInfo), pm, mTestProfile); + .fromProviderInfo(mContext, widgetInfo), mTestProfile, mIconCache); PackageItemInfo pInfo = new PackageItemInfo(wi.componentName.getPackageName()); pInfo.title = pInfo.packageName;