diff --git a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java index f42b39fb2d..e8374b813c 100644 --- a/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java +++ b/quickstep/src/com/android/launcher3/appprediction/AppsDividerView.java @@ -35,6 +35,7 @@ import androidx.core.content.ContextCompat; import com.android.launcher3.R; import com.android.launcher3.allapps.FloatingHeaderRow; import com.android.launcher3.allapps.FloatingHeaderView; +import com.android.launcher3.util.OnboardingPrefs; import com.android.launcher3.util.Themes; import com.android.launcher3.views.ActivityContext; @@ -92,8 +93,10 @@ public class AppsDividerView extends View implements FloatingHeaderRow { ? R.color.all_apps_label_text_dark : R.color.all_apps_label_text); - mShowAllAppsLabel = !ActivityContext.lookupContext( - getContext()).getOnboardingPrefs().hasReachedMaxCount(ALL_APPS_VISITED_COUNT); + OnboardingPrefs onboardingPrefs = ActivityContext.lookupContext( + getContext()).getOnboardingPrefs(); + mShowAllAppsLabel = onboardingPrefs == null || !onboardingPrefs.hasReachedMaxCount( + ALL_APPS_VISITED_COUNT); } public void setup(FloatingHeaderView parent, FloatingHeaderRow[] rows, boolean tabsHidden) { @@ -216,8 +219,8 @@ public class AppsDividerView extends View implements FloatingHeaderRow { CharSequence allAppsLabelText = getResources().getText(R.string.all_apps_label); mAllAppsLabelLayout = StaticLayout.Builder.obtain( - allAppsLabelText, 0, allAppsLabelText.length(), mPaint, - Math.round(mPaint.measureText(allAppsLabelText.toString()))) + allAppsLabelText, 0, allAppsLabelText.length(), mPaint, + Math.round(mPaint.measureText(allAppsLabelText.toString()))) .setAlignment(Layout.Alignment.ALIGN_CENTER) .setMaxLines(1) .setIncludePad(true) diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index 1f7b7de0e1..b7a50fc35e 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -334,6 +334,12 @@ public abstract class AbsSwipeUpHandler, return ActiveGestureErrorDetector.GestureEvent.STATE_GESTURE_COMPLETED; } else if (stateFlag == STATE_GESTURE_CANCELLED) { return ActiveGestureErrorDetector.GestureEvent.STATE_GESTURE_CANCELLED; + } else if (stateFlag == STATE_SCREENSHOT_CAPTURED) { + return ActiveGestureErrorDetector.GestureEvent.STATE_SCREENSHOT_CAPTURED; + } else if (stateFlag == STATE_CAPTURE_SCREENSHOT) { + return ActiveGestureErrorDetector.GestureEvent.STATE_CAPTURE_SCREENSHOT; + } else if (stateFlag == STATE_HANDLER_INVALIDATED) { + return ActiveGestureErrorDetector.GestureEvent.STATE_HANDLER_INVALIDATED; } return null; } @@ -1222,6 +1228,8 @@ public abstract class AbsSwipeUpHandler, // Let RecentsView handle the scrolling to the task, which we launch in startNewTask() // or resumeLastTask(). if (mRecentsView != null) { + ActiveGestureLog.INSTANCE.trackEvent(ActiveGestureErrorDetector.GestureEvent + .SET_ON_PAGE_TRANSITION_END_CALLBACK); mRecentsView.setOnPageTransitionEndCallback( () -> mGestureState.setState(STATE_RECENTS_SCROLLING_FINISHED)); } else { @@ -1535,7 +1543,7 @@ public abstract class AbsSwipeUpHandler, Rect keepClearArea; if (!ENABLE_PIP_KEEP_CLEAR_ALGORITHM) { // make the height equal to hotseatBarSizePx only - keepClearArea = new Rect(0, 0, mDp.hotseatBarSizePx, 0); + keepClearArea = new Rect(0, 0, 0, mDp.hotseatBarSizePx); return keepClearArea; } // the keep clear area in global screen coordinates, in pixels @@ -1699,6 +1707,9 @@ public abstract class AbsSwipeUpHandler, * handler (in case of quick switch). */ private void cancelCurrentAnimation() { + ActiveGestureLog.INSTANCE.addLog( + "AbsSwipeUpHandler.cancelCurrentAnimation", + ActiveGestureErrorDetector.GestureEvent.CANCEL_CURRENT_ANIMATION); mCanceled = true; mCurrentShift.cancelAnimation(); diff --git a/quickstep/src/com/android/quickstep/GestureState.java b/quickstep/src/com/android/quickstep/GestureState.java index bc2f55137b..38bf1fd6d8 100644 --- a/quickstep/src/com/android/quickstep/GestureState.java +++ b/quickstep/src/com/android/quickstep/GestureState.java @@ -189,6 +189,8 @@ public class GestureState implements RecentsAnimationCallbacks.RecentsAnimationL return ActiveGestureErrorDetector.GestureEvent.STATE_END_TARGET_ANIMATION_FINISHED; } else if (stateFlag == STATE_RECENTS_SCROLLING_FINISHED) { return ActiveGestureErrorDetector.GestureEvent.STATE_RECENTS_SCROLLING_FINISHED; + } else if (stateFlag == STATE_RECENTS_ANIMATION_CANCELED) { + return ActiveGestureErrorDetector.GestureEvent.STATE_RECENTS_ANIMATION_CANCELED; } return null; } diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationController.java b/quickstep/src/com/android/quickstep/RecentsAnimationController.java index fefef2f45e..542c0d4b21 100644 --- a/quickstep/src/com/android/quickstep/RecentsAnimationController.java +++ b/quickstep/src/com/android/quickstep/RecentsAnimationController.java @@ -32,6 +32,8 @@ import androidx.annotation.UiThread; import com.android.launcher3.util.Preconditions; import com.android.launcher3.util.RunnableList; +import com.android.quickstep.util.ActiveGestureErrorDetector; +import com.android.quickstep.util.ActiveGestureLog; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.InteractionJankMonitorWrapper; import com.android.systemui.shared.system.RecentsAnimationControllerCompat; @@ -172,7 +174,12 @@ public class RecentsAnimationController { */ @UiThread public void cleanupScreenshot() { - UI_HELPER_EXECUTOR.execute(() -> mController.cleanupScreenshot()); + UI_HELPER_EXECUTOR.execute(() -> { + ActiveGestureLog.INSTANCE.addLog( + "cleanupScreenshot", + ActiveGestureErrorDetector.GestureEvent.CLEANUP_SCREENSHOT); + mController.cleanupScreenshot(); + }); } /** diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java index 03147619e5..5fb806dacf 100644 --- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java +++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java @@ -36,6 +36,8 @@ import androidx.annotation.UiThread; import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; import com.android.quickstep.TopTaskTracker.CachedTaskInfo; +import com.android.quickstep.util.ActiveGestureErrorDetector; +import com.android.quickstep.util.ActiveGestureLog; import com.android.quickstep.views.RecentsView; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.ActivityManagerWrapper; @@ -136,6 +138,8 @@ public class TaskAnimationManager implements RecentsAnimationCallbacks.RecentsAn // handling this call entirely return; } + ActiveGestureLog.INSTANCE.addLog("TaskAnimationManager.startRecentsAnimation", + ActiveGestureErrorDetector.GestureEvent.START_RECENTS_ANIMATION); mController = controller; mTargets = targets; mLastAppearedTaskTarget = mTargets.findTask(mLastGestureState.getRunningTaskId()); diff --git a/quickstep/src/com/android/quickstep/TaskViewUtils.java b/quickstep/src/com/android/quickstep/TaskViewUtils.java index 93170cb79b..556b99ec16 100644 --- a/quickstep/src/com/android/quickstep/TaskViewUtils.java +++ b/quickstep/src/com/android/quickstep/TaskViewUtils.java @@ -43,7 +43,6 @@ import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.MOD import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorSet; -import android.animation.ObjectAnimator; import android.animation.ValueAnimator; import android.annotation.TargetApi; import android.app.PendingIntent; @@ -198,8 +197,7 @@ public final class TaskViewUtils { BaseActivity baseActivity = BaseActivity.fromContext(context); DeviceProfile dp = baseActivity.getDeviceProfile(); boolean showAsGrid = dp.isTablet; - boolean parallaxCenterAndAdjacentTask = - taskIndex != recentsView.getCurrentPage() && !showAsGrid; + boolean parallaxCenterAndAdjacentTask = taskIndex != recentsView.getCurrentPage(); int taskRectTranslationPrimary = recentsView.getScrollOffset(taskIndex); int taskRectTranslationSecondary = showAsGrid ? (int) v.getGridTranslationY() : 0; @@ -603,11 +601,7 @@ public final class TaskViewUtils { if (raController != null) { raController.setWillFinishToHome(false); } - Context context = v.getContext(); - DeviceProfile dp = BaseActivity.fromContext(context).getDeviceProfile(); - launcherAnim = dp.isTablet - ? ObjectAnimator.ofFloat(recentsView, RecentsView.CONTENT_ALPHA, 0) - : recentsView.createAdjacentPageAnimForTaskLaunch(taskView); + launcherAnim = recentsView.createAdjacentPageAnimForTaskLaunch(taskView); launcherAnim.setInterpolator(Interpolators.TOUCH_RESPONSE_INTERPOLATOR); launcherAnim.setDuration(RECENTS_LAUNCH_DURATION); diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index 82be3ec811..ba4f549bbb 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -721,8 +721,10 @@ public class TouchInteractionService extends Service gestureState.updateRunningTask(taskInfo); } // Log initial state for the gesture. - ActiveGestureLog.INSTANCE.addLog( - "Current SystemUi state flags= " + mDeviceState.getSystemUiStateString()); + ActiveGestureLog.INSTANCE.addLog(new CompoundString("Current running task package name=") + .append(taskInfo == null ? "no running task" : taskInfo.getPackageName())); + ActiveGestureLog.INSTANCE.addLog(new CompoundString("Current SystemUi state flags=") + .append(mDeviceState.getSystemUiStateString())); return gestureState; } @@ -1024,12 +1026,27 @@ public class TouchInteractionService extends Service .append("activity == null, trying to use default input consumer")); } - if (activity.getRootView().hasWindowFocus() - || previousGestureState.isRunningAnimationToLauncher() - || (ASSISTANT_GIVES_LAUNCHER_FOCUS.get() - && forceOverviewInputConsumer) - || (ENABLE_QUICKSTEP_LIVE_TILE.get() - && gestureState.getActivityInterface().isInLiveTileMode())) { + boolean hasWindowFocus = activity.getRootView().hasWindowFocus(); + boolean isPreviousGestureAnimatingToLauncher = + previousGestureState.isRunningAnimationToLauncher(); + boolean forcingOverviewInputConsumer = + ASSISTANT_GIVES_LAUNCHER_FOCUS.get() && forceOverviewInputConsumer; + boolean isInLiveTileMode = ENABLE_QUICKSTEP_LIVE_TILE.get() + && gestureState.getActivityInterface().isInLiveTileMode(); + reasonString.append(SUBSTRING_PREFIX) + .append(hasWindowFocus + ? "activity has window focus" + : (isPreviousGestureAnimatingToLauncher + ? "previous gesture is still animating to launcher" + : (forcingOverviewInputConsumer + ? "assistant gives launcher focus and forcing focus" + : (isInLiveTileMode + ? "device is in live mode" + : "all overview focus conditions failed")))); + if (hasWindowFocus + || isPreviousGestureAnimatingToLauncher + || forcingOverviewInputConsumer + || isInLiveTileMode) { reasonString.append(SUBSTRING_PREFIX) .append("overview should have focus, using OverviewInputConsumer"); return new OverviewInputConsumer(gestureState, activity, mInputMonitorCompat, diff --git a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java index 246239449f..b9b5e7c2b9 100644 --- a/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java +++ b/quickstep/src/com/android/quickstep/inputconsumers/ProgressDelegateInputConsumer.java @@ -28,6 +28,8 @@ import android.content.Intent; import android.graphics.Point; import android.view.MotionEvent; +import androidx.annotation.Nullable; + import com.android.launcher3.anim.AnimatorListeners; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.shared.TestProtocol; @@ -41,6 +43,7 @@ import com.android.quickstep.RecentsAnimationCallbacks; import com.android.quickstep.RecentsAnimationController; import com.android.quickstep.RecentsAnimationTargets; import com.android.quickstep.TaskAnimationManager; +import com.android.quickstep.util.ActiveGestureErrorDetector; import com.android.systemui.shared.recents.model.ThumbnailData; import com.android.systemui.shared.system.InputMonitorCompat; @@ -99,7 +102,8 @@ public class ProgressDelegateInputConsumer implements InputConsumer, mDisplaySize = DisplayController.INSTANCE.get(context).getInfo().currentSize; // Init states - mStateCallback = new MultiStateCallback(STATE_NAMES); + mStateCallback = new MultiStateCallback( + STATE_NAMES, ProgressDelegateInputConsumer::getTrackedEventForState); mStateCallback.runOnceAtState(STATE_TARGET_RECEIVED | STATE_HANDLER_INVALIDATED, this::endRemoteAnimation); mStateCallback.runOnceAtState(STATE_TARGET_RECEIVED | STATE_FLING_FINISHED, @@ -109,6 +113,14 @@ public class ProgressDelegateInputConsumer implements InputConsumer, mSwipeDetector.setDetectableScrollConditions(DIRECTION_POSITIVE, false); } + @Nullable + private static ActiveGestureErrorDetector.GestureEvent getTrackedEventForState(int stateFlag) { + if (stateFlag == STATE_HANDLER_INVALIDATED) { + return ActiveGestureErrorDetector.GestureEvent.STATE_HANDLER_INVALIDATED; + } + return null; + } + @Override public int getType() { return TYPE_PROGRESS_DELEGATE; diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java index 78075def8f..54f632afdb 100644 --- a/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java +++ b/quickstep/src/com/android/quickstep/util/ActiveGestureErrorDetector.java @@ -29,11 +29,24 @@ import java.util.Set; */ public class ActiveGestureErrorDetector { + /** + * Enums associated to gesture navigation events. + */ public enum GestureEvent { MOTION_DOWN, MOTION_UP, SET_END_TARGET, ON_SETTLED_ON_END_TARGET, START_RECENTS_ANIMATION, - FINISH_RECENTS_ANIMATION, CANCEL_RECENTS_ANIMATION, STATE_GESTURE_STARTED, - STATE_GESTURE_COMPLETED, STATE_GESTURE_CANCELLED, STATE_END_TARGET_ANIMATION_FINISHED, - STATE_RECENTS_SCROLLING_FINISHED + FINISH_RECENTS_ANIMATION, CANCEL_RECENTS_ANIMATION, SET_ON_PAGE_TRANSITION_END_CALLBACK, + CANCEL_CURRENT_ANIMATION, CLEANUP_SCREENSHOT, + + /** + * These GestureEvents are specifically associated to state flags that get set in + * {@link com.android.quickstep.MultiStateCallback}. If a state flag needs to be tracked + * for error detection, an enum should be added here and that state flag-enum pair should + * be added to the state flag's container class' {@code getTrackedEventForState} method. + */ + STATE_GESTURE_STARTED, STATE_GESTURE_COMPLETED, STATE_GESTURE_CANCELLED, + STATE_END_TARGET_ANIMATION_FINISHED, STATE_RECENTS_SCROLLING_FINISHED, + STATE_CAPTURE_SCREENSHOT, STATE_SCREENSHOT_CAPTURED, STATE_HANDLER_INVALIDATED, + STATE_RECENTS_ANIMATION_CANCELED } private ActiveGestureErrorDetector() {} @@ -90,6 +103,14 @@ public class ActiveGestureErrorDetector { + "before/without startRecentsAnimation.", writer); break; + case CLEANUP_SCREENSHOT: + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains(GestureEvent.STATE_SCREENSHOT_CAPTURED), + /* errorMessage= */ prefix + "\t\trecents activity screenshot was " + + "cleaned up before/without STATE_SCREENSHOT_CAPTURED " + + "being set.", + writer); + break; case STATE_GESTURE_COMPLETED: errorDetected |= printErrorIfTrue( !encounteredEvents.contains(GestureEvent.MOTION_UP), @@ -114,12 +135,39 @@ public class ActiveGestureErrorDetector { + "before/without STATE_GESTURE_STARTED.", writer); break; + case STATE_SCREENSHOT_CAPTURED: + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains(GestureEvent.STATE_CAPTURE_SCREENSHOT), + /* errorMessage= */ prefix + "\t\tSTATE_SCREENSHOT_CAPTURED set " + + "before/without STATE_CAPTURE_SCREENSHOT.", + writer); + break; + case STATE_RECENTS_SCROLLING_FINISHED: + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains( + GestureEvent.SET_ON_PAGE_TRANSITION_END_CALLBACK), + /* errorMessage= */ prefix + "\t\tSTATE_RECENTS_SCROLLING_FINISHED " + + "set before/without calling " + + "setOnPageTransitionEndCallback.", + writer); + break; + case STATE_RECENTS_ANIMATION_CANCELED: + errorDetected |= printErrorIfTrue( + !encounteredEvents.contains( + GestureEvent.START_RECENTS_ANIMATION), + /* errorMessage= */ prefix + "\t\tSTATE_RECENTS_ANIMATION_CANCELED " + + "set before/without startRecentsAnimation.", + writer); + break; case MOTION_DOWN: case SET_END_TARGET: case START_RECENTS_ANIMATION: + case SET_ON_PAGE_TRANSITION_END_CALLBACK: + case CANCEL_CURRENT_ANIMATION: case STATE_GESTURE_STARTED: case STATE_END_TARGET_ANIMATION_FINISHED: - case STATE_RECENTS_SCROLLING_FINISHED: + case STATE_CAPTURE_SCREENSHOT: + case STATE_HANDLER_INVALIDATED: default: // No-Op } @@ -183,6 +231,39 @@ public class ActiveGestureErrorDetector { + "STATE_GESTURE_COMPLETED and STATE_GESTURE_CANCELLED weren't.", writer); + errorDetected |= printErrorIfTrue( + /* condition= */ encounteredEvents.contains( + GestureEvent.STATE_CAPTURE_SCREENSHOT) + && !encounteredEvents.contains(GestureEvent.STATE_SCREENSHOT_CAPTURED), + /* errorMessage= */ prefix + "\t\tSTATE_CAPTURE_SCREENSHOT was set, but " + + "STATE_SCREENSHOT_CAPTURED wasn't.", + writer); + + errorDetected |= printErrorIfTrue( + /* condition= */ encounteredEvents.contains( + GestureEvent.SET_ON_PAGE_TRANSITION_END_CALLBACK) + && !encounteredEvents.contains( + GestureEvent.STATE_RECENTS_SCROLLING_FINISHED), + /* errorMessage= */ prefix + "\t\tsetOnPageTransitionEndCallback called, but " + + "STATE_RECENTS_SCROLLING_FINISHED wasn't set.", + writer); + + errorDetected |= printErrorIfTrue( + /* condition= */ !encounteredEvents.contains( + GestureEvent.CANCEL_CURRENT_ANIMATION) + && !encounteredEvents.contains(GestureEvent.STATE_HANDLER_INVALIDATED), + /* errorMessage= */ prefix + "\t\tAbsSwipeUpHandler.cancelCurrentAnimation " + + "wasn't called and STATE_HANDLER_INVALIDATED wasn't set.", + writer); + + errorDetected |= printErrorIfTrue( + /* condition= */ encounteredEvents.contains( + GestureEvent.STATE_RECENTS_ANIMATION_CANCELED) + && !encounteredEvents.contains(GestureEvent.CLEANUP_SCREENSHOT), + /* errorMessage= */ prefix + "\t\tSTATE_RECENTS_ANIMATION_CANCELED was set but " + + "the task screenshot wasn't cleaned up.", + writer); + if (!errorDetected) { writer.println(prefix + "\t\tNo errors detected."); } diff --git a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java index 9f08010b2b..40eb31b727 100644 --- a/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java +++ b/quickstep/src/com/android/quickstep/util/ActiveGestureLog.java @@ -138,14 +138,10 @@ public class ActiveGestureLog { List lastEventEntries = lastEventLog.eventEntries; EventEntry lastEntry = lastEventEntries.size() > 0 ? lastEventEntries.get(lastEventEntries.size() - 1) : null; - EventEntry secondLastEntry = lastEventEntries.size() > 1 - ? lastEventEntries.get(lastEventEntries.size() - 2) : null; // Update the last EventEntry if it's a duplicate - if (isEntrySame(lastEntry, type, event, compoundString, gestureEvent) - && isEntrySame(secondLastEntry, type, event, compoundString, gestureEvent)) { - lastEntry.update(type, event, extras, compoundString, gestureEvent); - secondLastEntry.duplicateCount++; + if (isEntrySame(lastEntry, type, event, extras, compoundString, gestureEvent)) { + lastEntry.duplicateCount++; return; } EventEntry eventEntry = new EventEntry(); @@ -223,11 +219,13 @@ public class ActiveGestureLog { EventEntry entry, int type, String event, + float extras, CompoundString compoundString, ActiveGestureErrorDetector.GestureEvent gestureEvent) { return entry != null && entry.type == type && entry.event.equals(event) + && Float.compare(entry.extras, extras) == 0 && entry.mCompoundString.equals(compoundString) && entry.gestureEvent == gestureEvent; } @@ -342,7 +340,7 @@ public class ActiveGestureLog { return false; } CompoundString other = (CompoundString) obj; - return mIsNoOp && other.mIsNoOp && Objects.equals(mSubstrings, other.mSubstrings); + return (mIsNoOp == other.mIsNoOp) && Objects.equals(mSubstrings, other.mSubstrings); } } } diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java index efaa644502..f00e858b04 100644 --- a/quickstep/src/com/android/quickstep/views/RecentsView.java +++ b/quickstep/src/com/android/quickstep/views/RecentsView.java @@ -4317,25 +4317,27 @@ public abstract class RecentsView16sp 2dp 4dp + 20dp 8dp 16dp 8dp diff --git a/src/com/android/launcher3/ButtonDropTarget.java b/src/com/android/launcher3/ButtonDropTarget.java index 3b24df2f18..5abe3d3af6 100644 --- a/src/com/android/launcher3/ButtonDropTarget.java +++ b/src/com/android/launcher3/ButtonDropTarget.java @@ -91,7 +91,7 @@ public abstract class ButtonDropTarget extends TextView Resources resources = getResources(); mDragDistanceThreshold = resources.getDimensionPixelSize(R.dimen.drag_distanceThreshold); - mDrawableSize = resources.getDimensionPixelSize(R.dimen.drop_target_text_size); + mDrawableSize = resources.getDimensionPixelSize(R.dimen.drop_target_button_drawable_size); mDrawablePadding = resources.getDimensionPixelSize( R.dimen.drop_target_button_drawable_padding); } @@ -374,11 +374,63 @@ public abstract class ButtonDropTarget extends TextView hideTooltip(); } + /** + * Returns if the text will be truncated within the provided availableWidth. + */ public boolean isTextTruncated(int availableWidth) { - availableWidth -= (getPaddingLeft() + getPaddingRight() + mDrawable.getIntrinsicWidth() - + getCompoundDrawablePadding()); - CharSequence displayedText = TextUtils.ellipsize(mText, getPaint(), availableWidth, + availableWidth -= getPaddingLeft() + getPaddingRight(); + if (mIconVisible) { + availableWidth -= mDrawable.getIntrinsicWidth() + getCompoundDrawablePadding(); + } + if (availableWidth <= 0) { + return true; + } + CharSequence firstLine = TextUtils.ellipsize(mText, getPaint(), availableWidth, TextUtils.TruncateAt.END); - return !mText.equals(displayedText); + if (!mTextMultiLine) { + return !TextUtils.equals(mText, firstLine); + } + if (TextUtils.equals(mText, firstLine)) { + // When multi-line is active, if it can display as one line, then text is not truncated. + return false; + } + CharSequence secondLine = + TextUtils.ellipsize(mText.subSequence(firstLine.length(), mText.length()), + getPaint(), availableWidth, TextUtils.TruncateAt.END); + return !(TextUtils.equals(mText.subSequence(0, firstLine.length()), firstLine) + && TextUtils.equals(mText.subSequence(firstLine.length(), secondLine.length()), + secondLine)); + } + + /** + * Reduce the size of the text until it fits the measured width or reaches a minimum. + * + * The minimum size is defined by {@code R.dimen.button_drop_target_min_text_size} and + * it diminishes by intervals defined by + * {@code R.dimen.button_drop_target_resize_text_increment} + * This functionality is very similar to the option + * {@link TextView#setAutoSizeTextTypeWithDefaults(int)} but can't be used in this view because + * the layout width is {@code WRAP_CONTENT}. + * + * @return The biggest text size in SP that makes the text fit or if the text can't fit returns + * the min available value + */ + public float resizeTextToFit() { + float minSize = Utilities.pxToSp(getResources() + .getDimensionPixelSize(R.dimen.button_drop_target_min_text_size)); + float step = Utilities.pxToSp(getResources() + .getDimensionPixelSize(R.dimen.button_drop_target_resize_text_increment)); + float textSize = Utilities.pxToSp(getTextSize()); + + int availableWidth = getMeasuredWidth(); + while (textSize > minSize) { + if (isTextTruncated(availableWidth)) { + textSize -= step; + setTextSize(textSize); + } else { + return textSize; + } + } + return minSize; } } diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java index a330fb3c7e..1febd68a17 100644 --- a/src/com/android/launcher3/DeviceProfile.java +++ b/src/com/android/launcher3/DeviceProfile.java @@ -1037,7 +1037,7 @@ public class DeviceProfile { / getCellLayoutHeight(); scale = Math.min(scale, 1f); - // Reduce scale if next pages would not be visible after scaling the workspace + // Reduce scale if next pages would not be visible after scaling the workspace. int workspaceWidth = availableWidthPx; float scaledWorkspaceWidth = workspaceWidth * scale; float maxAvailableWidth = workspaceWidth - (2 * workspaceSpringLoadedMinNextPageVisiblePx); diff --git a/src/com/android/launcher3/DropTargetBar.java b/src/com/android/launcher3/DropTargetBar.java index d64cb26ff4..98ecf3a7e6 100644 --- a/src/com/android/launcher3/DropTargetBar.java +++ b/src/com/android/launcher3/DropTargetBar.java @@ -151,6 +151,8 @@ public class DropTargetBar extends FrameLayout int widthSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST); ButtonDropTarget firstButton = mTempTargets[0]; + firstButton.setTextSize(TypedValue.COMPLEX_UNIT_PX, + mLauncher.getDeviceProfile().dropTargetTextSizePx); firstButton.setTextVisible(true); firstButton.setIconVisible(true); firstButton.measure(widthSpec, heightSpec); @@ -160,14 +162,16 @@ public class DropTargetBar extends FrameLayout int horizontalPadding = dp.dropTargetHorizontalPaddingPx; ButtonDropTarget firstButton = mTempTargets[0]; + firstButton.setTextSize(TypedValue.COMPLEX_UNIT_PX, dp.dropTargetTextSizePx); firstButton.setTextVisible(true); firstButton.setIconVisible(true); firstButton.setTextMultiLine(false); - // Reset second button padding in case it was previously changed to multi-line text. + // Reset first button padding in case it was previously changed to multi-line text. firstButton.setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding); ButtonDropTarget secondButton = mTempTargets[1]; + secondButton.setTextSize(TypedValue.COMPLEX_UNIT_PX, dp.dropTargetTextSizePx); secondButton.setTextVisible(true); secondButton.setIconVisible(true); secondButton.setTextMultiLine(false); @@ -175,20 +179,14 @@ public class DropTargetBar extends FrameLayout secondButton.setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding); - float scale = dp.getWorkspaceSpringLoadScale(mLauncher); - int scaledPanelWidth = (int) (dp.getCellLayoutWidth() * scale); - int availableWidth; if (dp.isTwoPanels) { - // Both buttons for two panel fit to the width of one Cell Layout (less - // half of the center gap between the buttons). - int halfButtonGap = dp.dropTargetGapPx / 2; - availableWidth = scaledPanelWidth - halfButtonGap / 2; + // Each button for two panel fits to half the width of the screen excluding the + // center gap between the buttons. + availableWidth = (dp.availableWidthPx - dp.dropTargetGapPx) / 2; } else { - // Both buttons plus the button gap do not display past the edge of the scaled - // workspace, less a pre-defined gap from the edge of the workspace. - availableWidth = scaledPanelWidth - dp.dropTargetGapPx - - 2 * dp.dropTargetButtonWorkspaceEdgeGapPx; + // Both buttons plus the button gap do not display past the edge of the screen. + availableWidth = dp.availableWidthPx - dp.dropTargetGapPx; } int widthSpec = MeasureSpec.makeMeasureSpec(availableWidth, MeasureSpec.AT_MOST); @@ -219,6 +217,15 @@ public class DropTargetBar extends FrameLayout horizontalPadding, verticalPadding / 2); } } + + // If text is still truncated, shrink to fit in measured width and resize both targets. + float minTextSize = + Math.min(firstButton.resizeTextToFit(), secondButton.resizeTextToFit()); + if (firstButton.getTextSize() != minTextSize + || secondButton.getTextSize() != minTextSize) { + firstButton.setTextSize(minTextSize); + secondButton.setTextSize(minTextSize); + } } setMeasuredDimension(width, height); } diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java index 33b2f55c12..a55f7e3b69 100644 --- a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java +++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java @@ -73,11 +73,11 @@ public class SecondaryDisplayLauncher extends BaseDraggingActivity protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mModel = LauncherAppState.getInstance(this).getModel(); + mOnboardingPrefs = new OnboardingPrefs<>(this, Utilities.getPrefs(this)); + mSecondaryDisplayPredictions = SecondaryDisplayPredictions.newInstance(this); if (getWindow().getDecorView().isAttachedToWindow()) { initUi(); } - mOnboardingPrefs = new OnboardingPrefs<>(this, Utilities.getPrefs(this)); - mSecondaryDisplayPredictions = SecondaryDisplayPredictions.newInstance(this); } @Override diff --git a/src/com/android/launcher3/views/ActivityContext.java b/src/com/android/launcher3/views/ActivityContext.java index 93078e4cf6..e2dc34fb67 100644 --- a/src/com/android/launcher3/views/ActivityContext.java +++ b/src/com/android/launcher3/views/ActivityContext.java @@ -141,6 +141,7 @@ public interface ActivityContext { default void applyOverwritesToLogItem(LauncherAtom.ItemInfo.Builder itemInfoBuilder) { } /** Onboarding preferences for any onboarding data within this context. */ + @Nullable default OnboardingPrefs getOnboardingPrefs() { return null; }