Merge "Making setActiveOverlay and getCurrentOverlayPackage public" into ub-launcher3-master
diff --git a/Android.mk b/Android.mk
index 3a9a2c3..a099ada 100644
--- a/Android.mk
+++ b/Android.mk
@@ -78,9 +78,8 @@
LOCAL_USE_AAPT2 := true
LOCAL_MODULE_TAGS := optional
-LOCAL_STATIC_ANDROID_LIBRARIES := \
- Launcher3CommonDepsLib \
- SecondaryDisplayLauncherLib
+LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
+
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
$(call all-java-files-under, src_shortcuts_overrides) \
@@ -154,9 +153,7 @@
endif
LOCAL_MODULE := Launcher3QuickStepLib
LOCAL_PRIVILEGED_MODULE := true
-LOCAL_STATIC_ANDROID_LIBRARIES := \
- Launcher3CommonDepsLib \
- SecondaryDisplayLauncherLib
+LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 555cc73..26698eb 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -184,5 +184,20 @@
android:writePermission="android.permission.WRITE_SECURE_SETTINGS"
android:exported="true"
android:enabled="false" />
+
+ <!--
+ Launcher activity for secondary display
+ -->
+ <activity
+ android:name="com.android.launcher3.secondarydisplay.SecondaryDisplayLauncher"
+ android:theme="@style/AppTheme"
+ android:launchMode="singleTop"
+ android:enabled="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.SECONDARY_HOME" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 4b5ba95..c359423 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -37,6 +37,7 @@
import com.android.launcher3.uioverrides.touchcontrollers.FlingAndHoldTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.LandscapeEdgeSwipeController;
import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
+import com.android.launcher3.uioverrides.touchcontrollers.NoButtonNavbarToOverviewTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.NoButtonQuickSwitchTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.OverviewToAllAppsTouchController;
import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
@@ -224,7 +225,11 @@
if (mode == NO_BUTTON) {
list.add(new NoButtonQuickSwitchTouchController(this));
list.add(new NavBarToHomeTouchController(this));
- list.add(new FlingAndHoldTouchController(this));
+ if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
+ list.add(new NoButtonNavbarToOverviewTouchController(this));
+ } else {
+ list.add(new FlingAndHoldTouchController(this));
+ }
} else {
if (getDeviceProfile().isVerticalBarLayout()) {
list.add(new OverviewToAllAppsTouchController(this));
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
index 73c0c97..9c78af9 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/states/OverviewState.java
@@ -30,11 +30,13 @@
import static com.android.launcher3.anim.Interpolators.DEACCEL_2;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_7;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import static com.android.launcher3.states.RotationHelper.REQUEST_ROTATE;
import android.graphics.Rect;
import android.view.View;
+import android.view.animation.Interpolator;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
@@ -44,7 +46,6 @@
import com.android.launcher3.Workspace;
import com.android.launcher3.allapps.DiscoveryBounce;
import com.android.launcher3.anim.AnimatorSetBuilder;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.quickstep.SysUINavigationMode;
@@ -116,6 +117,15 @@
}
@Override
+ public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
+ if (this == OVERVIEW && ENABLE_OVERVIEW_ACTIONS.get()) {
+ // Treat the QSB as part of the hotseat so they move together.
+ return getHotseatScaleAndTranslation(launcher);
+ }
+ return super.getQsbScaleAndTranslation(launcher);
+ }
+
+ @Override
public void onStateEnabled(Launcher launcher) {
AbstractFloatingView.closeAllOpenViews(launcher);
}
@@ -141,7 +151,7 @@
if (launcher.getDeviceProfile().isVerticalBarLayout()) {
return VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON;
} else {
- if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
+ if (ENABLE_OVERVIEW_ACTIONS.get()) {
return VERTICAL_SWIPE_INDICATOR | RECENTS_CLEAR_ALL_BUTTON;
}
@@ -195,9 +205,10 @@
@Override
public void prepareForAtomicAnimation(Launcher launcher, LauncherState fromState,
AnimatorSetBuilder builder) {
- if (fromState == NORMAL && this == OVERVIEW) {
+ if ((fromState == NORMAL || fromState == HINT_STATE) && this == OVERVIEW) {
if (SysUINavigationMode.getMode(launcher) == SysUINavigationMode.Mode.NO_BUTTON) {
- builder.setInterpolator(ANIM_WORKSPACE_SCALE, ACCEL);
+ builder.setInterpolator(ANIM_WORKSPACE_SCALE,
+ fromState == NORMAL ? ACCEL : OVERSHOOT_1_2);
builder.setInterpolator(ANIM_WORKSPACE_TRANSLATE, ACCEL);
} else {
builder.setInterpolator(ANIM_WORKSPACE_SCALE, OVERSHOOT_1_2);
@@ -210,8 +221,11 @@
}
builder.setInterpolator(ANIM_WORKSPACE_FADE, OVERSHOOT_1_2);
builder.setInterpolator(ANIM_OVERVIEW_SCALE, OVERSHOOT_1_2);
- builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, OVERSHOOT_1_7);
- builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, OVERSHOOT_1_7);
+ Interpolator translationInterpolator = ENABLE_OVERVIEW_ACTIONS.get()
+ ? OVERSHOOT_1_2
+ : OVERSHOOT_1_7;
+ builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_X, translationInterpolator);
+ builder.setInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, translationInterpolator);
builder.setInterpolator(ANIM_OVERVIEW_FADE, OVERSHOOT_1_2);
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
index d388f49..ff1b5f6 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/FlingAndHoldTouchController.java
@@ -60,7 +60,7 @@
private static final long PEEK_OUT_ANIM_DURATION = 100;
private static final float MAX_DISPLACEMENT_PERCENT = 0.75f;
- private final MotionPauseDetector mMotionPauseDetector;
+ protected final MotionPauseDetector mMotionPauseDetector;
private final float mMotionPauseMinDisplacement;
private final float mMotionPauseMaxDisplacement;
@@ -85,37 +85,39 @@
super.onDragStart(start);
if (handlingOverviewAnim()) {
- mMotionPauseDetector.setOnMotionPauseListener(isPaused -> {
- RecentsView recentsView = mLauncher.getOverviewPanel();
- recentsView.setOverviewStateEnabled(isPaused);
- if (mPeekAnim != null) {
- mPeekAnim.cancel();
- }
- LauncherState fromState = isPaused ? NORMAL : OVERVIEW_PEEK;
- LauncherState toState = isPaused ? OVERVIEW_PEEK : NORMAL;
- long peekDuration = isPaused ? PEEK_IN_ANIM_DURATION : PEEK_OUT_ANIM_DURATION;
- mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(fromState, toState,
- new AnimatorSetBuilder(), ATOMIC_OVERVIEW_PEEK_COMPONENT, peekDuration);
- mPeekAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- mPeekAnim = null;
- }
- });
- mPeekAnim.start();
- VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
-
- mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(isPaused ? 0 : 1,
- peekDuration, 0);
- });
+ mMotionPauseDetector.setOnMotionPauseListener(this::onMotionPauseChanged);
}
}
+ protected void onMotionPauseChanged(boolean isPaused) {
+ RecentsView recentsView = mLauncher.getOverviewPanel();
+ recentsView.setOverviewStateEnabled(isPaused);
+ if (mPeekAnim != null) {
+ mPeekAnim.cancel();
+ }
+ LauncherState fromState = isPaused ? NORMAL : OVERVIEW_PEEK;
+ LauncherState toState = isPaused ? OVERVIEW_PEEK : NORMAL;
+ long peekDuration = isPaused ? PEEK_IN_ANIM_DURATION : PEEK_OUT_ANIM_DURATION;
+ mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(fromState, toState,
+ new AnimatorSetBuilder(), ATOMIC_OVERVIEW_PEEK_COMPONENT, peekDuration);
+ mPeekAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mPeekAnim = null;
+ }
+ });
+ mPeekAnim.start();
+ VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
+
+ mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(isPaused ? 0 : 1,
+ peekDuration, 0);
+ }
+
/**
* @return Whether we are handling the overview animation, rather than
* having it as part of the existing animation to the target state.
*/
- private boolean handlingOverviewAnim() {
+ protected boolean handlingOverviewAnim() {
int stateFlags = SystemUiProxy.INSTANCE.get(mLauncher).getLastSystemUiStateFlags();
return mStartState == NORMAL && (stateFlags & SYSUI_STATE_OVERVIEW_DISABLED) == 0;
}
@@ -162,7 +164,8 @@
@Override
public boolean onDrag(float displacement, MotionEvent event) {
float upDisplacement = -displacement;
- mMotionPauseDetector.setDisallowPause(upDisplacement < mMotionPauseMinDisplacement
+ mMotionPauseDetector.setDisallowPause(!handlingOverviewAnim()
+ || upDisplacement < mMotionPauseMinDisplacement
|| upDisplacement > mMotionPauseMaxDisplacement);
mMotionPauseDetector.addPosition(displacement, event.getEventTime());
return super.onDrag(displacement, event);
@@ -171,19 +174,7 @@
@Override
public void onDragEnd(float velocity) {
if (mMotionPauseDetector.isPaused() && handlingOverviewAnim()) {
- if (mPeekAnim != null) {
- mPeekAnim.cancel();
- }
-
- Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation(
- INDEX_PAUSE_TO_OVERVIEW_ANIM);
- overviewAnim.addListener(new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE);
- }
- });
- overviewAnim.start();
+ goToOverviewOnDragEnd(velocity);
} else {
super.onDragEnd(velocity);
}
@@ -195,6 +186,22 @@
mMotionPauseDetector.clear();
}
+ protected void goToOverviewOnDragEnd(float velocity) {
+ if (mPeekAnim != null) {
+ mPeekAnim.cancel();
+ }
+
+ Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation(
+ INDEX_PAUSE_TO_OVERVIEW_ANIM);
+ overviewAnim.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE);
+ }
+ });
+ overviewAnim.start();
+ }
+
@Override
protected void goToTargetState(LauncherState targetState, int logAction) {
if (mPeekAnim != null && mPeekAnim.isStarted()) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
new file mode 100644
index 0000000..2ac2d2d
--- /dev/null
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonNavbarToOverviewTouchController.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2020 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.uioverrides.touchcontrollers;
+
+import static com.android.launcher3.LauncherState.HINT_STATE;
+import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
+import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_PEEK_COMPONENT;
+import static com.android.launcher3.Utilities.EDGE_NAV_BAR;
+import static com.android.launcher3.anim.Interpolators.ACCEL_DEACCEL;
+import static com.android.launcher3.util.VibratorWrapper.OVERVIEW_HAPTIC;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ValueAnimator;
+import android.graphics.PointF;
+import android.view.MotionEvent;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
+import com.android.launcher3.util.VibratorWrapper;
+import com.android.quickstep.util.StaggeredWorkspaceAnim;
+import com.android.quickstep.views.RecentsView;
+
+/**
+ * Touch controller which handles swipe and hold from the nav bar to go to Overview. Swiping above
+ * the nav bar falls back to go to All Apps. Swiping from the nav bar without holding goes to the
+ * first home screen instead of to Overview.
+ */
+public class NoButtonNavbarToOverviewTouchController extends FlingAndHoldTouchController {
+
+
+ // How much of the movement to use for translating overview after swipe and hold.
+ private static final float OVERVIEW_MOVEMENT_FACTOR = 0.25f;
+ private static final long TRANSLATION_ANIM_MIN_DURATION_MS = 80;
+ private static final float TRANSLATION_ANIM_VELOCITY_DP_PER_MS = 0.8f;
+
+ private final RecentsView mRecentsView;
+
+ private boolean mDidTouchStartInNavBar;
+ private boolean mReachedOverview;
+ // The last recorded displacement before we reached overview.
+ private PointF mStartDisplacement = new PointF();
+
+ public NoButtonNavbarToOverviewTouchController(Launcher l) {
+ super(l);
+ mRecentsView = l.getOverviewPanel();
+ }
+
+ @Override
+ protected boolean canInterceptTouch(MotionEvent ev) {
+ mDidTouchStartInNavBar = (ev.getEdgeFlags() & EDGE_NAV_BAR) != 0;
+ return super.canInterceptTouch(ev);
+ }
+
+ @Override
+ protected LauncherState getTargetState(LauncherState fromState, boolean isDragTowardPositive) {
+ if (fromState == NORMAL && mDidTouchStartInNavBar) {
+ return HINT_STATE;
+ } else if (fromState == OVERVIEW && isDragTowardPositive) {
+ // Don't allow swiping up to all apps.
+ return OVERVIEW;
+ }
+ return super.getTargetState(fromState, isDragTowardPositive);
+ }
+
+ @Override
+ protected float initCurrentAnimation(int animComponents) {
+ float progressMultiplier = super.initCurrentAnimation(animComponents);
+ if (mToState == HINT_STATE) {
+ // Track the drag across the entire height of the screen.
+ progressMultiplier = -1 / getShiftRange();
+ }
+ return progressMultiplier;
+ }
+
+ @Override
+ public void onDragStart(boolean start) {
+ super.onDragStart(start);
+
+ mReachedOverview = false;
+ }
+
+ @Override
+ protected void updateSwipeCompleteAnimation(ValueAnimator animator, long expectedDuration,
+ LauncherState targetState, float velocity, boolean isFling) {
+ super.updateSwipeCompleteAnimation(animator, expectedDuration, targetState, velocity,
+ isFling);
+ if (targetState == HINT_STATE) {
+ // Normally we compute the duration based on the velocity and distance to the given
+ // state, but since the hint state tracks the entire screen without a clear endpoint, we
+ // need to manually set the duration to a reasonable value.
+ animator.setDuration(HINT_STATE.transitionDuration);
+ }
+ }
+
+ @Override
+ protected void onMotionPauseChanged(boolean isPaused) {
+ if (mCurrentAnimation == null) {
+ return;
+ }
+ mCurrentAnimation.dispatchOnCancelWithoutCancelRunnable(() -> {
+ mLauncher.getStateManager().goToState(OVERVIEW, true, () -> {
+ mReachedOverview = true;
+ maybeSwipeInteractionToOverviewComplete();
+ });
+ });
+ VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
+ }
+
+ private void maybeSwipeInteractionToOverviewComplete() {
+ if (mReachedOverview && mDetector.isSettlingState()) {
+ onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE);
+ }
+ }
+
+ @Override
+ protected boolean handlingOverviewAnim() {
+ return mDidTouchStartInNavBar && super.handlingOverviewAnim();
+ }
+
+ @Override
+ public boolean onDrag(float yDisplacement, float xDisplacement, MotionEvent event) {
+ if (mMotionPauseDetector.isPaused()) {
+ if (!mReachedOverview) {
+ mStartDisplacement.set(xDisplacement, yDisplacement);
+ } else {
+ mRecentsView.setTranslationX((xDisplacement - mStartDisplacement.x)
+ * OVERVIEW_MOVEMENT_FACTOR);
+ mRecentsView.setTranslationY((yDisplacement - mStartDisplacement.y)
+ * OVERVIEW_MOVEMENT_FACTOR);
+ }
+ // Stay in Overview.
+ return true;
+ }
+ return super.onDrag(yDisplacement, xDisplacement, event);
+ }
+
+ @Override
+ protected void goToOverviewOnDragEnd(float velocity) {
+ float velocityDp = dpiFromPx(velocity);
+ boolean isFling = Math.abs(velocityDp) > 1;
+ LauncherStateManager stateManager = mLauncher.getStateManager();
+ if (isFling) {
+ // When flinging, go back to home instead of overview.
+ if (velocity > 0) {
+ stateManager.goToState(NORMAL, true,
+ () -> onSwipeInteractionCompleted(NORMAL, Touch.FLING));
+ } else {
+ StaggeredWorkspaceAnim staggeredWorkspaceAnim = new StaggeredWorkspaceAnim(
+ mLauncher, velocity, false /* animateOverviewScrim */);
+ staggeredWorkspaceAnim.start();
+
+ // StaggeredWorkspaceAnim doesn't animate overview, so we handle it here.
+ stateManager.cancelAnimation();
+ AnimatorSetBuilder builder = new AnimatorSetBuilder();
+ long duration = OVERVIEW.transitionDuration;
+ AnimatorSet anim = stateManager.createAtomicAnimation(
+ stateManager.getState(), NORMAL, builder,
+ ATOMIC_OVERVIEW_PEEK_COMPONENT, duration);
+ anim.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ onSwipeInteractionCompleted(NORMAL, Touch.SWIPE);
+ }
+ });
+ anim.start();
+ }
+ } else {
+ if (mReachedOverview) {
+ float distanceDp = dpiFromPx(Math.max(
+ Math.abs(mRecentsView.getTranslationX()),
+ Math.abs(mRecentsView.getTranslationY())));
+ long duration = (long) Math.max(TRANSLATION_ANIM_MIN_DURATION_MS,
+ distanceDp / TRANSLATION_ANIM_VELOCITY_DP_PER_MS);
+ mRecentsView.animate()
+ .translationX(0)
+ .translationY(0)
+ .setInterpolator(ACCEL_DEACCEL)
+ .setDuration(duration)
+ .withEndAction(this::maybeSwipeInteractionToOverviewComplete);
+ }
+ }
+ }
+
+ private float dpiFromPx(float pixels) {
+ return Utilities.dpiFromPx(pixels, mLauncher.getResources().getDisplayMetrics());
+ }
+}
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
index 4e08df9..8628db0 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/touchcontrollers/NoButtonQuickSwitchTouchController.java
@@ -62,6 +62,7 @@
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.graphics.OverviewScrim;
import com.android.launcher3.touch.BaseSwipeDetector;
import com.android.launcher3.touch.BothAxesSwipeDetector;
@@ -181,6 +182,12 @@
@Override
public void onMotionPauseChanged(boolean isPaused) {
+ VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
+
+ if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
+ return;
+ }
+
ShelfAnimState shelfState = isPaused ? PEEK : HIDE;
if (shelfState == PEEK) {
// Some shelf elements (e.g. qsb) were hidden, but we need them visible when peeking.
@@ -197,7 +204,6 @@
}
mShelfPeekAnim.setShelfState(shelfState, ShelfPeekAnim.INTERPOLATOR,
ShelfPeekAnim.DURATION);
- VibratorWrapper.INSTANCE.get(mLauncher).vibrate(OVERVIEW_HAPTIC);
}
private void setupAnimators() {
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 92c55da..3e6def3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -111,6 +111,11 @@
@Override
protected boolean isLauncherInitialized() {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
+ "isLauncherInitialized.TouchInteractionService.isInitialized=" +
+ TouchInteractionService.isInitialized());
+ }
return super.isLauncherInitialized() && TouchInteractionService.isInitialized();
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 71568b3..edaef30 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -61,6 +61,7 @@
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.provider.RestoreDbTask;
+import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.TraceHelper;
@@ -135,12 +136,16 @@
TouchInteractionService.this.initInputMonitor();
preloadOverview(true /* fromInit */);
});
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "TIS initialized");
+ }
sIsInitialized = true;
}
@BinderThread
@Override
public void onOverviewToggle() {
+ TestLogging.recordEvent("onOverviewToggle");
mOverviewCommandHelper.onOverviewToggle();
}
@@ -392,6 +397,9 @@
@Override
public void onDestroy() {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "TIS destroyed");
+ }
sIsInitialized = false;
if (mDeviceState.isUserUnlocked()) {
mInputConsumer.unregisterInputConsumer();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
index 41be683..217eca5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/ShelfPeekAnim.java
@@ -25,6 +25,7 @@
import android.view.animation.Interpolator;
import com.android.launcher3.Launcher;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.uioverrides.states.OverviewState;
/**
@@ -48,7 +49,7 @@
* Animates to the given state, canceling the previous animation if it was still running.
*/
public void setShelfState(ShelfAnimState shelfState, Interpolator interpolator, long duration) {
- if (mShelfState == shelfState) {
+ if (mShelfState == shelfState || FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
return;
}
mLauncher.getStateManager().cancelStateElementAnimation(INDEX_SHELF_ANIM);
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index 96ac489..31c1acf 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -55,7 +55,10 @@
RemoteAnimationTargetCompat[] wallpaperTargets, Runnable runnable) {
Runnable r = () -> {
finishExistingAnimation();
- mAnimationResult = new AnimationResult(runnable);
+ mAnimationResult = new AnimationResult(() -> {
+ runnable.run();
+ mAnimationResult = null;
+ });
onCreateAnimation(appTargets, wallpaperTargets, mAnimationResult);
};
if (mStartAtFrontOfQueue) {
diff --git a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
index befeee0..9817e32 100644
--- a/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
+++ b/quickstep/src/com/android/quickstep/QuickstepProcessInitializer.java
@@ -15,6 +15,11 @@
*/
package com.android.quickstep;
+import static android.content.Context.MODE_PRIVATE;
+
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+import static com.android.launcher3.config.FeatureFlags.FLAGS_PREF_NAME;
+
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.UserManager;
@@ -22,6 +27,7 @@
import com.android.launcher3.BuildConfig;
import com.android.launcher3.MainProcessInitializer;
+import com.android.launcher3.config.FeatureFlags;
import com.android.systemui.shared.system.ThreadedRendererCompat;
@SuppressWarnings("unused")
@@ -50,5 +56,21 @@
// Elevate GPU priority for Quickstep and Remote animations.
ThreadedRendererCompat.setContextPriority(ThreadedRendererCompat.EGL_CONTEXT_PRIORITY_HIGH_IMG);
+
+ // Force disable some feature flags based on the system ui navigation mode.
+ SysUINavigationMode.Mode currMode = SysUINavigationMode.INSTANCE.get(context)
+ .addModeChangeListener(mode -> disableFeatureFlagsForSysuiNavMode(context, mode));
+ disableFeatureFlagsForSysuiNavMode(context, currMode);
+ }
+
+ private void disableFeatureFlagsForSysuiNavMode(Context ctx, SysUINavigationMode.Mode mode) {
+ if (mode == SysUINavigationMode.Mode.TWO_BUTTONS) {
+ ctx.getSharedPreferences(FLAGS_PREF_NAME, MODE_PRIVATE)
+ .edit()
+ .putBoolean(ENABLE_OVERVIEW_ACTIONS.key, false)
+ .apply();
+
+ FeatureFlags.initialize(ctx);
+ }
}
}
diff --git a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
index 7885f5c..7ed1e21 100644
--- a/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
+++ b/quickstep/src/com/android/quickstep/views/ShelfScrimView.java
@@ -41,6 +41,8 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.uioverrides.states.OverviewState;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ScrimView;
import com.android.quickstep.SysUINavigationMode;
@@ -155,12 +157,18 @@
mRemainingScreenPathValid = false;
mShiftRange = mLauncher.getAllAppsController().getShiftRange();
+ Context context = getContext();
if ((OVERVIEW.getVisibleElements(mLauncher) & ALL_APPS_HEADER_EXTRA) == 0) {
- mMidProgress = 1;
mDragHandleProgress = 1;
- mMidAlpha = 0;
+ if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
+ // Fade in all apps background quickly to distinguish from swiping from nav bar.
+ mMidAlpha = Themes.getAttrInteger(context, R.attr.allAppsInterimScrimAlpha);
+ mMidProgress = OverviewState.getDefaultVerticalProgress(mLauncher);
+ } else {
+ mMidAlpha = 0;
+ mMidProgress = 1;
+ }
} else {
- Context context = getContext();
mMidAlpha = Themes.getAttrInteger(context, R.attr.allAppsInterimScrimAlpha);
mMidProgress = OVERVIEW.getVerticalProgress(mLauncher);
Rect hotseatPadding = dp.getHotseatLayoutPadding();
diff --git a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
index a7c33a9..3e84a76 100644
--- a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
@@ -68,7 +68,7 @@
private DigitalWellBeingToast getToast() {
executeOnLauncher(launcher -> launcher.getStateManager().goToState(OVERVIEW));
- waitForState("Launcher internal state didn't switch to Overview", OVERVIEW);
+ waitForState("Launcher internal state didn't switch to Overview", () -> OVERVIEW);
final TaskView task = getOnceNotNull("No latest task", launcher -> getLatestTask(launcher));
return getFromLauncher(launcher -> {
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index e34ea4a..c99df10 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -85,7 +85,8 @@
@Ignore // Enable after b/131115533
public void testPressRecentAppsLauncherAndGetOverview() throws RemoteException {
mDevice.pressRecentApps();
- waitForState("Launcher internal state didn't switch to Overview", LauncherState.OVERVIEW);
+ waitForState("Launcher internal state didn't switch to Overview",
+ () -> LauncherState.OVERVIEW);
assertNotNull("getOverview() returned null", mLauncher.getOverview());
}
@@ -96,7 +97,8 @@
public void testWorkspaceSwitchToAllApps() {
assertNotNull("switchToAllApps() returned null",
mLauncher.getWorkspace().switchToAllApps());
- assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
+ assertTrue("Launcher internal state is not All Apps",
+ isInState(() -> LauncherState.ALL_APPS));
}
@Test
@@ -115,7 +117,7 @@
// mLauncher.pressHome() also tests an important case of pressing home while in background.
Overview overview = mLauncher.pressHome().switchToOverview();
assertTrue("Launcher internal state didn't switch to Overview",
- isInState(LauncherState.OVERVIEW));
+ isInState(() -> LauncherState.OVERVIEW));
executeOnLauncher(
launcher -> assertTrue("Don't have at least 3 tasks", getTaskCount(launcher) >= 3));
@@ -124,14 +126,16 @@
0, getCurrentOverviewPage(launcher)));
overview.flingForward();
- assertTrue("Launcher internal state is not Overview", isInState(LauncherState.OVERVIEW));
+ assertTrue("Launcher internal state is not Overview",
+ isInState(() -> LauncherState.OVERVIEW));
final Integer currentTaskAfterFlingForward = getFromLauncher(
launcher -> getCurrentOverviewPage(launcher));
executeOnLauncher(launcher -> assertTrue("Current task in Overview is still 0",
currentTaskAfterFlingForward > 0));
overview.flingBackward();
- assertTrue("Launcher internal state is not Overview", isInState(LauncherState.OVERVIEW));
+ assertTrue("Launcher internal state is not Overview",
+ isInState(() -> LauncherState.OVERVIEW));
executeOnLauncher(launcher -> assertTrue("Flinging back in Overview did nothing",
getCurrentOverviewPage(launcher) < currentTaskAfterFlingForward));
@@ -150,7 +154,7 @@
// Test dismissing a task.
overview = mLauncher.pressHome().switchToOverview();
assertTrue("Launcher internal state didn't switch to Overview",
- isInState(LauncherState.OVERVIEW));
+ isInState(() -> LauncherState.OVERVIEW));
final Integer numTasks = getFromLauncher(launcher -> getTaskCount(launcher));
task = overview.getCurrentTask();
assertNotNull("overview.getCurrentTask() returned null (2)", task);
@@ -165,29 +169,29 @@
final AllAppsFromOverview allApps = overview.switchToAllApps();
assertNotNull("overview.switchToAllApps() returned null (1)", allApps);
assertTrue("Launcher internal state is not All Apps (1)",
- isInState(LauncherState.ALL_APPS));
+ isInState(() -> LauncherState.ALL_APPS));
overview = allApps.switchBackToOverview();
assertNotNull("allApps.switchBackToOverview() returned null", overview);
assertTrue("Launcher internal state didn't switch to Overview",
- isInState(LauncherState.OVERVIEW));
+ isInState(() -> LauncherState.OVERVIEW));
// Test UIDevice.pressBack()
overview.switchToAllApps();
assertNotNull("overview.switchToAllApps() returned null (2)", allApps);
assertTrue("Launcher internal state is not All Apps (2)",
- isInState(LauncherState.ALL_APPS));
+ isInState(() -> LauncherState.ALL_APPS));
mDevice.pressBack();
mLauncher.getOverview();
}
// Test UIDevice.pressHome, once we are in AllApps.
mDevice.pressHome();
- waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL);
+ waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
// Test dismissing all tasks.
mLauncher.getWorkspace().switchToOverview().dismissAllTasks();
- waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL);
+ waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
executeOnLauncher(
launcher -> assertEquals("Still have tasks after dismissing all",
0, getTaskCount(launcher)));
@@ -205,7 +209,8 @@
public void testAppIconLaunchFromAllAppsFromOverview() throws Exception {
final AllApps allApps =
mLauncher.getWorkspace().switchToOverview().switchToAllApps();
- assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
+ assertTrue("Launcher internal state is not All Apps",
+ isInState(() -> LauncherState.ALL_APPS));
TaplTestsLauncher3.runIconLaunchFromAllAppsTest(this, allApps);
}
@@ -217,7 +222,7 @@
assertNotNull("Workspace.switchToOverview() returned null",
mLauncher.pressHome().switchToOverview());
assertTrue("Launcher internal state didn't switch to Overview",
- isInState(LauncherState.OVERVIEW));
+ isInState(() -> LauncherState.OVERVIEW));
}
@Test
@@ -229,7 +234,7 @@
assertNotNull("Background.switchToOverview() returned null", background.switchToOverview());
assertTrue("Launcher internal state didn't switch to Overview",
- isInState(LauncherState.OVERVIEW));
+ isInState(() -> LauncherState.OVERVIEW));
}
private Background getAndAssertBackground() {
@@ -252,9 +257,11 @@
TaplTestsLauncher3.runAllAppsTest(this, mLauncher.getAllApps());
// Testing pressHome.
- assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
+ assertTrue("Launcher internal state is not All Apps",
+ isInState(() -> LauncherState.ALL_APPS));
assertNotNull("pressHome returned null", mLauncher.pressHome());
- assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL));
+ assertTrue("Launcher internal state is not Home",
+ isInState(() -> LauncherState.NORMAL));
assertNotNull("getHome returned null", mLauncher.getWorkspace());
}
diff --git a/res/drawable/bg_all_apps_button.xml b/res/drawable/bg_all_apps_button.xml
new file mode 100644
index 0000000..169a468
--- /dev/null
+++ b/res/drawable/bg_all_apps_button.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2020, 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.
+*/
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="oval" >
+ <solid android:color="#ffffffff" />
+ </shape>
+ </item>
+
+ <item>
+ <shape android:shape="oval" >
+ <solid android:color="?android:attr/colorAccent" />
+ </shape>
+ </item>
+</ripple>
diff --git a/res/drawable/ic_apps.xml b/res/drawable/ic_apps.xml
new file mode 100644
index 0000000..db779c2
--- /dev/null
+++ b/res/drawable/ic_apps.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<vector android:height="24dp" android:tint="#FFFFFF"
+ android:viewportHeight="24.0" android:viewportWidth="24.0"
+ android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#FF000000" android:pathData="M4,8h4L8,4L4,4v4zM10,20h4v-4h-4v4zM4,20h4v-4L4,16v4zM4,14h4v-4L4,10v4zM10,14h4v-4h-4v4zM16,4v4h4L20,4h-4zM10,8h4L14,4h-4v4zM16,14h4v-4h-4v4zM16,20h4v-4h-4v4z"/>
+</vector>
diff --git a/quickstep/res/drawable/ic_pin.xml b/res/drawable/ic_pin.xml
similarity index 100%
rename from quickstep/res/drawable/ic_pin.xml
rename to res/drawable/ic_pin.xml
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 9427ae0..0da51d9 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -16,7 +16,7 @@
<!-- The top and bottom paddings are defined in this container, but since we want
the list view to span the full width (for touch interception purposes), we
will bake the left/right padding into that view's background itself. -->
-<com.android.launcher3.allapps.AllAppsContainerView
+<com.android.launcher3.allapps.LauncherAllAppsContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/apps_view"
android:layout_width="match_parent"
@@ -79,4 +79,4 @@
layout="@layout/search_container_all_apps"/>
<include layout="@layout/all_apps_fast_scroller" />
-</com.android.launcher3.allapps.AllAppsContainerView>
\ No newline at end of file
+</com.android.launcher3.allapps.LauncherAllAppsContainerView>
\ No newline at end of file
diff --git a/res/layout/secondary_launcher.xml b/res/layout/secondary_launcher.xml
new file mode 100644
index 0000000..98cfc34
--- /dev/null
+++ b/res/layout/secondary_launcher.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+<com.android.launcher3.secondarydisplay.SecondaryDragLayer
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/drag_layer" >
+
+ <GridView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginBottom="100dp"
+ android:theme="@style/HomeScreenElementTheme"
+ android:layout_gravity="center_horizontal|top"
+ android:layout_margin="@dimen/dynamic_grid_edge_margin"
+ android:id="@+id/workspace_grid" />
+
+ <ImageButton
+ android:id="@+id/all_apps_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|end"
+ android:layout_margin="40dp"
+ android:padding="16dp"
+ android:src="@drawable/ic_apps"
+ android:background="@drawable/bg_all_apps_button"
+ android:contentDescription="@string/all_apps_button_label"
+ android:onClick="onAppsButtonClicked" />
+
+ <com.android.launcher3.allapps.AllAppsContainerView
+ android:id="@+id/apps_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="true"
+ android:clipToPadding="false"
+ android:focusable="false"
+ android:saveEnabled="false"
+ android:layout_gravity="bottom|end"
+ android:background="@drawable/round_rect_primary"
+ android:elevation="2dp"
+ android:visibility="invisible" >
+
+ <include
+ layout="@layout/all_apps_rv_layout"
+ android:visibility="gone" />
+
+ <com.android.launcher3.allapps.FloatingHeaderView
+ android:id="@+id/all_apps_header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/search_container_all_apps"
+ android:clipToPadding="false"
+ android:paddingTop="@dimen/all_apps_header_top_padding"
+ android:orientation="vertical" >
+
+ <com.android.launcher3.allapps.PersonalWorkSlidingTabStrip
+ android:id="@+id/tabs"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/all_apps_header_tab_height"
+ android:layout_marginLeft="@dimen/all_apps_tabs_side_padding"
+ android:layout_marginRight="@dimen/all_apps_tabs_side_padding"
+ android:orientation="horizontal"
+ style="@style/TextHeadline">
+
+ <Button
+ android:id="@+id/tab_personal"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="?android:attr/selectableItemBackground"
+ android:text="@string/all_apps_personal_tab"
+ android:textAllCaps="true"
+ android:textColor="@color/all_apps_tab_text"
+ android:textSize="14sp" />
+
+ <Button
+ android:id="@+id/tab_work"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="?android:attr/selectableItemBackground"
+ android:text="@string/all_apps_work_tab"
+ android:textAllCaps="true"
+ android:textColor="@color/all_apps_tab_text"
+ android:textSize="14sp" />
+ </com.android.launcher3.allapps.PersonalWorkSlidingTabStrip>
+ </com.android.launcher3.allapps.FloatingHeaderView>
+
+ <com.android.launcher3.allapps.search.AppsSearchContainerLayout
+ android:id="@id/search_container_all_apps"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/all_apps_search_bar_field_height"
+ android:layout_centerHorizontal="true"
+ android:layout_gravity="top|center_horizontal"
+ android:background="@drawable/bg_all_apps_searchbox"
+ android:elevation="1dp"
+ android:focusableInTouchMode="true"
+ android:gravity="center"
+ android:hint="@string/all_apps_search_bar_hint"
+ android:imeOptions="actionSearch|flagNoExtractUi"
+ android:inputType="text|textNoSuggestions|textCapWords"
+ android:maxLines="1"
+ android:padding="8dp"
+ android:saveEnabled="false"
+ android:scrollHorizontally="true"
+ android:singleLine="true"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textColorHint="@drawable/all_apps_search_hint"
+ android:textSize="16sp"
+ android:translationY="24dp" />
+
+ <include layout="@layout/all_apps_fast_scroller" />
+ </com.android.launcher3.allapps.AllAppsContainerView>
+</com.android.launcher3.secondarydisplay.SecondaryDragLayer>
\ No newline at end of file
diff --git a/res/values/config.xml b/res/values/config.xml
index 0dfed97..0945642 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -63,6 +63,7 @@
<!-- Various classes overriden by projects/build flavors. -->
<string name="app_filter_class" translatable="false"></string>
<string name="user_event_dispatcher_class" translatable="false"></string>
+ <string name="folder_name_provider_class" translatable="false"></string>
<string name="stats_log_manager_class" translatable="false"></string>
<string name="app_transition_manager_class" translatable="false"></string>
<string name="instant_app_resolver_class" translatable="false"></string>
diff --git a/src/com/android/launcher3/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 0283eac..dda38b3 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -29,6 +29,7 @@
import android.util.Log;
import android.view.ActionMode;
import android.view.View;
+import android.view.View.OnClickListener;
import android.widget.Toast;
import androidx.annotation.Nullable;
@@ -36,6 +37,7 @@
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.uioverrides.DisplayRotationListener;
import com.android.launcher3.uioverrides.WallpaperColorInfo;
import com.android.launcher3.util.PackageManagerHelper;
@@ -262,5 +264,9 @@
}
}
+ public OnClickListener getItemOnClickListener() {
+ return ItemClickHandler.INSTANCE;
+ }
+
protected abstract void reapplyUi();
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index bc6fa6e..0d71da4 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -245,7 +245,7 @@
allAppsIconTextSizePx = originalProfile.iconTextSizePx;
allAppsCellHeightPx = originalProfile.allAppsCellHeightPx;
allAppsIconDrawablePaddingPx = originalProfile.iconDrawablePaddingOriginalPx;
- allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx;
+ allAppsCellWidthPx = allAppsIconSizePx + 2 * allAppsIconDrawablePaddingPx;
}
updateWorkspacePadding();
@@ -360,7 +360,7 @@
allAppsIconTextSizePx = iconTextSizePx;
allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
allAppsCellHeightPx = getCellSize().y;
- allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx;
+ allAppsCellWidthPx = allAppsIconSizePx + 2 * allAppsIconDrawablePaddingPx;
if (isVerticalBarLayout()) {
// Always hide the Workspace text with vertical bar layout.
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index 763432d..a32fd12 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -16,11 +16,14 @@
package com.android.launcher3;
+import android.content.Context;
import android.graphics.Rect;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.folder.FolderNameProvider;
/**
* Interface defining an object that can receive a drag.
@@ -67,7 +70,12 @@
public DragViewStateAnnouncer stateAnnouncer;
- public DragObject() {
+ public FolderNameProvider folderNameProvider;
+
+ public DragObject(Context context) {
+ if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
+ folderNameProvider = FolderNameProvider.newInstance(context);
+ }
}
/**
diff --git a/src/com/android/launcher3/FastBitmapDrawable.java b/src/com/android/launcher3/FastBitmapDrawable.java
index 5091684..a78159f 100644
--- a/src/com/android/launcher3/FastBitmapDrawable.java
+++ b/src/com/android/launcher3/FastBitmapDrawable.java
@@ -29,36 +29,25 @@
import android.graphics.ColorMatrixColorFilter;
import android.graphics.Paint;
import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
-import android.graphics.PorterDuffColorFilter;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.util.Property;
-import android.util.SparseArray;
import com.android.launcher3.graphics.PlaceHolderIconDrawable;
import com.android.launcher3.icons.BitmapInfo;
+
public class FastBitmapDrawable extends Drawable {
private static final float PRESSED_SCALE = 1.1f;
private static final float DISABLED_DESATURATION = 1f;
private static final float DISABLED_BRIGHTNESS = 0.5f;
+ private static final float DISABLED_ALPHA = 0.54f;
public static final int CLICK_FEEDBACK_DURATION = 200;
- // Since we don't need 256^2 values for combinations of both the brightness and saturation, we
- // reduce the value space to a smaller value V, which reduces the number of cached
- // ColorMatrixColorFilters that we need to keep to V^2
- private static final int REDUCED_FILTER_VALUE_SPACE = 48;
-
- // A cache of ColorFilters for optimizing brightness and saturation animations
- private static final SparseArray<ColorFilter> sCachedFilter = new SparseArray<>();
-
- // Temporary matrices used for calculation
- private static final ColorMatrix sTempBrightnessMatrix = new ColorMatrix();
- private static final ColorMatrix sTempFilterMatrix = new ColorMatrix();
+ private static ColorFilter sDisabledFColorFilter;
protected final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG | Paint.ANTI_ALIAS_FLAG);
protected Bitmap mBitmap;
@@ -84,13 +73,7 @@
private ObjectAnimator mScaleAnimation;
private float mScale = 1;
-
- // The saturation and brightness are values that are mapped to REDUCED_FILTER_VALUE_SPACE and
- // as a result, can be used to compose the key for the cached ColorMatrixColorFilters
- private int mDesaturation = 0;
- private int mBrightness = 0;
private int mAlpha = 255;
- private int mPrevUpdateKey = Integer.MAX_VALUE;
public FastBitmapDrawable(Bitmap b) {
this(b, Color.TRANSPARENT);
@@ -243,15 +226,10 @@
return false;
}
- private void invalidateDesaturationAndBrightness() {
- setDesaturation(mIsDisabled ? DISABLED_DESATURATION : 0);
- setBrightness(mIsDisabled ? DISABLED_BRIGHTNESS : 0);
- }
-
public void setIsDisabled(boolean isDisabled) {
if (mIsDisabled != isDisabled) {
mIsDisabled = isDisabled;
- invalidateDesaturationAndBrightness();
+ updateFilter();
}
}
@@ -259,90 +237,33 @@
return mIsDisabled;
}
- /**
- * Sets the saturation of this icon, 0 [full color] -> 1 [desaturated]
- */
- private void setDesaturation(float desaturation) {
- int newDesaturation = (int) Math.floor(desaturation * REDUCED_FILTER_VALUE_SPACE);
- if (mDesaturation != newDesaturation) {
- mDesaturation = newDesaturation;
- updateFilter();
+ private ColorFilter getDisabledColorFilter() {
+ if (sDisabledFColorFilter == null) {
+ ColorMatrix tempBrightnessMatrix = new ColorMatrix();
+ ColorMatrix tempFilterMatrix = new ColorMatrix();
+
+ tempFilterMatrix.setSaturation(1f - DISABLED_DESATURATION);
+ float scale = 1 - DISABLED_BRIGHTNESS;
+ int brightnessI = (int) (255 * DISABLED_BRIGHTNESS);
+ float[] mat = tempBrightnessMatrix.getArray();
+ mat[0] = scale;
+ mat[6] = scale;
+ mat[12] = scale;
+ mat[4] = brightnessI;
+ mat[9] = brightnessI;
+ mat[14] = brightnessI;
+ mat[18] = DISABLED_ALPHA;
+ tempFilterMatrix.preConcat(tempBrightnessMatrix);
+ sDisabledFColorFilter = new ColorMatrixColorFilter(tempFilterMatrix);
}
- }
-
- public float getDesaturation() {
- return (float) mDesaturation / REDUCED_FILTER_VALUE_SPACE;
- }
-
- /**
- * Sets the brightness of this icon, 0 [no add. brightness] -> 1 [2bright2furious]
- */
- private void setBrightness(float brightness) {
- int newBrightness = (int) Math.floor(brightness * REDUCED_FILTER_VALUE_SPACE);
- if (mBrightness != newBrightness) {
- mBrightness = newBrightness;
- updateFilter();
- }
- }
-
- private float getBrightness() {
- return (float) mBrightness / REDUCED_FILTER_VALUE_SPACE;
+ return sDisabledFColorFilter;
}
/**
* Updates the paint to reflect the current brightness and saturation.
*/
protected void updateFilter() {
- boolean usePorterDuffFilter = false;
- int key = -1;
- if (mDesaturation > 0) {
- key = (mDesaturation << 16) | mBrightness;
- } else if (mBrightness > 0) {
- // Compose a key with a fully saturated icon if we are just animating brightness
- key = (1 << 16) | mBrightness;
-
- // We found that in L, ColorFilters cause drawing artifacts with shadows baked into
- // icons, so just use a PorterDuff filter when we aren't animating saturation
- usePorterDuffFilter = true;
- }
-
- // Debounce multiple updates on the same frame
- if (key == mPrevUpdateKey) {
- return;
- }
- mPrevUpdateKey = key;
-
- if (key != -1) {
- ColorFilter filter = sCachedFilter.get(key);
- if (filter == null) {
- float brightnessF = getBrightness();
- int brightnessI = (int) (255 * brightnessF);
- if (usePorterDuffFilter) {
- filter = new PorterDuffColorFilter(Color.argb(brightnessI, 255, 255, 255),
- PorterDuff.Mode.SRC_ATOP);
- } else {
- float saturationF = 1f - getDesaturation();
- sTempFilterMatrix.setSaturation(saturationF);
- if (mBrightness > 0) {
- // Brightness: C-new = C-old*(1-amount) + amount
- float scale = 1f - brightnessF;
- float[] mat = sTempBrightnessMatrix.getArray();
- mat[0] = scale;
- mat[6] = scale;
- mat[12] = scale;
- mat[4] = brightnessI;
- mat[9] = brightnessI;
- mat[14] = brightnessI;
- sTempFilterMatrix.preConcat(sTempBrightnessMatrix);
- }
- filter = new ColorMatrixColorFilter(sTempFilterMatrix);
- }
- sCachedFilter.append(key, filter);
- }
- mPaint.setColorFilter(filter);
- } else {
- mPaint.setColorFilter(null);
- }
+ mPaint.setColorFilter(mIsDisabled ? getDisabledColorFilter() : null);
invalidateSelf();
}
diff --git a/src/com/android/launcher3/FolderInfo.java b/src/com/android/launcher3/FolderInfo.java
index 0fea0dc..787eee1 100644
--- a/src/com/android/launcher3/FolderInfo.java
+++ b/src/com/android/launcher3/FolderInfo.java
@@ -16,6 +16,7 @@
package com.android.launcher3;
+import android.content.Intent;
import android.os.Process;
import com.android.launcher3.model.ModelWriter;
@@ -49,6 +50,8 @@
public int options;
+ public Intent suggestedFolderNames;
+
/**
* The apps and shortcuts
*/
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 9d87152..a807e4f 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -18,8 +18,8 @@
import static com.android.launcher3.Utilities.getDevicePrefs;
import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.settings.SettingsActivity.GRID_OPTIONS_PREFERENCE_KEY;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
import android.annotation.TargetApi;
@@ -41,6 +41,7 @@
import android.util.SparseArray;
import android.util.TypedValue;
import android.util.Xml;
+import android.view.Display;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@@ -48,6 +49,7 @@
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.util.ConfigMonitor;
import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DefaultDisplay.Info;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Themes;
@@ -172,6 +174,13 @@
}
/**
+ * This constructor should NOT have any monitors by design.
+ */
+ public InvariantDeviceProfile(Context context, Display display) {
+ initGrid(context, null, new Info(display));
+ }
+
+ /**
* Retrieve system defined or RRO overriden icon shape.
*/
private static String getIconShapePath(Context context) {
@@ -183,8 +192,10 @@
}
private String initGrid(Context context, String gridName) {
- DefaultDisplay.Info displayInfo = DefaultDisplay.INSTANCE.get(context).getInfo();
+ return initGrid(context, gridName, DefaultDisplay.INSTANCE.get(context).getInfo());
+ }
+ private String initGrid(Context context, String gridName, DefaultDisplay.Info displayInfo) {
Point smallestSize = new Point(displayInfo.smallestSize);
Point largestSize = new Point(displayInfo.largestSize);
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 06f3453..445ebc0 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -103,7 +103,6 @@
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.folder.FolderGridOrganizer;
import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.folder.FolderNameProvider;
import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.keyboard.CustomActionsPopup;
@@ -372,7 +371,7 @@
mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null);
setupViews();
- mPopupDataProvider = new PopupDataProvider(this);
+ mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
mAppTransitionManager = LauncherAppTransitionManager.newInstance(this);
mAppTransitionManager.registerRemoteAnimations();
@@ -616,10 +615,6 @@
return mStateManager;
}
- public FolderNameProvider getFolderNameProvider() {
- return new FolderNameProvider();
- }
-
@Override
public <T extends View> T findViewById(int id) {
return mLauncherView.findViewById(id);
@@ -1180,6 +1175,11 @@
mDropTargetBar.setup(mDragController);
mAllAppsController.setupViews(mAppsView);
+
+ if (FeatureFlags.ENABLE_OVERVIEW_ACTIONS.get()) {
+ // Overview is above all other launcher elements, including qsb, so move it to the top.
+ mOverviewPanel.bringToFront();
+ }
}
/**
@@ -1254,13 +1254,13 @@
cellXY[1] = cellY;
foundCellSpan = true;
+ DragObject dragObject = new DragObject(getApplicationContext());
+ dragObject.dragInfo = info;
// If appropriate, either create a folder or add to an existing folder
if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0,
- true, null)) {
+ true, dragObject)) {
return;
}
- DragObject dragObject = new DragObject();
- dragObject.dragInfo = info;
if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject,
true)) {
return;
@@ -1344,7 +1344,7 @@
}
};
- public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
+ private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
mWorkspace.updateNotificationDots(updatedDots);
mAppsView.getAppsStore().updateNotificationDots(updatedDots);
}
@@ -1807,7 +1807,6 @@
// Note: There should be at most one log per method call. This is enforced implicitly
// by using if-else statements.
- UserEventDispatcher ued = getUserEventDispatcher();
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
if (topView != null && topView.onBackPressed()) {
// Handled by the floating view.
@@ -1875,6 +1874,7 @@
}
}
+ @Override
public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
@Nullable String sourceContainer) {
if (!hasBeenResumed()) {
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 74362ed..cdfd257 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -16,6 +16,8 @@
package com.android.launcher3;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+
import android.graphics.drawable.Drawable;
import android.util.FloatProperty;
import android.util.Property;
@@ -28,9 +30,10 @@
* easier access from static classes and enums
*/
public static final int ALL_APPS_TRANSITION_MS = 320;
- public static final int OVERVIEW_TRANSITION_MS = 250;
+ public static final int OVERVIEW_TRANSITION_MS = ENABLE_OVERVIEW_ACTIONS.get() ? 380 : 250;
public static final int SPRING_LOADED_TRANSITION_MS = 150;
public static final int SPRING_LOADED_EXIT_DELAY = 500;
+ public static final int HINT_TRANSITION_MS = 80;
// The progress of an animation to all apps must be at least this far along to snap to all apps.
public static final float MIN_PROGRESS_TO_ALL_APPS = 0.5f;
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index cf978b5..04a7ecd 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -56,6 +56,7 @@
import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LooperExecutor;
@@ -96,6 +97,10 @@
private boolean mModelLoaded;
public boolean isModelLoaded() {
synchronized (mLock) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
+ "isModelLoaded: " + mModelLoaded + ", " + mLoaderTask);
+ }
return mModelLoaded && mLoaderTask == null;
}
}
@@ -368,6 +373,9 @@
public boolean stopLoader() {
synchronized (mLock) {
LoaderTask oldTask = mLoaderTask;
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "LauncherModel.stopLoader");
+ }
mLoaderTask = null;
if (oldTask != null) {
oldTask.stopLocked();
@@ -381,6 +389,10 @@
synchronized (mLock) {
stopLoader();
mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, mBgDataModel, results);
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
+ "LauncherModel.startLoaderForResults " + mLoaderTask);
+ }
// Always post the loader task, instead of running directly (even on same thread) so
// that we exit any nested synchronized blocks
@@ -482,6 +494,10 @@
public void close() {
synchronized (mLock) {
// If we are still the last one to be scheduled, remove ourselves.
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
+ "LauncherModel.close " + mLoaderTask + ", " + mTask);
+ }
if (mLoaderTask == mTask) {
mLoaderTask = null;
}
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index d2b447b..8b80cba 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -30,9 +30,11 @@
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL_1_7;
import static com.android.launcher3.anim.Interpolators.clampToProgress;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
import static com.android.launcher3.testing.TestProtocol.ALL_APPS_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.BACKGROUND_APP_STATE_ORDINAL;
+import static com.android.launcher3.testing.TestProtocol.HINT_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.NORMAL_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.OVERVIEW_PEEK_STATE_ORDINAL;
import static com.android.launcher3.testing.TestProtocol.OVERVIEW_STATE_ORDINAL;
@@ -42,6 +44,7 @@
import android.view.animation.Interpolator;
import com.android.launcher3.anim.AnimatorSetBuilder;
+import com.android.launcher3.states.HintState;
import com.android.launcher3.states.SpringLoadedState;
import com.android.launcher3.uioverrides.states.AllAppsState;
import com.android.launcher3.uioverrides.states.OverviewState;
@@ -88,7 +91,7 @@
}
};
- private static final LauncherState[] sAllStates = new LauncherState[7];
+ private static final LauncherState[] sAllStates = new LauncherState[8];
/**
* TODO: Create a separate class for NORMAL state.
@@ -104,6 +107,7 @@
public static final LauncherState SPRING_LOADED = new SpringLoadedState(
SPRING_LOADED_STATE_ORDINAL);
public static final LauncherState ALL_APPS = new AllAppsState(ALL_APPS_STATE_ORDINAL);
+ public static final LauncherState HINT_STATE = new HintState(HINT_STATE_ORDINAL);
public static final LauncherState OVERVIEW = new OverviewState(OVERVIEW_STATE_ORDINAL);
public static final LauncherState OVERVIEW_PEEK =
@@ -212,6 +216,10 @@
return launcher.getOverviewScaleAndTranslationForNormalState();
}
+ public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
+ return new ScaleAndTranslation(1, 0, 0);
+ }
+
public float getOverviewFullscreenProgress() {
return 0;
}
@@ -319,6 +327,10 @@
if (!isHotseatVisible) {
hotseat.setScaleX(0.92f);
hotseat.setScaleY(0.92f);
+ if (ENABLE_OVERVIEW_ACTIONS.get()) {
+ launcher.getAppsView().setScaleX(0.92f);
+ launcher.getAppsView().setScaleY(0.92f);
+ }
}
} else if (this == NORMAL && fromState == OVERVIEW_PEEK) {
// Keep fully visible until the very end (when overview is offscreen) to make invisible.
diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java
index 6c0d95e..7f443b6 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -381,7 +381,7 @@
return res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
}
- public static float dpiFromPx(int size, DisplayMetrics metrics){
+ public static float dpiFromPx(float size, DisplayMetrics metrics) {
float densityRatio = (float) metrics.densityDpi / DisplayMetrics.DENSITY_DEFAULT;
return (size / densityRatio);
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index abbf59d..beaafda 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1673,7 +1673,7 @@
}
boolean createUserFolderIfNecessary(View newView, int container, CellLayout target,
- int[] targetCell, float distance, boolean external, DragView dragView) {
+ int[] targetCell, float distance, boolean external, DragObject d) {
if (distance > mMaxDistanceForFolderCreation) return false;
View v = target.getChildAt(targetCell[0], targetCell[1]);
@@ -1711,14 +1711,13 @@
sourceInfo.cellY = -1;
// If the dragView is null, we can't animate
- boolean animate = dragView != null;
+ boolean animate = d != null;
if (animate) {
// In order to keep everything continuous, we hand off the currently rendered
// folder background to the newly created icon. This preserves animation state.
fi.setFolderBackground(mFolderCreateBg);
mFolderCreateBg = new PreviewBackground();
- fi.performCreateAnimation(destInfo, v, sourceInfo, dragView, folderLocation, scale
- );
+ fi.performCreateAnimation(destInfo, v, sourceInfo, d, folderLocation, scale);
} else {
fi.prepareCreateAnimation(v);
fi.addItem(destInfo);
@@ -1799,8 +1798,8 @@
// If the item being dropped is a shortcut and the nearest drop
// cell also contains a shortcut, then create a folder with the two shortcuts.
if (createUserFolderIfNecessary(cell, container,
- dropTargetLayout, mTargetCell, distance, false, d.dragView) ||
- addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
+ dropTargetLayout, mTargetCell, distance, false, d)
+ || addToExistingFolderIfNecessary(cell, dropTargetLayout, mTargetCell,
distance, d, false)) {
mLauncher.getStateManager().goToState(NORMAL, SPRING_LOADED_EXIT_DELAY);
return;
@@ -2561,7 +2560,7 @@
float distance = cellLayout.getDistanceFromCell(mDragViewVisualCenter[0],
mDragViewVisualCenter[1], mTargetCell);
if (createUserFolderIfNecessary(view, container, cellLayout, mTargetCell, distance,
- true, d.dragView)) {
+ true, d)) {
return;
}
if (addToExistingFolderIfNecessary(view, cellLayout, mTargetCell, distance, d,
@@ -3255,7 +3254,8 @@
return mOverlayShown;
}
- void moveToDefaultScreen() {
+ /** Calls {@link #snapToPage(int)} on the {@link #DEFAULT_PAGE}, then requests focus on it. */
+ public void moveToDefaultScreen() {
int page = DEFAULT_PAGE;
if (!workspaceInModalState() && getNextPage() != page) {
snapToPage(page);
diff --git a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
index 7a7e1fe..c33392d 100644
--- a/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
+++ b/src/com/android/launcher3/WorkspaceStateTransitionAnimation.java
@@ -36,6 +36,7 @@
import com.android.launcher3.LauncherState.PageAlphaProvider;
import com.android.launcher3.LauncherState.ScaleAndTranslation;
import com.android.launcher3.LauncherStateManager.AnimationConfig;
+import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.graphics.WorkspaceAndHotseatScrim;
@@ -77,6 +78,7 @@
ScaleAndTranslation scaleAndTranslation = state.getWorkspaceScaleAndTranslation(mLauncher);
ScaleAndTranslation hotseatScaleAndTranslation = state.getHotseatScaleAndTranslation(
mLauncher);
+ ScaleAndTranslation qsbScaleAndTranslation = state.getQsbScaleAndTranslation(mLauncher);
mNewScale = scaleAndTranslation.scale;
PageAlphaProvider pageAlphaProvider = state.getWorkspacePageAlphaProvider(mLauncher);
final int childCount = mWorkspace.getChildCount();
@@ -90,24 +92,24 @@
pageAlphaProvider.interpolator);
boolean playAtomicComponent = config.playAtomicOverviewScaleComponent();
Hotseat hotseat = mWorkspace.getHotseat();
+ // Since we set the pivot relative to mWorkspace, we need to scale a sibling of Workspace.
+ AllAppsContainerView qsbScaleView = mLauncher.getAppsView();
+ View qsbView = qsbScaleView.getSearchView();
if (playAtomicComponent) {
Interpolator scaleInterpolator = builder.getInterpolator(ANIM_WORKSPACE_SCALE, ZOOM_OUT);
propertySetter.setFloat(mWorkspace, SCALE_PROPERTY, mNewScale, scaleInterpolator);
if (!hotseat.getRotationMode().isTransposed) {
- // Set the hotseat's pivot point to match the workspace's, so that it scales
- // together. Since both hotseat and workspace can move, transform the point
- // manually instead of using dragLayer.getDescendantCoordRelativeToSelf and
- // related methods.
- hotseat.setPivotY(mWorkspace.getPivotY() + mWorkspace.getTop() - hotseat.getTop());
- hotseat.setPivotX(mWorkspace.getPivotX()
- + mWorkspace.getLeft() - hotseat.getLeft());
+ setPivotToScaleWithWorkspace(hotseat);
+ setPivotToScaleWithWorkspace(qsbScaleView);
}
float hotseatScale = hotseatScaleAndTranslation.scale;
Interpolator hotseatScaleInterpolator = builder.getInterpolator(ANIM_HOTSEAT_SCALE,
scaleInterpolator);
propertySetter.setFloat(hotseat, SCALE_PROPERTY, hotseatScale,
hotseatScaleInterpolator);
+ propertySetter.setFloat(qsbScaleView, SCALE_PROPERTY, qsbScaleAndTranslation.scale,
+ hotseatScaleInterpolator);
float hotseatIconsAlpha = (elements & HOTSEAT_ICONS) != 0 ? 1 : 0;
propertySetter.setViewAlpha(hotseat, hotseatIconsAlpha, fadeInterpolator);
@@ -134,10 +136,24 @@
hotseatScaleAndTranslation.translationY, hotseatTranslationInterpolator);
propertySetter.setFloat(mWorkspace.getPageIndicator(), View.TRANSLATION_Y,
hotseatScaleAndTranslation.translationY, hotseatTranslationInterpolator);
+ propertySetter.setFloat(qsbView, View.TRANSLATION_Y,
+ qsbScaleAndTranslation.translationY, hotseatTranslationInterpolator);
setScrim(propertySetter, state);
}
+ /**
+ * Set the given view's pivot point to match the workspace's, so that it scales together. Since
+ * both this view and workspace can move, transform the point manually instead of using
+ * dragLayer.getDescendantCoordRelativeToSelf and related methods.
+ */
+ private void setPivotToScaleWithWorkspace(View sibling) {
+ sibling.setPivotY(mWorkspace.getPivotY() + mWorkspace.getTop()
+ - sibling.getTop() - sibling.getTranslationY());
+ sibling.setPivotX(mWorkspace.getPivotX() + mWorkspace.getLeft()
+ - sibling.getLeft() - sibling.getTranslationX());
+ }
+
public void setScrim(PropertySetter propertySetter, LauncherState state) {
WorkspaceAndHotseatScrim scrim = mLauncher.getDragLayer().getScrim();
propertySetter.setFloat(scrim, SCRIM_PROGRESS, state.getWorkspaceScrimAlpha(mLauncher),
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 27668eb..6f15c9b 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -40,6 +40,7 @@
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.AppInfo;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.DragSource;
@@ -47,9 +48,6 @@
import com.android.launcher3.Insettable;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
-import com.android.launcher3.LauncherStateManager;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.keyboard.FocusedItemDecorator;
@@ -61,7 +59,6 @@
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.RecyclerViewFastScroller;
import com.android.launcher3.views.SpringRelativeLayout;
-import com.android.launcher3.views.WorkEduView;
/**
* The all apps view container.
@@ -74,8 +71,8 @@
private static final float FLING_ANIMATION_THRESHOLD = 0.55f;
private static final int ALPHA_CHANNEL_COUNT = 2;
- private final Launcher mLauncher;
- private final AdapterHolder[] mAH;
+ protected final BaseDraggingActivity mLauncher;
+ protected final AdapterHolder[] mAH;
private final ItemInfoMatcher mPersonalMatcher = ItemInfoMatcher.ofUser(Process.myUserHandle());
private final ItemInfoMatcher mWorkMatcher = ItemInfoMatcher.not(mPersonalMatcher);
private final AllAppsStore mAllAppsStore = new AllAppsStore();
@@ -83,18 +80,16 @@
private final Paint mNavBarScrimPaint;
private int mNavBarScrimHeight = 0;
- private SearchUiManager mSearchUiManager;
+ protected SearchUiManager mSearchUiManager;
private View mSearchContainer;
private AllAppsPagedView mViewPager;
private FloatingHeaderView mHeader;
private SpannableStringBuilder mSearchQueryBuilder = null;
- private boolean mUsingTabs;
+ protected boolean mUsingTabs;
private boolean mSearchModeWhileUsingTabs = false;
- private LauncherStateManager.StateListener mWorkTabListener;
-
private RecyclerViewFastScroller mTouchHandler;
private final Point mFastScrollerOffset = new Point();
@@ -111,7 +106,7 @@
public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- mLauncher = Launcher.getLauncher(context);
+ mLauncher = BaseDraggingActivity.fromContext(context);
mLauncher.addOnDeviceProfileChangeListener(this);
mSearchQueryBuilder = new SpannableStringBuilder();
@@ -133,6 +128,15 @@
mMultiValueAlpha = new MultiValueAlpha(this, ALPHA_CHANNEL_COUNT);
}
+ /**
+ * Sets the long click listener for icons
+ */
+ public void setOnIconLongClickListener(OnLongClickListener listener) {
+ for (AdapterHolder holder : mAH) {
+ holder.adapter.setOnIconLongClickListener(listener);
+ }
+ }
+
public AllAppsStore getAppsStore() {
return mAllAppsStore;
}
@@ -193,11 +197,6 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
-
- // The AllAppsContainerView houses the QSB and is hence visible from the Workspace
- // Overview states. We shouldn't intercept for the scrubber in these cases.
- if (!mLauncher.isInState(LauncherState.ALL_APPS)) return false;
-
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
AllAppsRecyclerView rv = getActiveRecyclerView();
if (rv != null &&
@@ -309,7 +308,6 @@
+ grid.cellLayoutPaddingLeftRightPx;
for (int i = 0; i < mAH.length; i++) {
- mAH[i].adapter.setAppsPerRow(grid.inv.numAllAppsColumns);
mAH[i].padding.bottom = insets.bottom;
mAH[i].padding.left = mAH[i].padding.right = leftRightPadding;
mAH[i].applyPadding();
@@ -327,8 +325,6 @@
setLayoutParams(mlp);
InsettableFrameLayout.dispatchInsets(this, insets);
- mLauncher.getAllAppsController()
- .setScrollRangeDelta(mSearchUiManager.getScrollRangeDelta(insets));
}
@Override
@@ -375,7 +371,6 @@
mAH[AdapterHolder.MAIN].setup(mViewPager.getChildAt(0), mPersonalMatcher);
mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher);
onTabChanged(mViewPager.getNextPage());
- mWorkTabListener = WorkEduView.showEduFlowIfNeeded(mLauncher, mWorkTabListener);
} else {
mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null);
mAH[AdapterHolder.WORK].recyclerView = null;
@@ -421,9 +416,6 @@
.setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.MAIN));
findViewById(R.id.tab_work)
.setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK));
- if (pos == AdapterHolder.WORK) {
- WorkEduView.showWorkEduIfNeeded(mLauncher);
- }
}
}
@@ -588,7 +580,7 @@
appsList.updateItemFilter(matcher);
recyclerView = (AllAppsRecyclerView) rv;
recyclerView.setEdgeEffectFactory(createEdgeEffectFactory());
- recyclerView.setApps(appsList, mUsingTabs);
+ recyclerView.setApps(appsList);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
recyclerView.setHasFixedSize(true);
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 80ea1eb..442b77b 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -15,17 +15,22 @@
*/
package com.android.launcher3.allapps;
+import static com.android.launcher3.touch.ItemLongClickListener.INSTANCE_ALL_APPS;
+
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
+import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.TextView;
+import androidx.annotation.Nullable;
import androidx.core.view.accessibility.AccessibilityEventCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityRecordCompat;
@@ -33,14 +38,12 @@
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.AppInfo;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.touch.ItemClickHandler;
-import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.PackageManagerHelper;
import java.util.List;
@@ -174,23 +177,26 @@
}
}
- private final Launcher mLauncher;
+ private final BaseDraggingActivity mLauncher;
private final LayoutInflater mLayoutInflater;
private final AlphabeticalAppsList mApps;
private final GridLayoutManager mGridLayoutMgr;
private final GridSpanSizer mGridSizer;
+ private final OnClickListener mOnIconClickListener;
+ private OnLongClickListener mOnIconLongClickListener = INSTANCE_ALL_APPS;
+
private int mAppsPerRow;
private BindViewCallback mBindViewCallback;
private OnFocusChangeListener mIconFocusListener;
// The text to show when there are no search results and no market search handler.
- private String mEmptySearchMessage;
+ protected String mEmptySearchMessage;
// The intent to send off to the market app, updated each time the search query changes.
private Intent mMarketSearchIntent;
- public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps) {
+ public AllAppsGridAdapter(BaseDraggingActivity launcher, AlphabeticalAppsList apps) {
Resources res = launcher.getResources();
mLauncher = launcher;
mApps = apps;
@@ -200,6 +206,8 @@
mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
mLayoutInflater = LayoutInflater.from(launcher);
+ mOnIconClickListener = launcher.getItemOnClickListener();
+
setAppsPerRow(mLauncher.getDeviceProfile().inv.numAllAppsColumns);
}
@@ -208,6 +216,13 @@
mGridLayoutMgr.setSpanCount(mAppsPerRow);
}
+ /**
+ * Sets the long click listener for icons
+ */
+ public void setOnIconLongClickListener(@Nullable OnLongClickListener listener) {
+ mOnIconLongClickListener = listener;
+ }
+
public static boolean isDividerViewType(int viewType) {
return isViewType(viewType, VIEW_TYPE_MASK_DIVIDER);
}
@@ -254,8 +269,8 @@
case VIEW_TYPE_ICON:
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
R.layout.all_apps_icon, parent, false);
- icon.setOnClickListener(ItemClickHandler.INSTANCE);
- icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_ALL_APPS);
+ icon.setOnClickListener(mOnIconClickListener);
+ icon.setOnLongClickListener(mOnIconLongClickListener);
icon.setLongPressTimeoutFactor(1f);
icon.setOnFocusChangeListener(mIconFocusListener);
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index f82e380..b6744cf 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -28,14 +28,13 @@
import androidx.recyclerview.widget.RecyclerView;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.BaseRecyclerView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsGridAdapter.AppsGridLayoutManager;
-import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -84,7 +83,7 @@
/**
* Sets the list of apps in this view, used to determine the fastscroll position.
*/
- public void setApps(AlphabeticalAppsList apps, boolean usingTabs) {
+ public void setApps(AlphabeticalAppsList apps) {
mApps = apps;
mFastScrollHelper = new AllAppsFastScrollHelper(this, apps);
}
@@ -94,7 +93,7 @@
}
private void updatePoolSize() {
- DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
+ DeviceProfile grid = BaseDraggingActivity.fromContext(getContext()).getDeviceProfile();
RecyclerView.RecycledViewPool pool = getRecycledViewPool();
int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH, 1);
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 10e2821..a33fe4d 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -21,7 +21,7 @@
import android.content.pm.PackageManager;
import com.android.launcher3.AppInfo;
-import com.android.launcher3.Launcher;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ItemInfoMatcher;
@@ -126,7 +126,7 @@
}
}
- private final Launcher mLauncher;
+ private final BaseDraggingActivity mLauncher;
// The set of apps from the system
private final List<AppInfo> mApps = new ArrayList<>();
@@ -151,7 +151,7 @@
public AlphabeticalAppsList(Context context, AllAppsStore appsStore, boolean isWork) {
mAllAppsStore = appsStore;
- mLauncher = Launcher.getLauncher(context);
+ mLauncher = BaseDraggingActivity.fromContext(context);
mAppNameComparator = new AppInfoComparator(context);
mIsWork = isWork;
mNumAppsPerRow = mLauncher.getDeviceProfile().inv.numColumns;
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 42a0eee..cc33af9 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -31,9 +31,9 @@
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
@@ -393,7 +393,7 @@
@Override
public void setInsets(Rect insets) {
- DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
+ DeviceProfile grid = BaseDraggingActivity.fromContext(getContext()).getDeviceProfile();
for (FloatingHeaderRow row : mAllRows) {
row.setInsets(insets, grid);
}
diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
new file mode 100644
index 0000000..9d0ecd3
--- /dev/null
+++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
@@ -0,0 +1,77 @@
+/*
+ * 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.launcher3.allapps;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.views.WorkEduView;
+
+/**
+ * AllAppsContainerView with launcher specific callbacks
+ */
+public class LauncherAllAppsContainerView extends AllAppsContainerView {
+
+ private final Launcher mLauncher;
+
+ private LauncherStateManager.StateListener mWorkTabListener;
+
+ public LauncherAllAppsContainerView(Context context) {
+ this(context, null);
+ }
+
+ public LauncherAllAppsContainerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public LauncherAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mLauncher = Launcher.getLauncher(context);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // The AllAppsContainerView houses the QSB and is hence visible from the Workspace
+ // Overview states. We shouldn't intercept for the scrubber in these cases.
+ if (!mLauncher.isInState(LauncherState.ALL_APPS)) return false;
+
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ super.setInsets(insets);
+ mLauncher.getAllAppsController()
+ .setScrollRangeDelta(mSearchUiManager.getScrollRangeDelta(insets));
+ }
+
+ @Override
+ public void onTabChanged(int pos) {
+ super.onTabChanged(pos);
+ if (mUsingTabs) {
+ if (pos == AdapterHolder.WORK) {
+ WorkEduView.showWorkEduIfNeeded(mLauncher);
+ } else {
+ mWorkTabListener = WorkEduView.showEduFlowIfNeeded(mLauncher, mWorkTabListener);
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
index decdcc0..6204f31 100644
--- a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
+++ b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
@@ -24,15 +24,14 @@
import android.widget.Button;
import android.widget.LinearLayout;
-import com.android.launcher3.Launcher;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.pageindicators.PageIndicator;
import com.android.launcher3.util.Themes;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
/**
* Supports two indicator colors, dedicated for personal and work tabs.
*/
@@ -73,7 +72,7 @@
mDividerPaint.setStrokeWidth(
getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height));
- mSharedPreferences = Launcher.getLauncher(getContext()).getSharedPrefs();
+ mSharedPreferences = Utilities.getPrefs(context);
mIsRtl = Utilities.isRtl(getResources());
}
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 4515dde..ed45749 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -25,8 +25,8 @@
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ExtendedEditText;
-import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.util.ComponentKey;
@@ -41,7 +41,7 @@
implements TextWatcher, OnEditorActionListener, ExtendedEditText.OnBackKeyListener,
OnFocusChangeListener {
- protected Launcher mLauncher;
+ protected BaseDraggingActivity mLauncher;
protected Callbacks mCb;
protected ExtendedEditText mInput;
protected String mQuery;
@@ -56,7 +56,7 @@
*/
public final void initialize(
SearchAlgorithm searchAlgorithm, ExtendedEditText input,
- Launcher launcher, Callbacks cb) {
+ BaseDraggingActivity launcher, Callbacks cb) {
mCb = cb;
mLauncher = launcher;
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index 31fcc8c..d497c3a 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -26,8 +26,6 @@
import android.content.Context;
import android.graphics.Rect;
import android.text.Selection;
-import android.text.Spannable;
-import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.method.TextKeyListener;
import android.util.AttributeSet;
@@ -36,18 +34,16 @@
import android.view.ViewGroup.MarginLayoutParams;
import android.view.animation.Interpolator;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AlphabeticalAppsList;
import com.android.launcher3.allapps.SearchUiManager;
import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.graphics.TintedDrawableSpan;
import com.android.launcher3.util.ComponentKey;
import java.util.ArrayList;
@@ -59,8 +55,7 @@
implements SearchUiManager, AllAppsSearchBarController.Callbacks,
AllAppsStore.OnUpdateListener, Insettable {
-
- private final Launcher mLauncher;
+ private final BaseDraggingActivity mLauncher;
private final AllAppsSearchBarController mSearchBarController;
private final SpannableStringBuilder mSearchQueryBuilder;
@@ -82,7 +77,7 @@
public AppsSearchContainerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- mLauncher = Launcher.getLauncher(context);
+ mLauncher = BaseDraggingActivity.fromContext(context);
mSearchBarController = new AllAppsSearchBarController();
mSearchQueryBuilder = new SpannableStringBuilder();
@@ -97,13 +92,13 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- mLauncher.getAppsView().getAppsStore().addUpdateListener(this);
+ mAppsView.getAppsStore().addUpdateListener(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mLauncher.getAppsView().getAppsStore().removeUpdateListener(this);
+ mAppsView.getAppsStore().removeUpdateListener(this);
}
@Override
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 4a52795..1c277ab 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -26,6 +26,7 @@
import android.animation.ValueAnimator;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
@@ -250,14 +251,24 @@
}
}
+ /** @see #dispatchOnCancelWithoutCancelRunnable(Runnable) */
+ public void dispatchOnCancelWithoutCancelRunnable() {
+ dispatchOnCancelWithoutCancelRunnable(null);
+ }
+
/**
* Sets mOnCancelRunnable = null before dispatching the cancel and restoring the runnable. This
* is intended to be used only if you need to cancel but want to defer cleaning up yourself.
+ * @param callback An optional callback to run after dispatching the cancel but before resetting
+ * the onCancelRunnable.
*/
- public void dispatchOnCancelWithoutCancelRunnable() {
+ public void dispatchOnCancelWithoutCancelRunnable(@Nullable Runnable callback) {
Runnable onCancel = mOnCancelRunnable;
setOnCancelRunnable(null);
dispatchOnCancel();
+ if (callback != null) {
+ callback.run();
+ }
setOnCancelRunnable(onCancel);
}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 8ccb369..b1a2c33 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -117,7 +117,8 @@
"Show launcher preview in grid picker");
public static final BooleanFlag ENABLE_OVERVIEW_ACTIONS = getDebugFlag(
- "ENABLE_OVERVIEW_ACTIONS", false, "Show app actions in Overview");
+ "ENABLE_OVERVIEW_ACTIONS", false, "Show app actions instead of the shelf in Overview."
+ + " As part of this decoupling, also distinguish swipe up from nav bar vs above it.");
public static final BooleanFlag ENABLE_DATABASE_RESTORE = getDebugFlag(
"ENABLE_DATABASE_RESTORE", true,
diff --git a/src/com/android/launcher3/dragndrop/DragController.java b/src/com/android/launcher3/dragndrop/DragController.java
index 8adec27..54a44ee 100644
--- a/src/com/android/launcher3/dragndrop/DragController.java
+++ b/src/com/android/launcher3/dragndrop/DragController.java
@@ -28,7 +28,6 @@
import android.graphics.Point;
import android.graphics.Rect;
import android.os.IBinder;
-import android.util.Log;
import android.view.DragEvent;
import android.view.HapticFeedbackConstants;
import android.view.KeyEvent;
@@ -43,7 +42,6 @@
import com.android.launcher3.R;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
-import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.Thunk;
import com.android.launcher3.util.TouchController;
@@ -174,7 +172,7 @@
mLastDropTarget = null;
- mDragObject = new DropTarget.DragObject();
+ mDragObject = new DropTarget.DragObject(mLauncher.getApplicationContext());
mIsInPreDrag = mOptions.preDragCondition != null
&& !mOptions.preDragCondition.shouldStartDrag(0);
diff --git a/src/com/android/launcher3/dragndrop/DragLayer.java b/src/com/android/launcher3/dragndrop/DragLayer.java
index 8823bde..92f35e2 100644
--- a/src/com/android/launcher3/dragndrop/DragLayer.java
+++ b/src/com/android/launcher3/dragndrop/DragLayer.java
@@ -536,6 +536,9 @@
mOverviewScrim.updateCurrentScrimmedView(this);
mFocusIndicatorHelper.draw(canvas);
super.dispatchDraw(canvas);
+ if (mOverviewScrim.getScrimmedView() == null) {
+ mOverviewScrim.draw(canvas);
+ }
}
@Override
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 90d8125..024c7dd 100644
--- a/src/com/android/launcher3/folder/Folder.java
+++ b/src/com/android/launcher3/folder/Folder.java
@@ -26,6 +26,7 @@
import android.annotation.SuppressLint;
import android.appwidget.AppWidgetHostView;
import android.content.Context;
+import android.content.Intent;
import android.graphics.Canvas;
import android.graphics.Path;
import android.graphics.Rect;
@@ -300,9 +301,8 @@
post(() -> {
if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
if (TextUtils.isEmpty(mFolderName.getText())) {
- final String[] suggestedNames = new String[FolderNameProvider.SUGGEST_MAX];
- mLauncher.getFolderNameProvider().getSuggestedFolderName(getContext(),
- mInfo.contents, suggestedNames);
+ String[] suggestedNames =
+ mInfo.suggestedFolderNames.getStringArrayExtra("suggest");
mFolderName.setText(suggestedNames[0]);
mFolderName.displayCompletions(Arrays.asList(suggestedNames).subList(1,
suggestedNames.length));
@@ -446,17 +446,19 @@
* Show suggested folder title.
*/
public void showSuggestedTitle(String[] suggestName) {
- if (FeatureFlags.FOLDER_NAME_SUGGEST.get()
- && TextUtils.isEmpty(mFolderName.getText().toString())
- && !mInfo.hasOption(FolderInfo.FLAG_MANUAL_FOLDER_NAME)) {
- if (suggestName.length > 0 && !TextUtils.isEmpty(suggestName[0])) {
- mFolderName.setHint("");
- mFolderName.setText(suggestName[0]);
- mInfo.title = suggestName[0];
- animateOpen(mInfo.contents, 0, true);
- mFolderName.showKeyboard();
- mFolderName.displayCompletions(
- Arrays.asList(suggestName).subList(1, suggestName.length));
+ if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
+ mInfo.suggestedFolderNames = new Intent().putExtra("suggest", suggestName);
+ if (TextUtils.isEmpty(mFolderName.getText().toString())
+ && !mInfo.hasOption(FolderInfo.FLAG_MANUAL_FOLDER_NAME)) {
+ if (suggestName.length > 0 && !TextUtils.isEmpty(suggestName[0])) {
+ mFolderName.setHint("");
+ mFolderName.setText(suggestName[0]);
+ mInfo.title = suggestName[0];
+ animateOpen(mInfo.contents, 0, true);
+ mFolderName.showKeyboard();
+ mFolderName.displayCompletions(
+ Arrays.asList(suggestName).subList(1, suggestName.length));
+ }
}
}
}
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index 8c56823..6a47b98 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -288,8 +288,9 @@
}
public void performCreateAnimation(final WorkspaceItemInfo destInfo, final View destView,
- final WorkspaceItemInfo srcInfo, final DragView srcView, Rect dstRect,
+ final WorkspaceItemInfo srcInfo, final DragObject d, Rect dstRect,
float scaleRelativeToDragLayer) {
+ final DragView srcView = d.dragView;
prepareCreateAnimation(destView);
addItem(destInfo);
// This will animate the first item from it's position as an icon into its
@@ -298,7 +299,7 @@
.start();
// This will animate the dragView (srcView) into the new folder
- onDrop(srcInfo, srcView, dstRect, scaleRelativeToDragLayer, 1,
+ onDrop(srcInfo, d, dstRect, scaleRelativeToDragLayer, 1,
false /* itemReturnedOnFailedDrop */);
}
@@ -313,11 +314,11 @@
mOpenAlarm.cancelAlarm();
}
- private void onDrop(final WorkspaceItemInfo item, DragView animateView, Rect finalRect,
+ private void onDrop(final WorkspaceItemInfo item, DragObject d, Rect finalRect,
float scaleRelativeToDragLayer, int index, boolean itemReturnedOnFailedDrop) {
item.cellX = -1;
item.cellY = -1;
-
+ DragView animateView = d.dragView;
// Typically, the animateView corresponds to the DragView; however, if this is being done
// after a configuration activity (ie. for a Shortcut being dragged from AllApps) we
// will not have a view to animate
@@ -395,7 +396,7 @@
String[] suggestedNameOut = new String[FolderNameProvider.SUGGEST_MAX];
if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
Executors.UI_HELPER_EXECUTOR.post(() -> {
- launcher.getFolderNameProvider().getSuggestedFolderName(
+ d.folderNameProvider.getSuggestedFolderName(
getContext(), mInfo.contents, suggestedNameOut);
showFinalView(finalIndex, item, suggestedNameOut);
});
@@ -429,9 +430,10 @@
item = (WorkspaceItemInfo) d.dragInfo;
}
mFolder.notifyDrop();
- onDrop(item, d.dragView, null, 1.0f,
+ onDrop(item, d, null, 1.0f,
itemReturnedOnFailedDrop ? item.rank : mInfo.contents.size(),
- itemReturnedOnFailedDrop);
+ itemReturnedOnFailedDrop
+ );
}
public void setDotInfo(FolderDotInfo dotInfo) {
diff --git a/src/com/android/launcher3/folder/FolderNameEditText.java b/src/com/android/launcher3/folder/FolderNameEditText.java
index 7e3f847..7e11b18 100644
--- a/src/com/android/launcher3/folder/FolderNameEditText.java
+++ b/src/com/android/launcher3/folder/FolderNameEditText.java
@@ -61,6 +61,9 @@
return connectionWrapper;
}
+ /**
+ * Send strings in @param suggestList to the IME to show up as suggestions.
+ */
public void displayCompletions(List<String> suggestList) {
int cnt = Math.min(suggestList.size(), FolderNameProvider.SUGGEST_MAX);
CompletionInfo[] cInfo = new CompletionInfo[cnt];
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index 9ea292c..957e636 100644
--- a/src/com/android/launcher3/folder/FolderNameProvider.java
+++ b/src/com/android/launcher3/folder/FolderNameProvider.java
@@ -24,6 +24,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.util.ResourceBasedOverride;
import java.util.ArrayList;
import java.util.Arrays;
@@ -38,10 +39,10 @@
/**
* Locates provider for the folder name.
*/
-public class FolderNameProvider {
+public class FolderNameProvider implements ResourceBasedOverride {
private static final String TAG = "FolderNameProvider";
- private static final boolean DEBUG = false;
+ private static final boolean DEBUG = true;
/**
* IME usually has up to 3 suggest slots. In total, there are 4 suggest slots as the folder
@@ -50,9 +51,14 @@
public static final int SUGGEST_MAX = 4;
/**
- * When inheriting class requires precaching, override this method.
+ * Retrieve instance of this object that can be overridden in runtime based on the build
+ * variant of the application.
*/
- public void load(Context context) {}
+ public static FolderNameProvider newInstance(Context context) {
+ FolderNameProvider fnp = Overrides.getObject(FolderNameProvider.class,
+ context.getApplicationContext(), R.string.folder_name_provider_class);
+ return fnp;
+ }
public CharSequence getSuggestedFolderName(Context context,
ArrayList<WorkspaceItemInfo> workspaceItemInfos, CharSequence[] candidates) {
diff --git a/src/com/android/launcher3/graphics/OverviewScrim.java b/src/com/android/launcher3/graphics/OverviewScrim.java
index d707403..94acbfd 100644
--- a/src/com/android/launcher3/graphics/OverviewScrim.java
+++ b/src/com/android/launcher3/graphics/OverviewScrim.java
@@ -55,16 +55,17 @@
mCurrentScrimmedView = mStableScrimmedView;
int currentIndex = root.indexOfChild(mCurrentScrimmedView);
final int childCount = root.getChildCount();
- while (mCurrentScrimmedView.getVisibility() != VISIBLE && currentIndex < childCount) {
+ while (mCurrentScrimmedView != null && mCurrentScrimmedView.getVisibility() != VISIBLE
+ && currentIndex < childCount) {
currentIndex++;
mCurrentScrimmedView = root.getChildAt(currentIndex);
}
}
/**
- * @return The view to draw the scrim behind.
+ * @return The view to draw the scrim behind, or null if all visible views should be scrimmed.
*/
- public View getScrimmedView() {
+ public @Nullable View getScrimmedView() {
return mCurrentScrimmedView;
}
}
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 571d41a..23ec459 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -60,6 +60,7 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderGridOrganizer;
+import com.android.launcher3.folder.FolderNameProvider;
import com.android.launcher3.icons.ComponentWithLabel;
import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
import com.android.launcher3.icons.IconCache;
@@ -76,6 +77,7 @@
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IOUtils;
import com.android.launcher3.util.LooperIdleLock;
@@ -168,15 +170,32 @@
}
public void run() {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
+ "LoaderTask1 " + this);
+ }
synchronized (this) {
// Skip fast if we are already stopped.
if (mStopped) {
return;
}
}
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
+ "LoaderTask2 " + this);
+ }
Object traceToken = TraceHelper.INSTANCE.beginSection(TAG);
- TimingLogger logger = new TimingLogger(TAG, "run");
+ TimingLogger logger = TestProtocol.sDebugTracing ?
+ new TimingLogger(TAG, "run") {
+ @Override
+ public void addSplit(String splitLabel) {
+ super.addSplit(splitLabel);
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
+ "LoaderTask.addSplit " + splitLabel);
+ }
+ }
+ : new TimingLogger(TAG, "run");
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
loadWorkspace(allShortcuts);
@@ -256,10 +275,19 @@
mApp.getContext(), true), mApp.getModel()::onWidgetLabelsUpdated);
logger.addSplit("save widgets in icon cache");
+ // fifth step
+ if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
+ loadFolderNames();
+ }
+
verifyNotStopped();
updateHandler.finish();
logger.addSplit("finish icon update");
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
+ "LoaderTask3 " + this);
+ }
transaction.commit();
} catch (CancellationException e) {
// Loader stopped, ignore
@@ -898,6 +926,21 @@
return allShortcuts;
}
+ private void loadFolderNames() {
+ FolderNameProvider provider = FolderNameProvider.newInstance(mApp.getContext());
+
+ synchronized (mBgDataModel) {
+ for (int i = 0; i < mBgDataModel.folders.size(); i++) {
+ String[] suggestedOut = new String[FolderNameProvider.SUGGEST_MAX];
+ FolderInfo info = mBgDataModel.folders.valueAt(i);
+ if (info.suggestedFolderNames == null) {
+ provider.getSuggestedFolderName(mApp.getContext(), info.contents, suggestedOut);
+ info.suggestedFolderNames = new Intent().putExtra("suggest", suggestedOut);
+ }
+ }
+ }
+ }
+
public static boolean isValidProvider(AppWidgetProviderInfo provider) {
return (provider != null) && (provider.provider != null)
&& (provider.provider.getPackageName() != null);
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 98f7fd8..d9bd714 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -40,8 +40,8 @@
import android.widget.FrameLayout;
import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.InsettableFrameLayout;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -57,14 +57,16 @@
/**
* A container for shortcuts to deep links and notifications associated with an app.
+ *
+ * @param <T> The activity on with the popup shows
*/
-public abstract class ArrowPopup extends AbstractFloatingView {
+public abstract class ArrowPopup<T extends BaseDraggingActivity> extends AbstractFloatingView {
private final Rect mTempRect = new Rect();
protected final LayoutInflater mInflater;
private final float mOutlineRadius;
- protected final Launcher mLauncher;
+ protected final T mLauncher;
protected final boolean mIsRtl;
private final int mArrowOffset;
@@ -83,7 +85,7 @@
super(context, attrs, defStyleAttr);
mInflater = LayoutInflater.from(context);
mOutlineRadius = Themes.getDialogCornerRadius(context);
- mLauncher = Launcher.getLauncher(context);
+ mLauncher = BaseDraggingActivity.fromContext(context);
mIsRtl = Utilities.isRtl(getResources());
setClipToOutline(true);
@@ -120,16 +122,22 @@
}
}
- public <T extends View> T inflateAndAdd(int resId, ViewGroup container) {
+ /**
+ * Utility method for inflating and adding a view
+ */
+ public <R extends View> R inflateAndAdd(int resId, ViewGroup container) {
View view = mInflater.inflate(resId, container, false);
container.addView(view);
- return (T) view;
+ return (R) view;
}
- public <T extends View> T inflateAndAdd(int resId, ViewGroup container, int index) {
+ /**
+ * Utility method for inflating and adding a view
+ */
+ public <R extends View> R inflateAndAdd(int resId, ViewGroup container, int index) {
View view = mInflater.inflate(resId, container, false);
container.addView(view, index);
- return (T) view;
+ return (R) view;
}
/**
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index b764a07..05ea694 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -44,6 +44,7 @@
import android.widget.ImageView;
import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
@@ -65,7 +66,6 @@
import com.android.launcher3.popup.PopupDataProvider.PopupDataChangeListener;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
-import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.ShortcutUtil;
@@ -74,22 +74,22 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* A container for shortcuts to deep links and notifications associated with an app.
+ *
+ * @param <T> The activity on with the popup shows
*/
-public class PopupContainerWithArrow extends ArrowPopup implements DragSource,
- DragController.DragListener, View.OnLongClickListener,
- View.OnTouchListener, PopupDataChangeListener {
+public class PopupContainerWithArrow<T extends BaseDraggingActivity> extends ArrowPopup<T>
+ implements DragSource, DragController.DragListener {
private final List<DeepShortcutView> mShortcuts = new ArrayList<>();
private final PointF mInterceptTouchDown = new PointF();
- protected final Point mIconLastTouchPos = new Point();
private final int mStartDragThreshold;
- private final LauncherAccessibilityDelegate mAccessibilityDelegate;
private BubbleTextView mOriginalIcon;
private NotificationItemView mNotificationItemView;
@@ -97,11 +97,13 @@
private ViewGroup mSystemShortcutContainer;
+ protected PopupItemDragHandler mPopupItemDragHandler;
+ protected LauncherAccessibilityDelegate mAccessibilityDelegate;
+
public PopupContainerWithArrow(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mStartDragThreshold = getResources().getDimensionPixelSize(
R.dimen.deep_shortcuts_start_drag_threshold);
- mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher);
}
public PopupContainerWithArrow(Context context, AttributeSet attrs) {
@@ -117,18 +119,6 @@
}
@Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mLauncher.getPopupDataProvider().setChangeListener(this);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mLauncher.getPopupDataProvider().setChangeListener(null);
- }
-
- @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mInterceptTouchDown.set(ev.getX(), ev.getY());
@@ -168,11 +158,15 @@
public OnClickListener getItemClickListener() {
return (view) -> {
- ItemClickHandler.INSTANCE.onClick(view);
+ mLauncher.getItemOnClickListener().onClick(view);
close(true);
};
}
+ public PopupItemDragHandler getItemDragHandler() {
+ return mPopupItemDragHandler;
+ }
+
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -201,18 +195,35 @@
icon.clearFocus();
return null;
}
- ItemInfo itemInfo = (ItemInfo) icon.getTag();
- if (!ShortcutUtil.supportsShortcuts(itemInfo)) {
+ ItemInfo item = (ItemInfo) icon.getTag();
+ if (!ShortcutUtil.supportsShortcuts(item)) {
return null;
}
final PopupContainerWithArrow container =
(PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
R.layout.popup_container, launcher.getDragLayer(), false);
- container.populateAndShow(icon, itemInfo);
+ container.configureForLauncher(launcher);
+
+ PopupDataProvider popupDataProvider = launcher.getPopupDataProvider();
+ container.populateAndShow(icon,
+ popupDataProvider.getShortcutCountForItem(item),
+ popupDataProvider.getNotificationKeysForItem(item),
+ launcher.getSupportedShortcuts()
+ .map(s -> s.getShortcut(launcher, item))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()));
+ launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item));
return container;
}
+ private void configureForLauncher(Launcher launcher) {
+ addOnAttachStateChangeListener(new LiveUpdateHandler(launcher));
+ mPopupItemDragHandler = new LauncherPopupItemDragHandler(launcher, this);
+ mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(launcher);
+ launcher.getDragController().addDragListener(this);
+ }
+
@Override
protected void onInflationComplete(boolean isReversed) {
if (isReversed && mNotificationItemView != null) {
@@ -234,23 +245,8 @@
}
}
- protected void populateAndShow(BubbleTextView icon, ItemInfo item) {
- PopupDataProvider popupDataProvider = mLauncher.getPopupDataProvider();
- populateAndShow(icon,
- popupDataProvider.getShortcutCountForItem(item),
- popupDataProvider.getNotificationKeysForItem(item),
- mLauncher.getSupportedShortcuts()
- .map(s -> s.getShortcut(mLauncher, item))
- .filter(s -> s != null)
- .collect(Collectors.toList()));
- }
-
- public ViewGroup getSystemShortcutContainerForTesting() {
- return mSystemShortcutContainer;
- }
-
@TargetApi(Build.VERSION_CODES.P)
- protected void populateAndShow(final BubbleTextView originalIcon, int shortcutCount,
+ public void populateAndShow(final BubbleTextView originalIcon, int shortcutCount,
final List<NotificationKeyData> notificationKeys, List<SystemShortcut> systemShortcuts) {
mNumNotifications = notificationKeys.size();
mOriginalIcon = originalIcon;
@@ -316,7 +312,6 @@
setAccessibilityPaneTitle(getTitleForAccessibility());
}
- mLauncher.getDragController().addDragListener(this);
mOriginalIcon.setForceHideDot(true);
// All views are added. Animate layout from now on.
@@ -390,44 +385,6 @@
}
}
- @Override
- public void onWidgetsBound() {
- ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
- SystemShortcut widgetInfo = SystemShortcut.WIDGETS.getShortcut(mLauncher, itemInfo);
- View widgetsView = null;
- int count = mSystemShortcutContainer.getChildCount();
- for (int i = 0; i < count; i++) {
- View systemShortcutView = mSystemShortcutContainer.getChildAt(i);
- if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) {
- widgetsView = systemShortcutView;
- break;
- }
- }
-
- if (widgetInfo != null && widgetsView == null) {
- // We didn't have any widgets cached but now there are some, so enable the shortcut.
- if (mSystemShortcutContainer != this) {
- initializeSystemShortcut(
- R.layout.system_shortcut_icon_only, mSystemShortcutContainer, widgetInfo);
- } else {
- // If using the expanded system shortcut (as opposed to just the icon), we need to
- // reopen the container to ensure measurements etc. all work out. While this could
- // be quite janky, in practice the user would typically see a small flicker as the
- // animation restarts partway through, and this is a very rare edge case anyway.
- close(false);
- PopupContainerWithArrow.showForIcon(mOriginalIcon);
- }
- } else if (widgetInfo == null && widgetsView != null) {
- // No widgets exist, but we previously added the shortcut so remove it.
- if (mSystemShortcutContainer != this) {
- mSystemShortcutContainer.removeView(widgetsView);
- } else {
- close(false);
- PopupContainerWithArrow.showForIcon(mOriginalIcon);
- }
- }
- }
-
private void initializeSystemShortcut(int resId, ViewGroup container, SystemShortcut info) {
View view = inflateAndAdd(
resId, container, getInsertIndexForSystemShortcut(container, info));
@@ -498,18 +455,6 @@
};
}
- /**
- * Updates the notification header if the original icon's dot updated.
- */
- @Override
- public void onNotificationDotsUpdated(Predicate<PackageUserKey> updatedDots) {
- ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
- PackageUserKey packageUser = PackageUserKey.fromItemInfo(itemInfo);
- if (updatedDots.test(packageUser)) {
- updateNotificationHeader();
- }
- }
-
private void updateNotificationHeader() {
ItemInfoWithIcon itemInfo = (ItemInfoWithIcon) mOriginalIcon.getTag();
DotInfo dotInfo = mLauncher.getDotInfoForItem(itemInfo);
@@ -520,25 +465,6 @@
}
@Override
- public void trimNotifications(Map<PackageUserKey, DotInfo> updatedDots) {
- if (mNotificationItemView == null) {
- return;
- }
- ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag();
- DotInfo dotInfo = updatedDots.get(PackageUserKey.fromItemInfo(originalInfo));
- if (dotInfo == null || dotInfo.getNotificationKeys().size() == 0) {
- // No more notifications, remove the notification views and expand all shortcuts.
- mNotificationItemView.removeAllViews();
- mNotificationItemView = null;
- updateHiddenShortcuts();
- updateDividers();
- } else {
- mNotificationItemView.trimNotifications(
- NotificationKeyData.extractKeysOnly(dotInfo.getNotificationKeys()));
- }
- }
-
- @Override
public void onDropCompleted(View target, DragObject d, boolean success) { }
@Override
@@ -592,47 +518,164 @@
super.closeComplete();
}
- @Override
- public boolean onTouch(View v, MotionEvent ev) {
- // Touched a shortcut, update where it was touched so we can drag from there on long click.
- switch (ev.getAction()) {
- case MotionEvent.ACTION_DOWN:
- case MotionEvent.ACTION_MOVE:
- mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
- break;
- }
- return false;
- }
-
- @Override
- public boolean onLongClick(View v) {
- if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
- // Return early if not the correct view
- if (!(v.getParent() instanceof DeepShortcutView)) return false;
-
- // Long clicked on a shortcut.
- DeepShortcutView sv = (DeepShortcutView) v.getParent();
- sv.setWillDrawIcon(false);
-
- // Move the icon to align with the center-top of the touch point
- Point iconShift = new Point();
- iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
- iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
-
- DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
- this, sv.getFinalInfo(),
- new ShortcutDragPreviewProvider(sv.getIconView(), iconShift), new DragOptions());
- dv.animateShift(-iconShift.x, -iconShift.y);
-
- // TODO: support dragging from within folder without having to close it
- AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
- return false;
- }
-
/**
* Returns a PopupContainerWithArrow which is already open or null
*/
- public static PopupContainerWithArrow getOpen(Launcher launcher) {
+ public static PopupContainerWithArrow getOpen(BaseDraggingActivity launcher) {
return getOpenView(launcher, TYPE_ACTION_POPUP);
}
+
+ /**
+ * Utility class to handle updates while the popup is visible (like widgets and
+ * notification changes)
+ */
+ private class LiveUpdateHandler implements
+ PopupDataChangeListener, View.OnAttachStateChangeListener {
+
+ private final Launcher mLauncher;
+
+ LiveUpdateHandler(Launcher launcher) {
+ mLauncher = launcher;
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ mLauncher.getPopupDataProvider().setChangeListener(this);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View view) {
+ mLauncher.getPopupDataProvider().setChangeListener(null);
+ }
+
+ @Override
+ public void onWidgetsBound() {
+ ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
+ SystemShortcut widgetInfo = SystemShortcut.WIDGETS.getShortcut(mLauncher, itemInfo);
+ View widgetsView = null;
+ int count = mSystemShortcutContainer.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View systemShortcutView = mSystemShortcutContainer.getChildAt(i);
+ if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) {
+ widgetsView = systemShortcutView;
+ break;
+ }
+ }
+
+ if (widgetInfo != null && widgetsView == null) {
+ // We didn't have any widgets cached but now there are some, so enable the shortcut.
+ if (mSystemShortcutContainer != PopupContainerWithArrow.this) {
+ initializeSystemShortcut(R.layout.system_shortcut_icon_only,
+ mSystemShortcutContainer, widgetInfo);
+ } else {
+ // If using the expanded system shortcut (as opposed to just the icon), we need
+ // to reopen the container to ensure measurements etc. all work out. While this
+ // could be quite janky, in practice the user would typically see a small
+ // flicker as the animation restarts partway through, and this is a very rare
+ // edge case anyway.
+ close(false);
+ PopupContainerWithArrow.showForIcon(mOriginalIcon);
+ }
+ } else if (widgetInfo == null && widgetsView != null) {
+ // No widgets exist, but we previously added the shortcut so remove it.
+ if (mSystemShortcutContainer != PopupContainerWithArrow.this) {
+ mSystemShortcutContainer.removeView(widgetsView);
+ } else {
+ close(false);
+ PopupContainerWithArrow.showForIcon(mOriginalIcon);
+ }
+ }
+ }
+
+ /**
+ * Updates the notification header if the original icon's dot updated.
+ */
+ @Override
+ public void onNotificationDotsUpdated(Predicate<PackageUserKey> updatedDots) {
+ ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
+ PackageUserKey packageUser = PackageUserKey.fromItemInfo(itemInfo);
+ if (updatedDots.test(packageUser)) {
+ updateNotificationHeader();
+ }
+ }
+
+
+ @Override
+ public void trimNotifications(Map<PackageUserKey, DotInfo> updatedDots) {
+ if (mNotificationItemView == null) {
+ return;
+ }
+ ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag();
+ DotInfo dotInfo = updatedDots.get(PackageUserKey.fromItemInfo(originalInfo));
+ if (dotInfo == null || dotInfo.getNotificationKeys().size() == 0) {
+ // No more notifications, remove the notification views and expand all shortcuts.
+ mNotificationItemView.removeAllViews();
+ mNotificationItemView = null;
+ updateHiddenShortcuts();
+ updateDividers();
+ } else {
+ mNotificationItemView.trimNotifications(
+ NotificationKeyData.extractKeysOnly(dotInfo.getNotificationKeys()));
+ }
+ }
+ }
+
+ /**
+ * Handler to control drag-and-drop for popup items
+ */
+ public interface PopupItemDragHandler extends OnLongClickListener, OnTouchListener { }
+
+ /**
+ * Drag and drop handler for popup items in Launcher activity
+ */
+ public static class LauncherPopupItemDragHandler implements PopupItemDragHandler {
+
+ protected final Point mIconLastTouchPos = new Point();
+ private final Launcher mLauncher;
+ private final PopupContainerWithArrow mContainer;
+
+ LauncherPopupItemDragHandler(Launcher launcher, PopupContainerWithArrow container) {
+ mLauncher = launcher;
+ mContainer = container;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent ev) {
+ // Touched a shortcut, update where it was touched so we can drag from there on
+ // long click.
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
+ // Return early if not the correct view
+ if (!(v.getParent() instanceof DeepShortcutView)) return false;
+
+ // Long clicked on a shortcut.
+ DeepShortcutView sv = (DeepShortcutView) v.getParent();
+ sv.setWillDrawIcon(false);
+
+ // Move the icon to align with the center-top of the touch point
+ Point iconShift = new Point();
+ iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
+ iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
+
+ DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
+ mContainer, sv.getFinalInfo(),
+ new ShortcutDragPreviewProvider(sv.getIconView(), iconShift),
+ new DragOptions());
+ dv.animateShift(-iconShift.x, -iconShift.y);
+
+ // TODO: support dragging from within folder without having to close it
+ AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
+ return false;
+ }
+ }
}
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index c5aa836..1092c7b 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -24,7 +24,6 @@
import androidx.annotation.Nullable;
import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.notification.NotificationKeyData;
@@ -41,6 +40,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -52,7 +52,7 @@
private static final boolean LOGD = false;
private static final String TAG = "PopupDataProvider";
- private final Launcher mLauncher;
+ private final Consumer<Predicate<PackageUserKey>> mNotificationDotsChangeListener;
/** Maps launcher activity components to a count of how many shortcuts they have. */
private HashMap<ComponentKey, Integer> mDeepShortcutMap = new HashMap<>();
@@ -63,12 +63,12 @@
private PopupDataChangeListener mChangeListener = PopupDataChangeListener.INSTANCE;
- public PopupDataProvider(Launcher launcher) {
- mLauncher = launcher;
+ public PopupDataProvider(Consumer<Predicate<PackageUserKey>> notificationDotsChangeListener) {
+ mNotificationDotsChangeListener = notificationDotsChangeListener;
}
private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
- mLauncher.updateNotificationDots(updatedDots);
+ mNotificationDotsChangeListener.accept(updatedDots);
mChangeListener.onNotificationDotsUpdated(updatedDots);
}
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index 947f49d..9faeb40 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -24,8 +24,8 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.notification.NotificationInfo;
@@ -33,7 +33,6 @@
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.util.PackageUserKey;
import java.util.ArrayList;
import java.util.Collections;
@@ -123,7 +122,11 @@
return filteredShortcuts;
}
- public static Runnable createUpdateRunnable(final Launcher launcher, final ItemInfo originalInfo,
+ /**
+ * Returns a runnable to update the provided shortcuts and notifications
+ */
+ public static Runnable createUpdateRunnable(final BaseDraggingActivity launcher,
+ final ItemInfo originalInfo,
final Handler uiHandler, final PopupContainerWithArrow container,
final List<DeepShortcutView> shortcutViews,
final List<NotificationKeyData> notificationKeys) {
@@ -162,11 +165,6 @@
final DeepShortcutView view = shortcutViews.get(i);
uiHandler.post(() -> view.applyShortcutInfo(si, shortcut, container));
}
-
- // This ensures that mLauncher.getWidgetsForPackageUser()
- // doesn't return null (it puts all the widgets in memory).
- uiHandler.post(() -> launcher.refreshAndBindWidgetsForPackageUser(
- PackageUserKey.fromItemInfo(originalInfo)));
};
}
}
diff --git a/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
new file mode 100644
index 0000000..54b7fb9
--- /dev/null
+++ b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2020 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.secondarydisplay;
+
+import static android.content.Context.MODE_PRIVATE;
+
+import android.content.ComponentName;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.os.Process;
+import android.os.UserHandle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsStore;
+import com.android.launcher3.allapps.AppInfoComparator;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.Executors;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * Adapter to manage pinned apps and show then in a grid.
+ */
+public class PinnedAppsAdapter extends BaseAdapter implements OnSharedPreferenceChangeListener {
+
+ private static final String PINNED_APPS_KEY = "pinned_apps";
+
+ private final SecondaryDisplayLauncher mLauncher;
+ private final OnClickListener mOnClickListener;
+ private final OnLongClickListener mOnLongClickListener;
+ private final SharedPreferences mPrefs;
+ private final AllAppsStore mAllAppsList;
+ private final AppInfoComparator mAppNameComparator;
+
+ private final Set<ComponentKey> mPinnedApps = new HashSet<>();
+ private final ArrayList<AppInfo> mItems = new ArrayList<>();
+
+ public PinnedAppsAdapter(SecondaryDisplayLauncher launcher, AllAppsStore allAppsStore,
+ OnLongClickListener onLongClickListener) {
+ mLauncher = launcher;
+ mOnClickListener = launcher.getItemOnClickListener();
+ mOnLongClickListener = onLongClickListener;
+ mAllAppsList = allAppsStore;
+ mPrefs = launcher.getSharedPreferences(PINNED_APPS_KEY, MODE_PRIVATE);
+ mAppNameComparator = new AppInfoComparator(launcher);
+
+ mAllAppsList.addUpdateListener(this::createFilteredAppsList);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+ if (PINNED_APPS_KEY.equals(key)) {
+ Executors.MODEL_EXECUTOR.submit(() -> {
+ Set<ComponentKey> apps = prefs.getStringSet(key, Collections.emptySet())
+ .stream()
+ .map(this::parseComponentKey)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Executors.MAIN_EXECUTOR.submit(() -> {
+ mPinnedApps.clear();
+ mPinnedApps.addAll(apps);
+ createFilteredAppsList();
+ });
+ });
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getCount() {
+ return mItems.size();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AppInfo getItem(int position) {
+ return mItems.get(position);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public View getView(int position, View view, ViewGroup parent) {
+ BubbleTextView icon;
+ if (view instanceof BubbleTextView) {
+ icon = (BubbleTextView) view;
+ } else {
+ icon = (BubbleTextView) LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.app_icon, parent, false);
+ icon.setOnClickListener(mOnClickListener);
+ icon.setOnLongClickListener(mOnLongClickListener);
+ icon.setLongPressTimeoutFactor(1f);
+ int padding = mLauncher.getDeviceProfile().edgeMarginPx;
+ icon.setPadding(padding, padding, padding, padding);
+ }
+
+ icon.applyFromApplicationInfo(mItems.get(position));
+ return icon;
+ }
+
+ private void createFilteredAppsList() {
+ mItems.clear();
+ mPinnedApps.stream().map(mAllAppsList::getApp)
+ .filter(Objects::nonNull).forEach(mItems::add);
+ mItems.sort(mAppNameComparator);
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Initialized the pinned apps list and starts listening for changes
+ */
+ public void init() {
+ mPrefs.registerOnSharedPreferenceChangeListener(this);
+ onSharedPreferenceChanged(mPrefs, PINNED_APPS_KEY);
+ }
+
+ /**
+ * Stops listening for any pinned apps changes
+ */
+ public void destroy() {
+ mPrefs.unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ private void update(ItemInfo info, Function<ComponentKey, Boolean> op) {
+ ComponentKey key = new ComponentKey(info.getTargetComponent(), info.user);
+ if (op.apply(key)) {
+ createFilteredAppsList();
+ Set<ComponentKey> copy = new HashSet<>(mPinnedApps);
+ Executors.MODEL_EXECUTOR.submit(() ->
+ mPrefs.edit().putStringSet(PINNED_APPS_KEY,
+ copy.stream().map(this::encode).collect(Collectors.toSet()))
+ .apply());
+ }
+ }
+
+ private ComponentKey parseComponentKey(String string) {
+ try {
+ String[] parts = string.split("#");
+ UserHandle user;
+ if (parts.length > 2) {
+ user = UserCache.INSTANCE.get(mLauncher)
+ .getUserForSerialNumber(Long.parseLong(parts[2]));
+ } else {
+ user = Process.myUserHandle();
+ }
+ ComponentName cn = ComponentName.unflattenFromString(parts[0]);
+ return new ComponentKey(cn, user);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private String encode(ComponentKey key) {
+ return key.componentName.flattenToShortString() + "#"
+ + UserCache.INSTANCE.get(mLauncher).getSerialNumberForUser(key.user);
+ }
+
+ /**
+ * Returns a system shortcut to pin/unpin a shortcut
+ */
+ public SystemShortcut getSystemShortcut(ItemInfo info) {
+ return new PinUnPinShortcut(mLauncher, info,
+ mPinnedApps.contains(new ComponentKey(info.getTargetComponent(), info.user)));
+ }
+
+ private class PinUnPinShortcut extends SystemShortcut<SecondaryDisplayLauncher> {
+
+ private final boolean mIsPinned;
+
+ PinUnPinShortcut(SecondaryDisplayLauncher target, ItemInfo info, boolean isPinned) {
+ super(isPinned ? R.drawable.ic_remove_no_shadow : R.drawable.ic_pin,
+ isPinned ? R.string.remove_drop_target_label : R.string.action_add_to_workspace,
+ target, info);
+ mIsPinned = isPinned;
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (mIsPinned) {
+ update(mItemInfo, mPinnedApps::remove);
+ } else {
+ update(mItemInfo, mPinnedApps::add);
+ }
+ AbstractFloatingView.closeAllOpenViews(mLauncher);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
new file mode 100644
index 0000000..1cc01f4
--- /dev/null
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2020 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.secondarydisplay;
+
+import static com.android.launcher3.model.AppLaunchTracker.CONTAINER_ALL_APPS;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewAnimationUtils;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.PromiseAppInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.popup.PopupDataProvider;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.Themes;
+import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.launcher3.views.BaseDragLayer;
+import com.android.launcher3.widget.WidgetListRowEntry;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Launcher activity for secondary displays
+ */
+public class SecondaryDisplayLauncher extends BaseDraggingActivity
+ implements BgDataModel.Callbacks {
+
+ private LauncherModel mModel;
+
+ private BaseDragLayer mDragLayer;
+ private AllAppsContainerView mAppsView;
+ private View mAppsButton;
+
+ private PopupDataProvider mPopupDataProvider;
+
+ private boolean mAppDrawerShown = false;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mModel = LauncherAppState.getInstance(this).getModel();
+ if (getWindow().getDecorView().isAttachedToWindow()) {
+ initUi();
+ }
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ initUi();
+ }
+
+ private void initUi() {
+ if (mDragLayer != null) {
+ return;
+ }
+ InvariantDeviceProfile mainIdp = LauncherAppState.getIDP(this);
+ InvariantDeviceProfile currentDisplayIdp =
+ new InvariantDeviceProfile(this, getWindow().getDecorView().getDisplay());
+
+ // Pick the device profile with the smaller icon size so that the cached icons are
+ // shown properly
+ if (mainIdp.iconBitmapSize <= currentDisplayIdp.iconBitmapSize) {
+ mDeviceProfile = mainIdp.getDeviceProfile(this).copy(this);
+ } else {
+ mDeviceProfile = currentDisplayIdp.getDeviceProfile(this);
+ }
+
+ setContentView(R.layout.secondary_launcher);
+ mDragLayer = findViewById(R.id.drag_layer);
+ mAppsView = findViewById(R.id.apps_view);
+ mAppsButton = findViewById(R.id.all_apps_button);
+
+ mPopupDataProvider = new PopupDataProvider(
+ mAppsView.getAppsStore()::updateNotificationDots);
+
+ mModel.addCallbacksAndLoad(this);
+ }
+
+ @Override
+ public void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+
+ if (Intent.ACTION_MAIN.equals(intent.getAction())) {
+ // Hide keyboard.
+ final View v = getWindow().peekDecorView();
+ if (v != null && v.getWindowToken() != null) {
+ getSystemService(InputMethodManager.class).hideSoftInputFromWindow(
+ v.getWindowToken(), 0);
+ }
+ }
+
+ // A new intent will bring the launcher to top. Hide the app drawer to reset the state.
+ showAppDrawer(false);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (finishAutoCancelActionMode()) {
+ return;
+ }
+
+ // Note: There should be at most one log per method call. This is enforced implicitly
+ // by using if-else statements.
+ AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
+ if (topView != null && topView.onBackPressed()) {
+ // Handled by the floating view.
+ } else {
+ showAppDrawer(false);
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mModel.removeCallbacks(this);
+ }
+
+ public boolean isAppDrawerShown() {
+ return mAppDrawerShown;
+ }
+
+ public AllAppsContainerView getAppsView() {
+ return mAppsView;
+ }
+
+ @Override
+ public <T extends View> T getOverviewPanel() {
+ return null;
+ }
+
+ @Override
+ public View getRootView() {
+ return mDragLayer;
+ }
+
+ @Override
+ public ActivityOptions getActivityLaunchOptions(View v) {
+ return null;
+ }
+
+ @Override
+ protected void reapplyUi() { }
+
+ @Override
+ public BaseDragLayer getDragLayer() {
+ return mDragLayer;
+ }
+
+ @Override
+ public int getPageToBindSynchronously() {
+ return 0;
+ }
+
+ @Override
+ public void clearPendingBinds() { }
+
+ @Override
+ public void startBinding() { }
+
+ @Override
+ public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
+
+ @Override
+ public void bindScreens(IntArray orderedScreenIds) { }
+
+ @Override
+ public void finishFirstPageBind(ViewOnDrawExecutor executor) {
+ if (executor != null) {
+ executor.onLoadAnimationCompleted();
+ }
+ }
+
+ @Override
+ public void finishBindingItems(int pageBoundFirst) { }
+
+ @Override
+ public void preAddApps() { }
+
+ @Override
+ public void bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated,
+ ArrayList<ItemInfo> addAnimated) { }
+
+ @Override
+ public void bindPromiseAppProgressUpdated(PromiseAppInfo app) {
+ mAppsView.getAppsStore().updatePromiseAppProgress(app);
+ }
+
+ @Override
+ public void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated) { }
+
+ @Override
+ public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
+
+ @Override
+ public void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
+
+ @Override
+ public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) { }
+
+ @Override
+ public void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets) { }
+
+ @Override
+ public void onPageBoundSynchronously(int page) { }
+
+ @Override
+ public void executeOnNextDraw(ViewOnDrawExecutor executor) {
+ executor.attachTo(getDragLayer(), false, null);
+ }
+
+ /**
+ * Called when apps-button is clicked
+ */
+ public void onAppsButtonClicked(View v) {
+ showAppDrawer(true);
+ }
+
+ /**
+ * Show/hide app drawer card with animation.
+ */
+ public void showAppDrawer(boolean show) {
+ if (show == mAppDrawerShown) {
+ return;
+ }
+
+ float openR = (float) Math.hypot(mAppsView.getWidth(), mAppsView.getHeight());
+ float closeR = Themes.getDialogCornerRadius(this);
+ float startR = mAppsButton.getWidth() / 2f;
+
+ float[] buttonPos = new float[] { startR, startR};
+ mDragLayer.getDescendantCoordRelativeToSelf(mAppsButton, buttonPos);
+ mDragLayer.mapCoordInSelfToDescendant(mAppsView, buttonPos);
+ final Animator animator = ViewAnimationUtils.createCircularReveal(mAppsView,
+ (int) buttonPos[0], (int) buttonPos[1],
+ show ? closeR : openR, show ? openR : closeR);
+
+ if (show) {
+ mAppDrawerShown = true;
+ mAppsView.setVisibility(View.VISIBLE);
+ mAppsButton.setVisibility(View.INVISIBLE);
+ } else {
+ mAppDrawerShown = false;
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAppsView.setVisibility(View.INVISIBLE);
+ mAppsButton.setVisibility(View.VISIBLE);
+ mAppsView.getSearchUiManager().resetSearch();
+ }
+ });
+ }
+ animator.start();
+ }
+
+ @Override
+ public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap) {
+ mPopupDataProvider.setDeepShortcutMap(deepShortcutMap);
+ }
+
+ @Override
+ public void bindAllApplications(AppInfo[] apps) {
+ mAppsView.getAppsStore().setApps(apps);
+ }
+
+ public PopupDataProvider getPopupDataProvider() {
+ return mPopupDataProvider;
+ }
+
+ @Override
+ public OnClickListener getItemOnClickListener() {
+ return this::onIconClicked;
+ }
+
+ private void onIconClicked(View v) {
+ // Make sure that rogue clicks don't get through while allapps is launching, or after the
+ // view has detached (it's possible for this to happen if the view is removed mid touch).
+ if (v.getWindowToken() == null) return;
+
+ Object tag = v.getTag();
+ if (tag instanceof ItemInfo) {
+ ItemInfo item = (ItemInfo) tag;
+ Intent intent;
+ if (item instanceof PromiseAppInfo) {
+ PromiseAppInfo promiseAppInfo = (PromiseAppInfo) item;
+ intent = promiseAppInfo.getMarketIntent(this);
+ } else {
+ intent = item.getIntent();
+ }
+ if (intent == null) {
+ throw new IllegalArgumentException("Input must have a valid intent");
+ }
+ startActivitySafely(v, intent, item, CONTAINER_ALL_APPS);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
new file mode 100644
index 0000000..8fffee8
--- /dev/null
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2020 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.secondarydisplay;
+
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.GridView;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.util.ShortcutUtil;
+import com.android.launcher3.util.TouchController;
+import com.android.launcher3.views.BaseDragLayer;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * DragLayer for Secondary launcher
+ */
+public class SecondaryDragLayer extends BaseDragLayer<SecondaryDisplayLauncher> {
+
+ private View mAllAppsButton;
+ private AllAppsContainerView mAppsView;
+
+ private GridView mWorkspace;
+ private PinnedAppsAdapter mPinnedAppsAdapter;
+
+ public SecondaryDragLayer(Context context, AttributeSet attrs) {
+ super(context, attrs, 1 /* alphaChannelCount */);
+ mControllers = new TouchController[] {new CloseAllAppsTouchController()};
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mAllAppsButton = findViewById(R.id.all_apps_button);
+
+ mAppsView = findViewById(R.id.apps_view);
+ mAppsView.setOnIconLongClickListener(this::onIconLongClicked);
+
+ // Setup workspace
+ mWorkspace = findViewById(R.id.workspace_grid);
+ mPinnedAppsAdapter = new PinnedAppsAdapter(mActivity, mAppsView.getAppsStore(),
+ this::onIconLongClicked);
+ mWorkspace.setAdapter(mPinnedAppsAdapter);
+ mWorkspace.setNumColumns(mActivity.getDeviceProfile().inv.numColumns);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mPinnedAppsAdapter.init();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mPinnedAppsAdapter.destroy();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ setMeasuredDimension(width, height);
+
+ DeviceProfile grid = mActivity.getDeviceProfile();
+ InvariantDeviceProfile idp = grid.inv;
+
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child == mAppsView) {
+ int padding = 2 * (grid.desiredWorkspaceLeftRightMarginPx
+ + grid.cellLayoutPaddingLeftRightPx);
+ int maxWidth = grid.allAppsCellWidthPx * idp.numAllAppsColumns + padding;
+
+ int appsWidth = Math.min(width, maxWidth);
+ int appsHeight = Math.round(appsWidth * (float) height / (float) width);
+
+ mAppsView.measure(
+ makeMeasureSpec(appsWidth, EXACTLY), makeMeasureSpec(appsHeight, EXACTLY));
+
+ } else if (child == mAllAppsButton) {
+ int appsButtonSpec = makeMeasureSpec(grid.iconSizePx, EXACTLY);
+ mAllAppsButton.measure(appsButtonSpec, appsButtonSpec);
+
+ } else if (child == mWorkspace) {
+ measureChildWithMargins(mWorkspace, widthMeasureSpec, 0, heightMeasureSpec,
+ grid.iconSizePx + grid.edgeMarginPx);
+
+ } else {
+ measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
+ }
+ }
+ }
+
+ private class CloseAllAppsTouchController implements TouchController {
+
+ @Override
+ public boolean onControllerTouchEvent(MotionEvent ev) {
+ return false;
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if (!mActivity.isAppDrawerShown()) {
+ return false;
+ }
+
+ if (AbstractFloatingView.getTopOpenView(mActivity) != null) {
+ return false;
+ }
+
+ if (ev.getAction() == MotionEvent.ACTION_DOWN
+ && !isEventOverView(mActivity.getAppsView(), ev)) {
+ mActivity.showAppDrawer(false);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private boolean onIconLongClicked(View v) {
+ if (!(v instanceof BubbleTextView)) {
+ return false;
+ }
+ if (PopupContainerWithArrow.getOpen(mActivity) != null) {
+ // There is already an items container open, so don't open this one.
+ v.clearFocus();
+ return false;
+ }
+ ItemInfo item = (ItemInfo) v.getTag();
+ if (!ShortcutUtil.supportsShortcuts(item)) {
+ return false;
+ }
+ final PopupContainerWithArrow container =
+ (PopupContainerWithArrow) mActivity.getLayoutInflater().inflate(
+ R.layout.popup_container, mActivity.getDragLayer(), false);
+
+ container.populateAndShow((BubbleTextView) v,
+ mActivity.getPopupDataProvider().getShortcutCountForItem(item),
+ Collections.emptyList(),
+ Arrays.asList(mPinnedAppsAdapter.getSystemShortcut(item),
+ APP_INFO.getShortcut(mActivity, item)));
+ v.getParent().requestDisallowInterceptTouchEvent(true);
+ return true;
+ }
+}
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index 9274d44..9cc7d8f 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -27,8 +27,8 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.Utilities;
+import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.popup.PopupContainerWithArrow;
/**
@@ -111,8 +111,8 @@
// TODO: Add the click handler to this view directly and not the child view.
mBubbleText.setOnClickListener(container.getItemClickListener());
- mBubbleText.setOnLongClickListener(container);
- mBubbleText.setOnTouchListener(container);
+ mBubbleText.setOnLongClickListener(container.getItemDragHandler());
+ mBubbleText.setOnTouchListener(container.getItemDragHandler());
}
/**
diff --git a/src/com/android/launcher3/states/HintState.java b/src/com/android/launcher3/states/HintState.java
new file mode 100644
index 0000000..cb56097
--- /dev/null
+++ b/src/com/android/launcher3/states/HintState.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 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.states;
+
+import static com.android.launcher3.LauncherAnimUtils.HINT_TRANSITION_MS;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+
+/**
+ * Scale down workspace/hotseat to hint at going to either overview (on pause) or first home screen.
+ */
+public class HintState extends LauncherState {
+
+ private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY | FLAG_DISABLE_RESTORE
+ | FLAG_HAS_SYS_UI_SCRIM;
+
+ public HintState(int id) {
+ super(id, ContainerType.DEFAULT_CONTAINERTYPE, HINT_TRANSITION_MS, STATE_FLAGS);
+ }
+
+ @Override
+ public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
+ return new ScaleAndTranslation(0.9f, 0, 0);
+ }
+
+ @Override
+ public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
+ // Treat the QSB as part of the hotseat so they move together.
+ return getHotseatScaleAndTranslation(launcher);
+ }
+
+ @Override
+ public void onStateTransitionEnd(Launcher launcher) {
+ launcher.getStateManager().goToState(NORMAL);
+ Workspace workspace = launcher.getWorkspace();
+ workspace.post(workspace::moveToDefaultScreen);
+ }
+}
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 506830d..4af5e0a 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -25,6 +25,7 @@
import android.os.Bundle;
import android.os.Debug;
import android.system.Os;
+import android.util.Log;
import android.view.View;
import androidx.annotation.Keep;
@@ -190,6 +191,11 @@
}
protected boolean isLauncherInitialized() {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
+ "isLauncherInitialized " + Launcher.ACTIVITY_TRACKER.getCreatedActivity() + ", "
+ + LauncherAppState.getInstance(mContext).getModel().isModelLoaded());
+ }
return Launcher.ACTIVITY_TRACKER.getCreatedActivity() == null
|| LauncherAppState.getInstance(mContext).getModel().isModelLoaded();
}
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 01c207f..2f053c9 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -31,6 +31,7 @@
public static final int QUICK_SWITCH_STATE_ORDINAL = 4;
public static final int ALL_APPS_STATE_ORDINAL = 5;
public static final int BACKGROUND_APP_STATE_ORDINAL = 6;
+ public static final int HINT_STATE_ORDINAL = 7;
public static final String TAPL_EVENTS_TAG = "TaplEvents";
public static String stateOrdinalToString(int ordinal) {
@@ -49,6 +50,8 @@
return "AllApps";
case BACKGROUND_APP_STATE_ORDINAL:
return "Background";
+ case HINT_STATE_ORDINAL:
+ return "Hint";
default:
return null;
}
@@ -89,4 +92,5 @@
public static final String NO_BACKGROUND_TO_OVERVIEW_TAG = "b/138251824";
public static final String APP_NOT_DISABLED = "b/139891609";
+ public static final String LAUNCHER_DIDNT_INITIALIZE = "b/148313079";
}
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index d193bef..3ec480d 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -525,7 +525,11 @@
if (targetState != mStartState) {
logReachedState(logAction, targetState);
}
- mLauncher.getStateManager().goToState(targetState, false /* animated */);
+ if (!mLauncher.isInState(targetState)) {
+ // If we're already in the target state, don't jump to it at the end of the animation in
+ // case the user started interacting with it before the animation finished.
+ mLauncher.getStateManager().goToState(targetState, false /* animated */);
+ }
mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(1, 0, 0);
}
diff --git a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
index f2ebc45..9d406f3 100644
--- a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
+++ b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
@@ -54,8 +54,8 @@
}
@Override
- boolean canScrollStart(PointF displacement, float touchSlop) {
- return Math.abs(displacement.y) >= Math.max(Math.abs(displacement.x), touchSlop);
+ float extractOrthogonalDirection(PointF direction) {
+ return direction.x;
}
};
@@ -80,9 +80,10 @@
}
@Override
- boolean canScrollStart(PointF displacement, float touchSlop) {
- return Math.abs(displacement.x) >= Math.max(Math.abs(displacement.y), touchSlop);
+ float extractOrthogonalDirection(PointF direction) {
+ return direction.y;
}
+
};
private final Direction mDir;
@@ -126,7 +127,9 @@
@Override
protected boolean shouldScrollStart(PointF displacement) {
// Reject cases where the angle or slop condition is not met.
- if (!mDir.canScrollStart(displacement, mTouchSlop)) {
+ float minDisplacement = Math.max(mTouchSlop,
+ Math.abs(mDir.extractOrthogonalDirection(displacement)));
+ if (Math.abs(mDir.extractDirection(displacement)) < minDisplacement) {
return false;
}
@@ -150,7 +153,8 @@
@Override
protected void reportDraggingInternal(PointF displacement, MotionEvent event) {
- mListener.onDrag(mDir.extractDirection(displacement), event);
+ mListener.onDrag(mDir.extractDirection(displacement),
+ mDir.extractOrthogonalDirection(displacement), event);
}
@Override
@@ -164,13 +168,16 @@
/** @param start whether this was the original drag start, as opposed to a recatch. */
void onDragStart(boolean start);
- // TODO remove
boolean onDrag(float displacement);
default boolean onDrag(float displacement, MotionEvent event) {
return onDrag(displacement);
}
+ default boolean onDrag(float displacement, float orthogonalDisplacement, MotionEvent ev) {
+ return onDrag(displacement, ev);
+ }
+
void onDragEnd(float velocity);
}
@@ -183,8 +190,7 @@
/** Returns the part of the given {@link PointF} that is relevant to this direction. */
abstract float extractDirection(PointF point);
- /** Reject cases where the angle or slop condition is not met. */
- abstract boolean canScrollStart(PointF displacement, float touchSlop);
+ abstract float extractOrthogonalDirection(PointF point);
}
}
diff --git a/src/com/android/launcher3/util/ActivityTracker.java b/src/com/android/launcher3/util/ActivityTracker.java
index 499f655..b83c8fc 100644
--- a/src/com/android/launcher3/util/ActivityTracker.java
+++ b/src/com/android/launcher3/util/ActivityTracker.java
@@ -45,7 +45,13 @@
}
public void onActivityDestroyed(T activity) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "onActivityDestroyed");
+ }
if (mCurrentActivity.get() == activity) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "onActivityDestroyed: clear");
+ }
mCurrentActivity.clear();
}
}
@@ -110,6 +116,10 @@
}
public boolean handleCreate(T activity) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
+ "ActivityTracker.handleCreate " + mCurrentActivity.get() + " => " + activity);
+ }
mCurrentActivity = new WeakReference<>(activity);
return handleIntent(activity, activity.getIntent(), false, false);
}
diff --git a/src/com/android/launcher3/util/DefaultDisplay.java b/src/com/android/launcher3/util/DefaultDisplay.java
index 8529d50..d3dac04 100644
--- a/src/com/android/launcher3/util/DefaultDisplay.java
+++ b/src/com/android/launcher3/util/DefaultDisplay.java
@@ -128,8 +128,10 @@
public final DisplayMetrics metrics;
private Info(Context context) {
- Display display = context.getSystemService(WindowManager.class).getDefaultDisplay();
+ this(context.getSystemService(WindowManager.class).getDefaultDisplay());
+ }
+ public Info(Display display) {
id = display.getDisplayId();
rotation = display.getRotation();
diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
index 451ae28..82e24c2 100644
--- a/src/com/android/launcher3/util/ViewOnDrawExecutor.java
+++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
@@ -29,6 +29,7 @@
import java.util.ArrayList;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* An executor which runs all the tasks after the first onDraw is called on the target view.
@@ -38,7 +39,7 @@
private final ArrayList<Runnable> mTasks = new ArrayList<>();
- private Launcher mLauncher;
+ private Consumer<ViewOnDrawExecutor> mOnClearCallback;
private View mAttachedView;
private boolean mCompleted;
@@ -46,11 +47,16 @@
private boolean mFirstDrawCompleted;
public void attachTo(Launcher launcher) {
- attachTo(launcher, launcher.getWorkspace(), true /* waitForLoadAnimation */);
+ attachTo(launcher.getWorkspace(), true /* waitForLoadAnimation */,
+ launcher::clearPendingExecutor);
}
- public void attachTo(Launcher launcher, View attachedView, boolean waitForLoadAnimation) {
- mLauncher = launcher;
+ /**
+ * Attached the executor to the existence of the view
+ */
+ public void attachTo(View attachedView, boolean waitForLoadAnimation,
+ Consumer<ViewOnDrawExecutor> onClearCallback) {
+ mOnClearCallback = onClearCallback;
mAttachedView = attachedView;
mAttachedView.addOnAttachStateChangeListener(this);
if (!waitForLoadAnimation) {
@@ -110,8 +116,8 @@
mAttachedView.getViewTreeObserver().removeOnDrawListener(this);
mAttachedView.removeOnAttachStateChangeListener(this);
}
- if (mLauncher != null) {
- mLauncher.clearPendingExecutor(this);
+ if (mOnClearCallback != null) {
+ mOnClearCallback.accept(this);
}
MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
}
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 5ba931d..880f123 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -126,7 +126,8 @@
popup.mTargetRect = targetRect;
for (OptionItem item : items) {
- DeepShortcutView view = popup.inflateAndAdd(R.layout.system_shortcut, popup);
+ DeepShortcutView view =
+ (DeepShortcutView) popup.inflateAndAdd(R.layout.system_shortcut, popup);
view.getIconView().setBackgroundResource(item.mIconRes);
view.getBubbleText().setText(item.mLabelRes);
view.setDividerVisibility(View.INVISIBLE);
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index f713b33..f055adf 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -104,7 +104,7 @@
}
private void setContainerWidth() {
- mCellSize = (int) (mDeviceProfile.allAppsCellWidthPx * WIDTH_SCALE);
+ mCellSize = (int) (mDeviceProfile.cellWidthPx * WIDTH_SCALE);
mPresetPreviewSize = (int) (mCellSize * PREVIEW_SCALE);
}
diff --git a/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
index efbd9c9..33066e4 100644
--- a/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
+++ b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
@@ -49,7 +49,7 @@
super.setUp();
mDevice.pressHome();
waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
- waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL);
+ waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
mSessionId = -1;
}
diff --git a/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
index 6d463b5..b0ece77 100644
--- a/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
+++ b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
@@ -22,15 +22,16 @@
import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyFloat;
-import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.util.Log;
+import android.view.MotionEvent;
import android.view.ViewConfiguration;
import androidx.test.InstrumentationRegistry;
@@ -158,7 +159,7 @@
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(), anyObject());
+ verify(mMockListener).onDrag(anyFloat(), anyFloat(), any(MotionEvent.class));
}
@Test
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 19997eb..4ffc251 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -84,6 +84,7 @@
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.Supplier;
/**
* Base class for all instrumentation tests providing various utility methods.
@@ -183,16 +184,8 @@
@After
public void verifyLauncherState() {
- try {
- // Limits UI tests affecting tests running after them.
- mLauncher.waitForLauncherInitialized();
- } catch (Throwable t) {
- Log.e(TAG,
- "Couldn't deinit after a test, exiting tests, see logs for failures that "
- + "could have caused this",
- t);
- exit(1);
- }
+ // Limits UI tests affecting tests running after them.
+ mLauncher.waitForLauncherInitialized();
}
protected void clearLauncherData() throws IOException, InterruptedException {
@@ -281,9 +274,9 @@
// Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call expecting
// the results of that gesture because the wait can hide flakeness.
- protected void waitForState(String message, LauncherState state) {
+ protected void waitForState(String message, Supplier<LauncherState> state) {
waitForLauncherCondition(message,
- launcher -> launcher.getStateManager().getCurrentStableState() == state);
+ launcher -> launcher.getStateManager().getCurrentStableState() == state.get());
}
protected void waitForResumed(String message) {
@@ -430,9 +423,9 @@
return !launcher.hasBeenResumed();
}
- protected boolean isInState(LauncherState state) {
+ protected boolean isInState(Supplier<LauncherState> state) {
if (!TestHelpers.isInLauncherProcess()) return true;
- return getFromLauncher(launcher -> launcher.getStateManager().getState() == state);
+ return getFromLauncher(launcher -> launcher.getStateManager().getState() == state.get());
}
protected int getAllAppsScroll(Launcher launcher) {
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 9b4023e..4b72882 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -76,7 +76,8 @@
test.clearLauncherData();
test.mDevice.pressHome();
test.waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
- test.waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL);
+ test.waitForState("Launcher internal state didn't switch to Home",
+ () -> LauncherState.NORMAL);
test.waitForResumed("Launcher internal state is still Background");
// Check that we switched to home.
test.mLauncher.getWorkspace();
@@ -146,7 +147,7 @@
assertTrue(
"Launcher internal state is not All Apps",
- test.isInState(LauncherState.ALL_APPS));
+ test.isInState(() -> LauncherState.ALL_APPS));
// Test flinging forward and backward.
test.executeOnLauncher(launcher -> assertEquals(
@@ -155,7 +156,7 @@
allApps.flingForward();
assertTrue("Launcher internal state is not All Apps",
- test.isInState(LauncherState.ALL_APPS));
+ test.isInState(() -> LauncherState.ALL_APPS));
final Integer flingForwardY = test.getFromLauncher(
launcher -> test.getAllAppsScroll(launcher));
test.executeOnLauncher(
@@ -165,7 +166,7 @@
allApps.flingBackward();
assertTrue(
"Launcher internal state is not All Apps",
- test.isInState(LauncherState.ALL_APPS));
+ test.isInState(() -> LauncherState.ALL_APPS));
final Integer flingBackwardY = test.getFromLauncher(
launcher -> test.getAllAppsScroll(launcher));
test.executeOnLauncher(launcher -> assertTrue("flingBackward() didn't scroll App Apps",
@@ -182,7 +183,7 @@
assertTrue(
"Launcher internal state is not All Apps",
- test.isInState(LauncherState.ALL_APPS));
+ test.isInState(() -> LauncherState.ALL_APPS));
} finally {
allApps.unfreeze();
}
@@ -193,7 +194,8 @@
public void testWorkspaceSwitchToAllApps() {
assertNotNull("switchToAllApps() returned null",
mLauncher.getWorkspace().switchToAllApps());
- assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
+ assertTrue("Launcher internal state is not All Apps",
+ isInState(() -> LauncherState.ALL_APPS));
}
@Test
@@ -219,7 +221,7 @@
// Test flinging workspace.
workspace.flingBackward();
- assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL));
+ assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL));
executeOnLauncher(
launcher -> assertEquals("Flinging back didn't switch workspace to page #0",
0, getCurrentWorkspacePage(launcher)));
@@ -228,7 +230,7 @@
executeOnLauncher(
launcher -> assertEquals("Flinging forward didn't switch workspace to page #1",
1, getCurrentWorkspacePage(launcher)));
- assertTrue("Launcher internal state is not Home", isInState(LauncherState.NORMAL));
+ assertTrue("Launcher internal state is not Home", isInState(() -> LauncherState.NORMAL));
// Test starting a workspace app.
final AppIcon app = workspace.getWorkspaceAppIcon("Chrome");
@@ -254,7 +256,8 @@
@PortraitLandscape
public void testAppIconLaunchFromAllAppsFromHome() throws Exception {
final AllApps allApps = mLauncher.getWorkspace().switchToAllApps();
- assertTrue("Launcher internal state is not All Apps", isInState(LauncherState.ALL_APPS));
+ assertTrue("Launcher internal state is not All Apps",
+ isInState(() -> LauncherState.ALL_APPS));
runIconLaunchFromAllAppsTest(this, allApps);
}
diff --git a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
index c553b9b..be6f865 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
@@ -52,6 +52,12 @@
logSinceTestsStart)) {
return 147845913;
}
+ if (matches(
+ "Attempt to invoke virtual method 'boolean android\\.graphics\\.Bitmap\\"
+ + ".isRecycled\\(\\)' on a null object reference",
+ logSinceTestsStart)) {
+ return 148424291;
+ }
} else if (matches("java.lang.AssertionError: Launcher build match not found", exception)) {
if (matches(
"TestStabilityRule: Launcher package: com.google.android.setupwizard",
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index cdda0f0..7763cc2 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -8,6 +8,7 @@
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -15,6 +16,7 @@
public class FailureWatcher extends TestWatcher {
private static final String TAG = "FailureWatcher";
+ private static boolean sHadFailedTestDeinitialization;
final private UiDevice mDevice;
public FailureWatcher(UiDevice device) {
@@ -60,4 +62,31 @@
device.takeScreenshot(new File(pathname));
}
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+
+ @Override
+ public void evaluate() throws Throwable {
+ if (sHadFailedTestDeinitialization) {
+ Log.d(TAG, "Skipping due to a recent test deinitialization failure: " +
+ description.getDisplayName());
+ return;
+ }
+
+ try {
+ base.evaluate();
+ } catch (Throwable e) {
+ if (!Log.getStackTraceString(e).contains(
+ "androidx.test.internal.runner.junit4.statement.RunBefores.evaluate")) {
+ // Test failed to deinitialize. Since the global state is probably
+ // corrupted, won't execute other tests.
+ sHadFailedTestDeinitialization = true;
+ }
+ throw e;
+ }
+ }
+ };
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 5c4689b..59d295f 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -37,6 +37,7 @@
*/
public class Background extends LauncherInstrumentation.VisibleContainer {
private static final int ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION = 500;
+ private static final Pattern SQUARE_BUTTON_EVENT = Pattern.compile("onOverviewToggle");
Background(LauncherInstrumentation launcher) {
super(launcher);
@@ -129,6 +130,7 @@
}
case THREE_BUTTON:
+ mLauncher.expectEvent(SQUARE_BUTTON_EVENT);
mLauncher.runToState(
() -> mLauncher.waitForSystemUiObject("recent_apps").click(),
OVERVIEW_STATE_ORDINAL);
@@ -189,8 +191,10 @@
case THREE_BUTTON:
// Double press the recents button.
UiObject2 recentsButton = mLauncher.waitForSystemUiObject("recent_apps");
+ mLauncher.expectEvent(SQUARE_BUTTON_EVENT);
mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL);
mLauncher.getOverview();
+ mLauncher.expectEvent(SQUARE_BUTTON_EVENT);
recentsButton.click();
break;
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index ede5bd9..8659aa7 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -24,6 +24,8 @@
import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiObject2;
+import com.android.launcher3.tapl.LauncherInstrumentation.GestureScope;
+
import java.util.Collection;
/**
@@ -93,7 +95,7 @@
int i = 0;
for (; ; ) {
final Collection<UiObject2> cells = mLauncher.getObjectsInContainer(
- widgetsContainer, "widgets_cell_list_container");
+ widgetsContainer, "widgets_scroll_container");
mLauncher.assertTrue("Widgets doesn't have 2 rows", cells.size() >= 2);
for (UiObject2 cell : cells) {
final UiObject2 label = cell.findObject(labelSelector);
@@ -105,6 +107,19 @@
"com.android.launcher3.widget.WidgetCell",
widget.getClassName());
+ int maxWidth = 0;
+ for (UiObject2 sibling : widget.getParent().getChildren()) {
+ maxWidth = Math.max(sibling.getVisibleBounds().width(), maxWidth);
+ }
+
+ int visibleDelta = maxWidth - widget.getVisibleBounds().width();
+ if (visibleDelta > 0) {
+ Rect parentBounds = cell.getVisibleBounds();
+ mLauncher.linearGesture(parentBounds.centerX() + visibleDelta,
+ parentBounds.centerY(), parentBounds.centerX(),
+ parentBounds.centerY(), 10, true, GestureScope.INSIDE);
+ }
+
if (widget.getVisibleBounds().bottom
<= displaySize.y - mLauncher.getBottomGestureSize()) {
return new Widget(mLauncher, widget);