Merge "Fixing another reference to Launcher activity after its destruction" into ub-launcher3-master
diff --git a/iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java b/iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java
index a66b929..5751ed9 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/BitmapRenderer.java
@@ -17,9 +17,13 @@
import android.annotation.TargetApi;
import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Picture;
+import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.Build;
+import android.os.Build.VERSION_CODES;
/**
* Interface representing a bitmap draw operation.
@@ -29,6 +33,7 @@
boolean USE_HARDWARE_BITMAP = Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
static Bitmap createSoftwareBitmap(int width, int height, BitmapRenderer renderer) {
+ GraphicsUtils.noteNewBitmapCreated();
Bitmap result = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
renderer.draw(new Canvas(result));
return result;
@@ -40,11 +45,26 @@
return createSoftwareBitmap(width, height, renderer);
}
+ GraphicsUtils.noteNewBitmapCreated();
Picture picture = new Picture();
renderer.draw(picture.beginRecording(width, height));
picture.endRecording();
return Bitmap.createBitmap(picture);
}
+ /**
+ * Returns a bitmap from subset of the source bitmap. The new bitmap may be the
+ * same object as source, or a copy may have been made.
+ */
+ static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height) {
+ if (Build.VERSION.SDK_INT >= VERSION_CODES.O && source.getConfig() == Config.HARDWARE) {
+ return createHardwareBitmap(width, height, c -> c.drawBitmap(source,
+ new Rect(x, y, x + width, y + height), new RectF(0, 0, width, height), null));
+ } else {
+ GraphicsUtils.noteNewBitmapCreated();
+ return Bitmap.createBitmap(source, x, y, width, height);
+ }
+ }
+
void draw(Canvas out);
}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java b/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
index 3e818a5..97eef66 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
@@ -21,11 +21,11 @@
import android.graphics.RegionIterator;
import android.util.Log;
+import androidx.annotation.ColorInt;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
-import androidx.annotation.ColorInt;
-
public class GraphicsUtils {
private static final String TAG = "GraphicsUtils";
@@ -73,4 +73,9 @@
}
return area;
}
+
+ /**
+ * Utility method to track new bitmap creation
+ */
+ public static void noteNewBitmapCreated() { }
}
diff --git a/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java b/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java
index 5df8043..7702727 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/ShadowGenerator.java
@@ -19,7 +19,6 @@
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
import android.graphics.BlurMaskFilter;
import android.graphics.BlurMaskFilter.Blur;
import android.graphics.Canvas;
@@ -135,9 +134,7 @@
bounds.offsetTo(center - width / 2f, center - height / 2f);
int size = center * 2;
- Bitmap result = Bitmap.createBitmap(size, size, Config.ARGB_8888);
- drawShadow(new Canvas(result));
- return result;
+ return BitmapRenderer.createHardwareBitmap(size, size, this::drawShadow);
}
public void drawShadow(Canvas c) {
diff --git a/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml b/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml
index f0e70a8..fa3a0f8 100644
--- a/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml
+++ b/quickstep/recents_ui_overrides/res/drawable/hotseat_edu_notification_icon.xml
@@ -15,5 +15,5 @@
-->
<vector android:height="24dp" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
- <path android:fillColor="@color/hotseat_edu_background" android:pathData="M19 9l1.25-2.75L23 5l-2.75-1.25L19 1l-1.25 2.75L15 5l2.75 1.25L19 9zm-7.5.5L9 4 6.5 9.5 1 12l5.5 2.5L9 20l2.5-5.5L17 12l-5.5-2.5zM19 15l-1.25 2.75L15 19l2.75 1.25L19 23l1.25-2.75L23 19l-2.75-1.25L19 15z"/>
+ <path android:fillColor="@color/bottom_panel_background" android:pathData="M19 9l1.25-2.75L23 5l-2.75-1.25L19 1l-1.25 2.75L15 5l2.75 1.25L19 9zm-7.5.5L9 4 6.5 9.5 1 12l5.5 2.5L9 20l2.5-5.5L17 12l-5.5-2.5zM19 15l-1.25 2.75L15 19l2.75 1.25L19 23l1.25-2.75L23 19l-2.75-1.25L19 15z"/>
</vector>
diff --git a/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
index ee38e3b..a7cd167 100644
--- a/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
+++ b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
@@ -24,12 +24,13 @@
<View
android:layout_width="match_parent"
android:layout_height="32dp"
- android:background="@drawable/hotseat_prediction_edu_top" />
+ android:backgroundTint="@color/bottom_panel_background"
+ android:background="@drawable/bottom_sheet_top_border" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:background="@color/hotseat_edu_background"
+ android:background="@color/bottom_panel_background"
android:orientation="vertical">
<TextView
@@ -37,8 +38,8 @@
android:layout_height="wrap_content"
android:layout_marginTop="18dp"
android:fontFamily="google-sans"
- android:paddingLeft="@dimen/hotseat_edu_padding"
- android:paddingRight="@dimen/hotseat_edu_padding"
+ android:paddingLeft="@dimen/bottom_sheet_edu_padding"
+ android:paddingRight="@dimen/bottom_sheet_edu_padding"
android:text="@string/hotseat_migrate_title"
android:textAlignment="center"
android:textColor="@android:color/white"
@@ -50,8 +51,8 @@
android:layout_marginTop="18dp"
android:layout_marginBottom="18dp"
android:fontFamily="roboto-medium"
- android:paddingLeft="@dimen/hotseat_edu_padding"
- android:paddingRight="@dimen/hotseat_edu_padding"
+ android:paddingLeft="@dimen/bottom_sheet_edu_padding"
+ android:paddingRight="@dimen/bottom_sheet_edu_padding"
android:text="@string/hotseat_migrate_message"
android:textAlignment="center"
android:textColor="@android:color/white"
@@ -72,9 +73,9 @@
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:paddingLeft="@dimen/hotseat_edu_padding"
+ android:paddingLeft="@dimen/bottom_sheet_edu_padding"
android:paddingTop="8dp"
- android:paddingRight="@dimen/hotseat_edu_padding">
+ android:paddingRight="@dimen/bottom_sheet_edu_padding">
<Button
android:id="@+id/turn_predictions_on"
diff --git a/quickstep/recents_ui_overrides/res/values/colors.xml b/quickstep/recents_ui_overrides/res/values/colors.xml
index 4fa5684..7426e30 100644
--- a/quickstep/recents_ui_overrides/res/values/colors.xml
+++ b/quickstep/recents_ui_overrides/res/values/colors.xml
@@ -6,6 +6,4 @@
<color name="all_apps_label_text_dark">#61FFFFFF</color>
<color name="all_apps_prediction_row_separator">#3c000000</color>
<color name="all_apps_prediction_row_separator_dark">#3cffffff</color>
-
- <color name="hotseat_edu_background">#f01A73E8</color>
</resources>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/res/values/dimens.xml b/quickstep/recents_ui_overrides/res/values/dimens.xml
index c458ec7..de97d08 100644
--- a/quickstep/recents_ui_overrides/res/values/dimens.xml
+++ b/quickstep/recents_ui_overrides/res/values/dimens.xml
@@ -29,7 +29,4 @@
<dimen name="swipe_up_y_overshoot">10dp</dimen>
<dimen name="swipe_up_max_workspace_trans_y">-60dp</dimen>
- <!-- Hybrid hotseat related -->
- <dimen name="hotseat_edu_padding">24dp</dimen>
-
</resources>
\ No newline at end of file
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
index 923e050..bfbd00e 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatEduController.java
@@ -109,7 +109,7 @@
NOTIFICATION_CHANNEL_ID)
.setContentTitle(name)
.setOngoing(true)
- .setColor(mLauncher.getColor(R.color.hotseat_edu_background))
+ .setColor(mLauncher.getColor(R.color.bottom_panel_background))
.setContentIntent(PendingIntent.getActivity(mLauncher, 0, intent,
PendingIntent.FLAG_CANCEL_CURRENT))
.setSmallIcon(R.drawable.hotseat_edu_notification_icon)
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/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/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
index cb20ed0..c1ea533 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/RecentsView.java
@@ -1918,6 +1918,11 @@
@Override
public void addView(View child, int index) {
+ // RecentsView is set to RTL in the constructor when system is using LTR. Here we set the
+ // child direction back to match system settings.
+ child.setLayoutDirection(
+ Utilities.isRtl(getResources())
+ ? View.LAYOUT_DIRECTION_RTL : View.LAYOUT_DIRECTION_LTR);
super.addView(child, index);
if (isExtraCardView(child, index)) {
mTaskViewStartIndex++;
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/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 5539b3e..3c6537a 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -18,6 +18,8 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Insets;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.IBinder;
@@ -25,6 +27,7 @@
import android.os.RemoteException;
import android.util.Log;
import android.view.MotionEvent;
+
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.quickstep.util.SharedApiCompat;
import com.android.systemui.shared.recents.ISystemUiProxy;
@@ -282,4 +285,15 @@
}
}
}
+
+ @Override
+ public void handleImageAsScreenshot(Bitmap bitmap, Rect rect, Insets insets, int i) {
+ if (mSystemUiProxy != null) {
+ try {
+ mSystemUiProxy.handleImageAsScreenshot(bitmap, rect, insets, i);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed call handleImageAsScreenshot", e);
+ }
+ }
+ }
}
diff --git a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
index a466f12..53859ad 100644
--- a/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
+++ b/quickstep/src/com/android/quickstep/logging/StatsLogCompatManager.java
@@ -43,7 +43,7 @@
import com.android.launcher3.userevent.nano.LauncherLogProto.ItemType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.ComponentKey;
-import com.android.systemui.shared.system.StatsLogCompat;
+import com.android.systemui.shared.system.SysUiStatsLog;
import com.google.protobuf.nano.MessageNano;
@@ -72,8 +72,8 @@
if (ext.srcTarget[0] != null) {
ext.srcTarget[0].item = LauncherTarget.APP_ICON;
}
- StatsLogCompat.write(LAUNCH_APP, srcState, BACKGROUND /* dstState */,
- MessageNano.toByteArray(ext), true);
+ SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT, LAUNCH_APP, srcState,
+ BACKGROUND /* dstState */, MessageNano.toByteArray(ext), true);
}
@Override
@@ -82,8 +82,8 @@
ext.srcTarget = new LauncherTarget[SUPPORTED_TARGET_DEPTH];
int srcState = OVERVIEW;
fillInLauncherExtension(v, ext);
- StatsLogCompat.write(LAUNCH_TASK, srcState, BACKGROUND /* dstState */,
- MessageNano.toByteArray(ext), true);
+ SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT, LAUNCH_TASK, srcState,
+ BACKGROUND /* dstState */, MessageNano.toByteArray(ext), true);
}
@Override
@@ -92,8 +92,8 @@
ext.srcTarget = new LauncherTarget[SUPPORTED_TARGET_DEPTH];
int srcState = OVERVIEW;
fillInLauncherExtension(v, ext);
- StatsLogCompat.write(DISMISS_TASK, srcState, BACKGROUND /* dstState */,
- MessageNano.toByteArray(ext), true);
+ SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT, DISMISS_TASK, srcState,
+ BACKGROUND /* dstState */, MessageNano.toByteArray(ext), true);
}
@Override
@@ -103,7 +103,7 @@
int srcState = mStateProvider.getCurrentState();
fillInLauncherExtensionWithPageId(ext, pageId);
int launcherAction = isSwipingToLeft ? Launcher.SWIPE_LEFT : Launcher.SWIPE_RIGHT;
- StatsLogCompat.write(launcherAction, srcState, srcState,
+ SysUiStatsLog.write(SysUiStatsLog.LAUNCHER_EVENT, launcherAction, srcState, srcState,
MessageNano.toByteArray(ext), true);
}
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-hdpi/work_tab_user_education.png b/res/drawable-hdpi/work_tab_user_education.png
deleted file mode 100644
index 1879dfb..0000000
--- a/res/drawable-hdpi/work_tab_user_education.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-mdpi/work_tab_user_education.png b/res/drawable-mdpi/work_tab_user_education.png
deleted file mode 100644
index 65c7e63..0000000
--- a/res/drawable-mdpi/work_tab_user_education.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xhdpi/work_tab_user_education.png b/res/drawable-xhdpi/work_tab_user_education.png
deleted file mode 100644
index 59df7a8..0000000
--- a/res/drawable-xhdpi/work_tab_user_education.png
+++ /dev/null
Binary files differ
diff --git a/res/drawable-xxhdpi/work_tab_user_education.png b/res/drawable-xxhdpi/work_tab_user_education.png
deleted file mode 100644
index 3c6aa20..0000000
--- a/res/drawable-xxhdpi/work_tab_user_education.png
+++ /dev/null
Binary files differ
diff --git a/quickstep/recents_ui_overrides/res/drawable/hotseat_prediction_edu_top.xml b/res/drawable/bottom_sheet_top_border.xml
similarity index 84%
rename from quickstep/recents_ui_overrides/res/drawable/hotseat_prediction_edu_top.xml
rename to res/drawable/bottom_sheet_top_border.xml
index e3cc549..23f4e51 100644
--- a/quickstep/recents_ui_overrides/res/drawable/hotseat_prediction_edu_top.xml
+++ b/res/drawable/bottom_sheet_top_border.xml
@@ -15,5 +15,5 @@
-->
<vector android:height="15.53398dp" android:viewportHeight="32"
android:viewportWidth="412" android:width="200dp" xmlns:android="http://schemas.android.com/apk/res/android">
- <path android:fillColor="@color/hotseat_edu_background" android:pathData="M412,32v-2.64C349.26,10.51 279.5,0 206,0S62.74,10.51 0,29.36V32H412z"/>
+ <path android:fillColor="@android:color/white" android:pathData="M412,32v-2.64C349.26,10.51 279.5,0 206,0S62.74,10.51 0,29.36V32H412z"/>
</vector>
diff --git a/res/layout/user_folder_icon_normalized.xml b/res/layout/user_folder_icon_normalized.xml
index a1033f0..893d796 100644
--- a/res/layout/user_folder_icon_normalized.xml
+++ b/res/layout/user_folder_icon_normalized.xml
@@ -41,7 +41,7 @@
android:paddingLeft="12dp"
android:paddingRight="12dp" >
- <com.android.launcher3.ExtendedEditText
+ <com.android.launcher3.folder.FolderNameEditText
android:id="@+id/folder_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
diff --git a/res/layout/work_profile_edu.xml b/res/layout/work_profile_edu.xml
new file mode 100644
index 0000000..a8e3d20
--- /dev/null
+++ b/res/layout/work_profile_edu.xml
@@ -0,0 +1,61 @@
+<?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.views.WorkEduView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom"
+ android:gravity="bottom"
+ android:orientation="vertical">
+
+ <View
+ android:layout_width="match_parent"
+ android:backgroundTint="@color/bottom_panel_background"
+ android:layout_height="32dp"
+ android:background="@drawable/bottom_sheet_top_border" />
+
+ <LinearLayout
+ android:id="@+id/view_wrapper"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@color/bottom_panel_background"
+ android:orientation="vertical"
+ android:paddingLeft="@dimen/bottom_sheet_edu_padding"
+ android:paddingRight="@dimen/bottom_sheet_edu_padding">
+
+ <TextView
+ android:id="@+id/content_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="48dp"
+ android:layout_marginBottom="48dp"
+ android:fontFamily="google-sans"
+ android:text="@string/work_profile_edu_personal_apps"
+ android:textAlignment="center"
+ android:textColor="@android:color/white"
+ android:textSize="20sp" />
+
+ <Button
+ android:id="@+id/proceed"
+ android:layout_width="wrap_content"
+ android:layout_height="48dp"
+ android:layout_gravity="end"
+ android:background="?android:attr/selectableItemBackground"
+ android:gravity="center"
+ android:text="@string/work_profile_edu_next"
+ android:textAlignment="center"
+ android:textColor="@android:color/white" />
+ </LinearLayout>
+
+</com.android.launcher3.views.WorkEduView>
\ No newline at end of file
diff --git a/res/layout/work_tab_bottom_user_education_view.xml b/res/layout/work_tab_bottom_user_education_view.xml
deleted file mode 100644
index ac2deeb..0000000
--- a/res/layout/work_tab_bottom_user_education_view.xml
+++ /dev/null
@@ -1,69 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- 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.
--->
-<com.android.launcher3.views.BottomUserEducationView
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_gravity="bottom"
- android:background="?android:attr/colorAccent"
- android:elevation="2dp"
- android:focusable="true"
- android:orientation="horizontal">
-
- <ImageView
- android:layout_width="134dp"
- android:layout_height="134dp"
- android:layout_marginTop="28dp"
- android:layout_marginLeft="20dp"
- android:src="@drawable/work_tab_user_education"/>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginStart="24dp"
- android:orientation="vertical">
-
- <ImageView
- android:id="@+id/close_bottom_user_tip"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_marginTop="12dp"
- android:layout_marginEnd="12dp"
- android:layout_gravity="right"
- android:contentDescription="@string/bottom_work_tab_user_education_close_button"
- android:src="@drawable/ic_remove_no_shadow"/>
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginTop="4dp"
- android:layout_marginEnd="24dp"
- android:fontFamily="roboto-medium"
- android:text="@string/bottom_work_tab_user_education_title"
- android:textColor="@android:color/white"
- android:textSize="20sp"/>
-
- <TextView
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginEnd="24dp"
- android:text="@string/bottom_work_tab_user_education_body"
- android:textColor="@android:color/white"
- android:textSize="14sp"/>
-
- </LinearLayout>
-
-</com.android.launcher3.views.BottomUserEducationView>
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 815ae21..36f8468 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -43,4 +43,7 @@
<color name="back_gesture_tutorial_title_color">#FF000000</color>
<color name="back_gesture_tutorial_action_button_label_color">#FFFFFFFF</color>
<color name="back_gesture_tutorial_primary_color">#1A73E8</color> <!-- Blue -->
+
+
+ <color name="bottom_panel_background">#f01A73E8</color>
</resources>
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/res/values/dimens.xml b/res/values/dimens.xml
index 4bcb8a7..0293573 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -235,4 +235,7 @@
<!-- Theming related -->
<dimen name="default_dialog_corner_radius">8dp</dimen>
+ <!-- Onboarding bottomsheet related -->
+ <dimen name="bottom_sheet_edu_padding">24dp</dimen>
+
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 218f6db..3f279f4 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -327,17 +327,20 @@
<!-- This string is in the work profile tab when a user has All Apps open on their phone. This is a label for a toggle to turn the work profile on and off. "Work profile" means a separate profile on a user's phone that's specifically for their work apps and managed by their company. "Work" is used as an adjective.-->
<string name="work_profile_toggle_label">Work profile</string>
- <!-- Title of an overlay in All Apps. This overlay is letting a user know about their work profile, which is managed by their employer. "Work apps" are apps in a user's work profile.-->
- <string name="bottom_work_tab_user_education_title">Find work apps here</string>
- <!-- Text in an overlay in All Apps. This overlay is letting a user know about their work profile, which is managed by their employer.-->
- <string name="bottom_work_tab_user_education_body">Each work app has a badge and is kept secure by your organization. Move apps to your Home screen for easier access.</string>
+ <!--- User onboarding title for personal apps -->
+ <string name="work_profile_edu_personal_apps">Personal apps are private & can\'t be seen by IT</string>
+ <!--- User onboarding title for work profile apps -->
+ <string name="work_profile_edu_work_apps">Work apps are badged and monitored by IT</string>
+ <!-- Action label to proceed to the next work profile edu section-->
+ <string name="work_profile_edu_next">Next</string>
+ <!-- Action label to finish work profile edu-->
+ <string name="work_profile_edu_accept">Got it</string>
+
<!-- This string is in the work profile tab when a user has All Apps open on their phone. It describes the label of a toggle, "Work profile," as being managed by the user's employer.
"Organization" is used to represent a variety of businesses, non-profits, and educational institutions).-->
<string name="work_mode_on_label">Managed by your organization</string>
<!-- This string appears under a the label of a toggle in the work profile tab on a user's phone. It describes the status of the toggle, "Work profile," when it's turned off. "Work profile" means a separate profile on a user's phone that's speficially for their work apps and is managed by their company.-->
<string name="work_mode_off_label">Notifications and apps are off</string>
- <string name="bottom_work_tab_user_education_close_button">Close</string>
- <string name="bottom_work_tab_user_education_closed">Closed</string>
<!-- Failed action error message: e.g. Failed: Pause -->
<string name="remote_action_failed">Failed: <xliff:g id="what" example="Pause">%1$s</xliff:g></string>
diff --git a/robolectric_tests/Android.mk b/robolectric_tests/Android.mk
index 86a6e8c..6059981 100644
--- a/robolectric_tests/Android.mk
+++ b/robolectric_tests/Android.mk
@@ -29,7 +29,7 @@
mockito-robolectric-prebuilt \
truth-prebuilt
LOCAL_JAVA_LIBRARIES := \
- platform-robolectric-4.3-prebuilt
+ platform-robolectric-4.3.1-prebuilt
LOCAL_JAVA_RESOURCE_DIRS := resources config
@@ -56,4 +56,4 @@
LOCAL_ROBOTEST_TIMEOUT := 36000
-include prebuilts/misc/common/robolectric/4.3/run_robotests.mk
+include prebuilts/misc/common/robolectric/4.3.1/run_robotests.mk
diff --git a/robolectric_tests/config/robolectric.properties b/robolectric_tests/config/robolectric.properties
index 932b01b..3d78689 100644
--- a/robolectric_tests/config/robolectric.properties
+++ b/robolectric_tests/config/robolectric.properties
@@ -1 +1 @@
-sdk=28
+sdk=29
diff --git a/robolectric_tests/src/com/android/launcher3/model/BackupRestoreTest.java b/robolectric_tests/src/com/android/launcher3/model/BackupRestoreTest.java
index 6223760..7072adf 100644
--- a/robolectric_tests/src/com/android/launcher3/model/BackupRestoreTest.java
+++ b/robolectric_tests/src/com/android/launcher3/model/BackupRestoreTest.java
@@ -16,14 +16,32 @@
package com.android.launcher3.model;
+import static android.content.pm.PackageManager.INSTALL_REASON_DEVICE_RESTORE;
+
import static com.android.launcher3.LauncherSettings.Favorites.BACKUP_TABLE_NAME;
+import static com.android.launcher3.LauncherSettings.Favorites.TABLE_NAME;
+import static com.android.launcher3.LauncherSettings.Favorites.addTableToDb;
+import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
import static com.android.launcher3.provider.LauncherDbUtils.tableExists;
+import static com.android.launcher3.util.LauncherModelHelper.APP_ICON;
+import static com.android.launcher3.util.LauncherModelHelper.NO__ICON;
+import static com.android.launcher3.util.LauncherModelHelper.SHORTCUT;
+import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
+import static org.robolectric.util.ReflectionHelpers.setField;
+import android.app.backup.BackupManager;
+import android.content.pm.PackageInstaller;
+import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
+import android.os.UserHandle;
+import android.os.UserManager;
+import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.provider.RestoreDbTask;
+import com.android.launcher3.shadows.LShadowBackupManager;
+import com.android.launcher3.shadows.LShadowUserManager;
import com.android.launcher3.util.LauncherModelHelper;
import com.android.launcher3.util.LauncherRoboTestRunner;
@@ -32,6 +50,7 @@
import org.junit.runner.RunWith;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.LooperMode;
+import org.robolectric.shadow.api.Shadow;
/**
* Tests to verify backup and restore flow.
@@ -40,18 +59,123 @@
@LooperMode(LooperMode.Mode.PAUSED)
public class BackupRestoreTest {
+ private static final long MY_OLD_PROFILE_ID = 1;
+ private static final long MY_PROFILE_ID = 0;
+ private static final long OLD_WORK_PROFILE_ID = 11;
+ private static final int WORK_PROFILE_ID = 10;
+
+ private static final int SYSTEM_USER = 0;
+ private static final int FLAG_SYSTEM = 0x00000800;
+ private static final int FLAG_PROFILE = 0x00001000;
+
+ private LShadowUserManager mUserManager;
+ private BackupManager mBackupManager;
private LauncherModelHelper mModelHelper;
private SQLiteDatabase mDb;
+ private InvariantDeviceProfile mIdp;
+ private UserHandle mMainProfileUser;
+ private UserHandle mWorkProfileUser;
@Before
public void setUp() {
+ setupUserManager();
+ setupBackupManager();
mModelHelper = new LauncherModelHelper();
RestoreDbTask.setPending(RuntimeEnvironment.application, true);
mDb = mModelHelper.provider.getDb();
+ mIdp = InvariantDeviceProfile.INSTANCE.get(RuntimeEnvironment.application);
+ }
+
+ private void setupUserManager() {
+ final UserManager userManager = RuntimeEnvironment.application.getSystemService(
+ UserManager.class);
+ mUserManager = Shadow.extract(userManager);
+ // sign in to primary user
+ mMainProfileUser = mUserManager.addUser(SYSTEM_USER, "me", FLAG_SYSTEM);
+ // sign in to work profile
+ mWorkProfileUser = mUserManager.addUser(WORK_PROFILE_ID, "work", FLAG_PROFILE);
+ }
+
+ private void setupBackupManager() {
+ mBackupManager = new BackupManager(RuntimeEnvironment.application);
+ final LShadowBackupManager bm = Shadow.extract(mBackupManager);
+ bm.addProfile(MY_OLD_PROFILE_ID, mMainProfileUser);
+ bm.addProfile(OLD_WORK_PROFILE_ID, mWorkProfileUser);
}
@Test
public void testOnCreateDbIfNotExists_CreatesBackup() {
assertTrue(tableExists(mDb, BACKUP_TABLE_NAME));
}
+
+ @Test
+ public void testOnRestoreSessionWithValidCondition_PerformsRestore() throws Exception {
+ setupBackup();
+ verifyTableIsFilled(BACKUP_TABLE_NAME, false);
+ verifyTableIsEmpty(TABLE_NAME);
+ createRestoreSession();
+ verifyTableIsFilled(TABLE_NAME, true);
+ }
+
+ private void setupBackup() {
+ createTableUsingOldProfileId();
+ // setup grid for main user on first screen
+ mModelHelper.createGrid(new int[][][]{{
+ { APP_ICON, APP_ICON, SHORTCUT, SHORTCUT},
+ { SHORTCUT, SHORTCUT, NO__ICON, NO__ICON},
+ { NO__ICON, NO__ICON, SHORTCUT, SHORTCUT},
+ { APP_ICON, SHORTCUT, SHORTCUT, APP_ICON},
+ }}, 1, MY_OLD_PROFILE_ID);
+ // setup grid for work profile on second screen
+ mModelHelper.createGrid(new int[][][]{{
+ { NO__ICON, APP_ICON, SHORTCUT, SHORTCUT},
+ { SHORTCUT, SHORTCUT, NO__ICON, NO__ICON},
+ { NO__ICON, NO__ICON, SHORTCUT, SHORTCUT},
+ { APP_ICON, SHORTCUT, SHORTCUT, NO__ICON},
+ }}, 2, OLD_WORK_PROFILE_ID);
+ // simulates the creation of backup upon restore
+ new GridBackupTable(RuntimeEnvironment.application, mDb, mIdp.numHotseatIcons,
+ mIdp.numColumns, mIdp.numRows).doBackup(
+ MY_OLD_PROFILE_ID, GridBackupTable.OPTION_REQUIRES_SANITIZATION);
+ // reset favorites table
+ createTableUsingOldProfileId();
+ }
+
+ private void verifyTableIsEmpty(String tableName) {
+ assertEquals(0, getCount(mDb, "SELECT * FROM " + tableName));
+ }
+
+ private void verifyTableIsFilled(String tableName, boolean sanitized) {
+ assertEquals(sanitized ? 12 : 13, getCount(mDb,
+ "SELECT * FROM " + tableName + " WHERE profileId = "
+ + (sanitized ? MY_PROFILE_ID : MY_OLD_PROFILE_ID)));
+ assertEquals(10, getCount(mDb, "SELECT * FROM " + tableName + " WHERE profileId = "
+ + (sanitized ? WORK_PROFILE_ID : OLD_WORK_PROFILE_ID)));
+ }
+
+ private void createTableUsingOldProfileId() {
+ // simulates the creation of favorites table on old device
+ dropTable(mDb, TABLE_NAME);
+ addTableToDb(mDb, MY_OLD_PROFILE_ID, false);
+ }
+
+ private void createRestoreSession() throws Exception {
+ final PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(
+ PackageInstaller.SessionParams.MODE_FULL_INSTALL);
+ final PackageInstaller installer = RuntimeEnvironment.application.getPackageManager()
+ .getPackageInstaller();
+ final int sessionId = installer.createSession(params);
+ final PackageInstaller.SessionInfo info = installer.getSessionInfo(sessionId);
+ setField(info, "installReason", INSTALL_REASON_DEVICE_RESTORE);
+ // TODO: (b/148410677) we should verify the following call instead
+ // InstallSessionHelper.INSTANCE.get(getContext()).restoreDbIfApplicable(info);
+ RestoreDbTask.restoreIfPossible(RuntimeEnvironment.application,
+ mModelHelper.provider.getHelper(), mBackupManager);
+ }
+
+ private static int getCount(SQLiteDatabase db, String sql) {
+ try (Cursor c = db.rawQuery(sql, null)) {
+ return c.getCount();
+ }
+ }
}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowBackupManager.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowBackupManager.java
new file mode 100644
index 0000000..eae0101
--- /dev/null
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowBackupManager.java
@@ -0,0 +1,45 @@
+/*
+ * 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.shadows;
+
+import android.app.backup.BackupManager;
+import android.os.UserHandle;
+import android.util.LongSparseArray;
+
+import androidx.annotation.Nullable;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowBackupManager;
+
+/**
+ * Extension of {@link ShadowBackupManager} with missing shadow methods
+ */
+@Implements(value = BackupManager.class)
+public class LShadowBackupManager extends ShadowBackupManager {
+
+ private LongSparseArray<UserHandle> mProfileMapping = new LongSparseArray<>();
+
+ public void addProfile(long userSerial, UserHandle userHandle) {
+ mProfileMapping.put(userSerial, userHandle);
+ }
+
+ @Implementation
+ @Nullable
+ public UserHandle getUserForAncestralSerialNumber(long ancestralSerialNumber) {
+ return mProfileMapping.get(ancestralSerialNumber);
+ }
+}
diff --git a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
index 166e28b..f16ed33 100644
--- a/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
+++ b/robolectric_tests/src/com/android/launcher3/shadows/LShadowLauncherApps.java
@@ -26,6 +26,7 @@
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherActivityInfo;
import android.content.pm.LauncherApps;
+import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ShortcutInfo;
@@ -43,6 +44,7 @@
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.Executor;
import java.util.stream.Collectors;
/**
@@ -111,6 +113,17 @@
return true;
}
+ @Implementation
+ public List<PackageInstaller.SessionInfo> getAllPackageInstallerSessions() {
+ return RuntimeEnvironment.application.getPackageManager().getPackageInstaller()
+ .getAllSessions();
+ }
+
+ @Implementation
+ public void registerPackageInstallerSessionCallback(
+ Executor executor, PackageInstaller.SessionCallback callback) {
+ }
+
@Override
protected List<LauncherActivityInfo> getShortcutConfigActivityList(String packageName,
UserHandle user) {
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
index e8b7157..e133cf2 100644
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/robolectric_tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.util;
+import static com.android.launcher3.LauncherSettings.Favorites.CONTENT_URI;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static org.mockito.Mockito.atLeast;
@@ -30,6 +31,7 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
+import android.os.Process;
import android.provider.Settings;
import com.android.launcher3.AppInfo;
@@ -42,6 +44,7 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.model.AllAppsList;
import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.pm.UserCache;
import org.mockito.ArgumentCaptor;
import org.robolectric.Robolectric;
@@ -80,15 +83,17 @@
private static final int DEFAULT_BITMAP_SIZE = 10;
private static final int DEFAULT_GRID_SIZE = 4;
-
private final HashMap<Class, HashMap<String, Field>> mFieldCache = new HashMap<>();
public final TestLauncherProvider provider;
+ private final long mDefaultProfileId;
private BgDataModel mDataModel;
private AllAppsList mAllAppsList;
public LauncherModelHelper() {
provider = Robolectric.setupContentProvider(TestLauncherProvider.class);
+ mDefaultProfileId = UserCache.INSTANCE.get(RuntimeEnvironment.application)
+ .getSerialNumberForUser(Process.myUserHandle());
ShadowContentResolver.registerProviderInternal(LauncherProvider.AUTHORITY, provider);
}
@@ -224,12 +229,16 @@
return item;
}
+ public int addItem(int type, int screen, int container, int x, int y) {
+ return addItem(type, screen, container, x, y, mDefaultProfileId);
+ }
+
/**
* Adds a dummy item in the DB.
* @param type {@link #APP_ICON} or {@link #SHORTCUT} or >= 2 for
* folder (where the type represents the number of items in the folder).
*/
- public int addItem(int type, int screen, int container, int x, int y) {
+ public int addItem(int type, int screen, int container, int x, int y, long profileId) {
Context context = RuntimeEnvironment.application;
int id = LauncherSettings.Settings.call(context.getContentResolver(),
LauncherSettings.Settings.METHOD_NEW_ITEM_ID)
@@ -243,6 +252,7 @@
values.put(LauncherSettings.Favorites.CELLY, y);
values.put(LauncherSettings.Favorites.SPANX, 1);
values.put(LauncherSettings.Favorites.SPANY, 1);
+ values.put(LauncherSettings.Favorites.PROFILE_ID, profileId);
if (type == APP_ICON || type == SHORTCUT) {
values.put(LauncherSettings.Favorites.ITEM_TYPE, type);
@@ -253,11 +263,11 @@
LauncherSettings.Favorites.ITEM_TYPE_FOLDER);
// Add folder items.
for (int i = 0; i < type; i++) {
- addItem(APP_ICON, 0, id, 0, 0);
+ addItem(APP_ICON, 0, id, 0, 0, profileId);
}
}
- context.getContentResolver().insert(LauncherSettings.Favorites.CONTENT_URI, values);
+ context.getContentResolver().insert(CONTENT_URI, values);
return id;
}
@@ -265,6 +275,15 @@
return createGrid(typeArray, 1);
}
+ public int[][][] createGrid(int[][][] typeArray, int startScreen) {
+ final Context context = RuntimeEnvironment.application;
+ LauncherSettings.Settings.call(context.getContentResolver(),
+ LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
+ LauncherSettings.Settings.call(context.getContentResolver(),
+ LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
+ return createGrid(typeArray, startScreen, mDefaultProfileId);
+ }
+
/**
* Initializes the DB with dummy elements to represent the provided grid structure.
* @param typeArray A 3d array of item types. {@see #addItem(int, long, long, int, int)} for
@@ -273,14 +292,9 @@
* @param startScreen First screen id from where the icons will be added.
* @return the same grid representation where each entry is the corresponding item id.
*/
- public int[][][] createGrid(int[][][] typeArray, int startScreen) {
+ public int[][][] createGrid(int[][][] typeArray, int startScreen, long profileId) {
Context context = RuntimeEnvironment.application;
- LauncherSettings.Settings.call(context.getContentResolver(),
- LauncherSettings.Settings.METHOD_CREATE_EMPTY_DB);
- LauncherSettings.Settings.call(context.getContentResolver(),
- LauncherSettings.Settings.METHOD_CLEAR_EMPTY_DB_FLAG);
int[][][] ids = new int[typeArray.length][][];
-
for (int i = 0; i < typeArray.length; i++) {
// Add screen to DB
int screenId = startScreen + i;
@@ -297,7 +311,8 @@
// Empty cell
ids[i][y][x] = -1;
} else {
- ids[i][y][x] = addItem(typeArray[i][y][x], screenId, DESKTOP, x, y);
+ ids[i][y][x] = addItem(
+ typeArray[i][y][x], screenId, DESKTOP, x, y, profileId);
}
}
}
@@ -357,5 +372,9 @@
createDbIfNotExists();
return mOpenHelper.getWritableDatabase();
}
+
+ public DatabaseHelper getHelper() {
+ return mOpenHelper;
+ }
}
}
diff --git a/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java b/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java
index b8fff9c..6277c66 100644
--- a/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java
+++ b/robolectric_tests/src/com/android/launcher3/util/LauncherRoboTestRunner.java
@@ -18,12 +18,13 @@
import static org.mockito.Mockito.mock;
import com.android.launcher3.shadows.LShadowAppWidgetManager;
+import com.android.launcher3.shadows.LShadowBackupManager;
import com.android.launcher3.shadows.LShadowBitmap;
import com.android.launcher3.shadows.LShadowLauncherApps;
import com.android.launcher3.shadows.LShadowUserManager;
+import com.android.launcher3.shadows.ShadowDeviceFlag;
import com.android.launcher3.shadows.ShadowLooperExecutor;
import com.android.launcher3.shadows.ShadowMainThreadInitializedObject;
-import com.android.launcher3.shadows.ShadowDeviceFlag;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import org.junit.runners.model.InitializationError;
@@ -47,7 +48,7 @@
LShadowUserManager.class,
LShadowLauncherApps.class,
LShadowBitmap.class,
-
+ LShadowBackupManager.class,
ShadowLooperExecutor.class,
ShadowMainThreadInitializedObject.class,
ShadowDeviceFlag.class,
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/ExtendedEditText.java b/src/com/android/launcher3/ExtendedEditText.java
index 5b453c3..d64967b 100644
--- a/src/com/android/launcher3/ExtendedEditText.java
+++ b/src/com/android/launcher3/ExtendedEditText.java
@@ -21,15 +21,11 @@
import android.view.DragEvent;
import android.view.KeyEvent;
import android.view.View;
-import android.view.inputmethod.CompletionInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
-import com.android.launcher3.folder.FolderNameProvider;
import com.android.launcher3.util.UiThreadHelper;
-import java.util.List;
-
/**
* The edit text that reports back when the back key has been pressed.
@@ -105,25 +101,6 @@
UiThreadHelper.hideKeyboardAsync(getContext(), getWindowToken());
}
- @Override
- public void onCommitCompletion(CompletionInfo text) {
- setText(text.getText());
- setSelection(text.getText().length());
- }
-
- /**
- * Currently only used for folder name suggestion.
- */
- public void displayCompletions(List<String> suggestList) {
- int cnt = Math.min(suggestList.size(), FolderNameProvider.SUGGEST_MAX);
- CompletionInfo[] cInfo = new CompletionInfo[cnt];
- for (int i = 0; i < cnt; i++) {
- cInfo[i] = new CompletionInfo(i, i, suggestList.get(i));
- }
- post(() -> getContext().getSystemService(InputMethodManager.class)
- .displayCompletions(this, cInfo));
- }
-
private boolean showSoftInput() {
return requestFocus() &&
getContext().getSystemService(InputMethodManager.class)
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 e2b7b68..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;
@@ -45,8 +46,12 @@
*/
public static final int FLAG_MULTI_PAGE_ANIMATION = 0x00000004;
+ public static final int FLAG_MANUAL_FOLDER_NAME = 0x00000008;
+
public int options;
+ public Intent suggestedFolderNames;
+
/**
* The apps and shortcuts
*/
@@ -140,4 +145,10 @@
writer.updateItemInDatabase(this);
}
}
+
+ @Override
+ protected String dumpProperties() {
+ return super.dumpProperties()
+ + " manuallyTypedTitle=" + hasOption(FLAG_MANUAL_FOLDER_NAME);
+ }
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 06f3453..fe987bc 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;
@@ -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;
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/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 af9a1b4..7f443b6 100644
--- a/src/com/android/launcher3/Utilities.java
+++ b/src/com/android/launcher3/Utilities.java
@@ -75,7 +75,6 @@
import com.android.launcher3.widget.PendingAddShortcutInfo;
import java.lang.reflect.Method;
-import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
@@ -382,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);
}
@@ -465,12 +464,14 @@
}
public static SharedPreferences getPrefs(Context context) {
- return context.getSharedPreferences(
+ // Use application context for shared preferences, so that we use a single cached instance
+ return context.getApplicationContext().getSharedPreferences(
LauncherFiles.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE);
}
public static SharedPreferences getDevicePrefs(Context context) {
- return context.getSharedPreferences(
+ // Use application context for shared preferences, so that we use a single cached instance
+ return context.getApplicationContext().getSharedPreferences(
LauncherFiles.DEVICE_PREFERENCES_KEY, Context.MODE_PRIVATE);
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 3278960..beaafda 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -39,7 +39,6 @@
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@@ -79,6 +78,7 @@
import com.android.launcher3.graphics.DragPreviewProvider;
import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.graphics.RotationMode;
+import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.pageindicators.WorkspacePageIndicator;
import com.android.launcher3.popup.PopupContainerWithArrow;
@@ -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,
@@ -2606,11 +2605,10 @@
int width = MeasureSpec.makeMeasureSpec(unScaledSize[0], MeasureSpec.EXACTLY);
int height = MeasureSpec.makeMeasureSpec(unScaledSize[1], MeasureSpec.EXACTLY);
- Bitmap b = Bitmap.createBitmap(unScaledSize[0], unScaledSize[1],
- Bitmap.Config.ARGB_8888);
layout.measure(width, height);
layout.layout(0, 0, unScaledSize[0], unScaledSize[1]);
- layout.draw(new Canvas(b));
+ Bitmap b = BitmapRenderer.createHardwareBitmap(
+ unScaledSize[0], unScaledSize[1], layout::draw);
layout.setVisibility(visibility);
return b;
}
@@ -3256,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 13b7b54..27668eb 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -49,6 +49,7 @@
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;
@@ -58,9 +59,9 @@
import com.android.launcher3.util.MultiValueAlpha;
import com.android.launcher3.util.MultiValueAlpha.AlphaProperty;
import com.android.launcher3.util.Themes;
-import com.android.launcher3.views.BottomUserEducationView;
import com.android.launcher3.views.RecyclerViewFastScroller;
import com.android.launcher3.views.SpringRelativeLayout;
+import com.android.launcher3.views.WorkEduView;
/**
* The all apps view container.
@@ -92,6 +93,8 @@
private boolean mUsingTabs;
private boolean mSearchModeWhileUsingTabs = false;
+ private LauncherStateManager.StateListener mWorkTabListener;
+
private RecyclerViewFastScroller mTouchHandler;
private final Point mFastScrollerOffset = new Point();
@@ -287,7 +290,8 @@
}
@Override
- public void onDropCompleted(View target, DragObject d, boolean success) { }
+ public void onDropCompleted(View target, DragObject d, boolean success) {
+ }
@Override
public void fillInLogContainerData(View v, ItemInfo info, Target target, Target targetParent) {
@@ -371,6 +375,7 @@
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;
@@ -416,10 +421,9 @@
.setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.MAIN));
findViewById(R.id.tab_work)
.setOnClickListener((View view) -> mViewPager.snapToPage(AdapterHolder.WORK));
-
- }
- if (pos == AdapterHolder.WORK) {
- BottomUserEducationView.showIfNeeded(mLauncher);
+ if (pos == AdapterHolder.WORK) {
+ WorkEduView.showWorkEduIfNeeded(mLauncher);
+ }
}
}
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/dragndrop/LivePreviewWidgetCell.java b/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
index e36f607..a9389bc 100644
--- a/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
+++ b/src/com/android/launcher3/dragndrop/LivePreviewWidgetCell.java
@@ -2,7 +2,6 @@
import android.content.Context;
import android.graphics.Bitmap;
-import android.graphics.Canvas;
import android.util.AttributeSet;
import android.view.View;
import android.widget.FrameLayout;
@@ -11,6 +10,7 @@
import com.android.launcher3.BaseActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAppWidgetProviderInfo;
+import com.android.launcher3.icons.BitmapRenderer;
import com.android.launcher3.widget.WidgetCell;
/**
@@ -88,11 +88,9 @@
bitmapHeight = viewHeight;
}
- Bitmap preview = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888);
- Canvas c = new Canvas(preview);
- c.scale(scale, scale);
- v.draw(c);
- c.setBitmap(null);
- return preview;
+ return BitmapRenderer.createSoftwareBitmap(bitmapWidth, bitmapHeight, c -> {
+ c.scale(scale, scale);
+ v.draw(c);
+ });
}
}
diff --git a/src/com/android/launcher3/folder/Folder.java b/src/com/android/launcher3/folder/Folder.java
index 844189f..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;
@@ -96,7 +97,7 @@
View.OnLongClickListener, DropTarget, FolderListener, TextView.OnEditorActionListener,
View.OnFocusChangeListener, DragListener, ExtendedEditText.OnBackKeyListener {
private static final String TAG = "Launcher.Folder";
-
+ private static final boolean DEBUG = false;
/**
* We avoid measuring {@link #mContent} with a 0 width or height, as this
* results in CellLayout being measured as UNSPECIFIED, which it does not support.
@@ -146,7 +147,7 @@
@Thunk FolderIcon mFolderIcon;
@Thunk FolderPagedView mContent;
- public ExtendedEditText mFolderName;
+ public FolderNameEditText mFolderName;
private PageIndicatorDots mPageIndicator;
protected View mFooter;
@@ -300,12 +301,12 @@
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));
+ mFolderName.setEnteredCompose(false);
}
}
mFolderName.setHint("");
@@ -318,7 +319,13 @@
// Convert to a string here to ensure that no other state associated with the text field
// gets saved.
String newTitle = mFolderName.getText().toString();
+ if (DEBUG) {
+ Log.d(TAG, "onBackKey newTitle=" + newTitle);
+ }
+
mInfo.title = newTitle;
+ mInfo.setOption(FolderInfo.FLAG_MANUAL_FOLDER_NAME, mFolderName.isEnteredCompose(),
+ mLauncher.getModelWriter());
mFolderIcon.onTitleChanged(newTitle);
mLauncher.getModelWriter().updateItemInDatabase(mInfo);
@@ -350,6 +357,10 @@
}
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (DEBUG) {
+ Log.d(TAG, "onEditorAction actionId=" + actionId + " key="
+ + (event != null ? event.getKeyCode() : "null event"));
+ }
if (actionId == EditorInfo.IME_ACTION_DONE) {
mFolderName.dispatchBackKey();
return true;
@@ -435,16 +446,19 @@
* Show suggested folder title.
*/
public void showSuggestedTitle(String[] suggestName) {
- if (FeatureFlags.FOLDER_NAME_SUGGEST.get()
- && TextUtils.isEmpty(mFolderName.getText().toString())) {
- 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));
+ }
}
}
}
@@ -552,9 +566,6 @@
openFolder.close(true);
}
- if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
- mLauncher.getFolderNameProvider().load(getContext());
- }
mContent.bindItems(items);
centerAboutIcon();
mItemsInvalidated = true;
@@ -1495,6 +1506,9 @@
return ContainerType.FOLDER;
}
+ /**
+ * Navigation bar back key or hardware input back key has been issued.
+ */
@Override
public boolean onBackPressed() {
if (isEditingName()) {
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
new file mode 100644
index 0000000..7e11b18
--- /dev/null
+++ b/src/com/android/launcher3/folder/FolderNameEditText.java
@@ -0,0 +1,124 @@
+/*
+ * 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.folder;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.inputmethod.CompletionInfo;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputConnectionWrapper;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.launcher3.ExtendedEditText;
+
+import java.util.List;
+
+/**
+ * Handles additional edit text functionality to better support folder name suggestion.
+ * First, makes suggestion to the InputMethodManager via {@link #displayCompletions(List)}
+ * Second, intercepts whether user accepted the suggestion or manually edited their
+ * folder names.
+ */
+public class FolderNameEditText extends ExtendedEditText {
+ private static final String TAG = "FolderNameEditText";
+ private static final boolean DEBUG = false;
+
+ private boolean mEnteredCompose = false;
+
+ public FolderNameEditText(Context context) {
+ super(context);
+ }
+
+ public FolderNameEditText(Context context, AttributeSet attrs) {
+ // ctor chaining breaks the touch handling
+ super(context, attrs);
+ }
+
+ public FolderNameEditText(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ InputConnection con = super.onCreateInputConnection(outAttrs);
+ FolderNameEditTextInputConnection connectionWrapper =
+ new FolderNameEditTextInputConnection(con, true);
+ 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];
+ for (int i = 0; i < cnt; i++) {
+ cInfo[i] = new CompletionInfo(i, i, suggestList.get(i));
+ }
+ post(() -> getContext().getSystemService(InputMethodManager.class)
+ .displayCompletions(this, cInfo));
+ }
+
+ /**
+ * Within 's', the 'count' characters beginning at 'start' have just replaced
+ * old text 'before'
+ */
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ String reason = "unknown";
+ if (start == 0 && count == 0 && before > 0) {
+ reason = "suggestion was rejected";
+ mEnteredCompose = true;
+ }
+ if (DEBUG) {
+ Log.d(TAG, "onTextChanged " + start + "," + before + "," + count
+ + ", " + reason);
+ }
+ }
+
+ @Override
+ public void onCommitCompletion(CompletionInfo text) {
+ setText(text.getText());
+ setSelection(text.getText().length());
+ mEnteredCompose = false;
+ }
+
+ protected void setEnteredCompose(boolean value) {
+ mEnteredCompose = value;
+ }
+
+ protected boolean isEnteredCompose() {
+ if (DEBUG) {
+ Log.d(TAG, "isEnteredCompose " + mEnteredCompose);
+ }
+ return mEnteredCompose;
+ }
+
+ private class FolderNameEditTextInputConnection extends InputConnectionWrapper {
+
+ FolderNameEditTextInputConnection(InputConnection target, boolean mutable) {
+ super(target, mutable);
+ }
+
+ @Override
+ public boolean setComposingText(CharSequence cs, int newCursorPos) {
+ mEnteredCompose = true;
+ return super.setComposingText(cs, newCursorPos);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/folder/FolderNameProvider.java b/src/com/android/launcher3/folder/FolderNameProvider.java
index 782b0e2..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) {
@@ -109,11 +115,14 @@
if (contains(candidatesOut, candidate)) {
return;
}
+
for (int i = 0; i < candidate.length(); i++) {
if (TextUtils.isEmpty(candidatesOut[i])) {
candidatesOut[i] = candidate;
+ return;
}
}
+ candidatesOut[candidate.length() - 1] = candidate;
}
private boolean contains(CharSequence[] list, CharSequence key) {
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/graphics/ShadowDrawable.java b/src/com/android/launcher3/graphics/ShadowDrawable.java
index f10b972..d8a7070 100644
--- a/src/com/android/launcher3/graphics/ShadowDrawable.java
+++ b/src/com/android/launcher3/graphics/ShadowDrawable.java
@@ -120,36 +120,35 @@
}
private void regenerateBitmapCache() {
- Bitmap bitmap = Bitmap.createBitmap(mState.mIntrinsicWidth, mState.mIntrinsicHeight,
- Bitmap.Config.ARGB_8888);
- Canvas canvas = new Canvas(bitmap);
-
// Call mutate, so that the pixel allocation by the underlying vector drawable is cleared.
Drawable d = mState.mChildState.newDrawable().mutate();
d.setBounds(mState.mShadowSize, mState.mShadowSize,
mState.mIntrinsicWidth - mState.mShadowSize,
mState.mIntrinsicHeight - mState.mShadowSize);
d.setTint(mState.mIsDark ? mState.mDarkTintColor : Color.WHITE);
- d.draw(canvas);
- // Do not draw shadow on dark theme
- if (!mState.mIsDark) {
+ if (mState.mIsDark) {
+ // Dark text do not have any shadow, but just the bitmap
+ mState.mLastDrawnBitmap = BitmapRenderer.createHardwareBitmap(
+ mState.mIntrinsicWidth, mState.mIntrinsicHeight, d::draw);
+ } else {
Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
paint.setMaskFilter(new BlurMaskFilter(mState.mShadowSize, BlurMaskFilter.Blur.NORMAL));
+
+ // Generate the shadow bitmap
int[] offset = new int[2];
- Bitmap shadow = bitmap.extractAlpha(paint, offset);
+ Bitmap shadow = BitmapRenderer.createSoftwareBitmap(
+ mState.mIntrinsicWidth, mState.mIntrinsicHeight, d::draw)
+ .extractAlpha(paint, offset);
paint.setMaskFilter(null);
paint.setColor(mState.mShadowColor);
- bitmap.eraseColor(Color.TRANSPARENT);
- canvas.drawBitmap(shadow, offset[0], offset[1], paint);
- d.draw(canvas);
+ mState.mLastDrawnBitmap = BitmapRenderer.createHardwareBitmap(
+ mState.mIntrinsicWidth, mState.mIntrinsicHeight, c -> {
+ c.drawBitmap(shadow, offset[0], offset[1], paint);
+ d.draw(c);
+ });
}
-
- if (BitmapRenderer.USE_HARDWARE_BITMAP) {
- bitmap = bitmap.copy(Bitmap.Config.HARDWARE, false);
- }
- mState.mLastDrawnBitmap = bitmap;
}
@Override
diff --git a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
index 8020f15..5a1dcab 100644
--- a/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
+++ b/src/com/android/launcher3/graphics/WorkspaceAndHotseatScrim.java
@@ -259,7 +259,7 @@
}
}
- public Bitmap createDitheredAlphaMask() {
+ private Bitmap createDitheredAlphaMask() {
DisplayMetrics dm = mLauncher.getResources().getDisplayMetrics();
int width = ResourceUtils.pxFromDp(ALPHA_MASK_WIDTH_DP, dm);
int gradientHeight = ResourceUtils.pxFromDp(ALPHA_MASK_HEIGHT_DP, dm);
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 571d41a..6faa22f 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;
@@ -256,6 +257,11 @@
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");
@@ -898,6 +904,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/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/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 01c207f..5c20050 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;
}
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/views/BottomUserEducationView.java b/src/com/android/launcher3/views/BottomUserEducationView.java
deleted file mode 100644
index bdc69af..0000000
--- a/src/com/android/launcher3/views/BottomUserEducationView.java
+++ /dev/null
@@ -1,149 +0,0 @@
-/*
- * 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.views;
-
-import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
-
-import android.animation.PropertyValuesHolder;
-import android.content.Context;
-import android.graphics.Rect;
-import android.util.AttributeSet;
-import android.view.LayoutInflater;
-import android.view.TouchDelegate;
-import android.view.View;
-import android.view.accessibility.AccessibilityEvent;
-
-import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.R;
-import com.android.launcher3.anim.Interpolators;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
-
-public class BottomUserEducationView extends AbstractSlideInView implements Insettable {
-
- private static final String KEY_SHOWED_BOTTOM_USER_EDUCATION = "showed_bottom_user_education";
-
- private static final int DEFAULT_CLOSE_DURATION = 200;
-
- private final Rect mInsets = new Rect();
-
- private View mCloseButton;
-
- public BottomUserEducationView(Context context, AttributeSet attr) {
- this(context, attr, 0);
- }
-
- public BottomUserEducationView(Context context, AttributeSet attrs,
- int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- mContent = this;
- }
-
- @Override
- protected void onFinishInflate() {
- super.onFinishInflate();
- mCloseButton = findViewById(R.id.close_bottom_user_tip);
- mCloseButton.setOnClickListener(view -> handleClose(true));
- }
-
- @Override
- protected void onLayout(boolean changed, int l, int t, int r, int b) {
- super.onLayout(changed, l, t, r, b);
- setTranslationShift(mTranslationShift);
- expandTouchAreaOfCloseButton();
- }
-
- @Override
- public void logActionCommand(int command) {
- // Since this is on-boarding popup, it is not a user controlled action.
- }
-
- @Override
- public int getLogContainerType() {
- return ContainerType.TIP;
- }
-
- @Override
- protected boolean isOfType(int type) {
- return (type & TYPE_ON_BOARD_POPUP) != 0;
- }
-
- @Override
- public void setInsets(Rect insets) {
- // Extend behind left, right, and bottom insets.
- int leftInset = insets.left - mInsets.left;
- int rightInset = insets.right - mInsets.right;
- int bottomInset = insets.bottom - mInsets.bottom;
- mInsets.set(insets);
- setPadding(getPaddingLeft() + leftInset, getPaddingTop(),
- getPaddingRight() + rightInset, getPaddingBottom() + bottomInset);
- }
-
- @Override
- protected void handleClose(boolean animate) {
- handleClose(animate, DEFAULT_CLOSE_DURATION);
- if (animate) {
- // We animate only when the user is visible, which is a proxy for an explicit
- // close action.
- mLauncher.getSharedPrefs().edit()
- .putBoolean(KEY_SHOWED_BOTTOM_USER_EDUCATION, true).apply();
- sendCustomAccessibilityEvent(
- BottomUserEducationView.this,
- AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
- getContext().getString(R.string.bottom_work_tab_user_education_closed));
- }
- }
-
- private void open(boolean animate) {
- if (mIsOpen || mOpenCloseAnimator.isRunning()) {
- return;
- }
- mIsOpen = true;
- if (animate) {
- mOpenCloseAnimator.setValues(
- PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
- mOpenCloseAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
- mOpenCloseAnimator.start();
- } else {
- setTranslationShift(TRANSLATION_SHIFT_OPENED);
- }
- }
-
- public static void showIfNeeded(Launcher launcher) {
- if (launcher.getSharedPrefs().getBoolean(KEY_SHOWED_BOTTOM_USER_EDUCATION, false)) {
- return;
- }
-
- LayoutInflater layoutInflater = LayoutInflater.from(launcher);
- BottomUserEducationView bottomUserEducationView =
- (BottomUserEducationView) layoutInflater.inflate(
- R.layout.work_tab_bottom_user_education_view, launcher.getDragLayer(),
- false);
- launcher.getDragLayer().addView(bottomUserEducationView);
- bottomUserEducationView.open(true);
- }
-
- private void expandTouchAreaOfCloseButton() {
- Rect hitRect = new Rect();
- mCloseButton.getHitRect(hitRect);
- hitRect.left -= mCloseButton.getWidth();
- hitRect.top -= mCloseButton.getHeight();
- hitRect.right += mCloseButton.getWidth();
- hitRect.bottom += mCloseButton.getHeight();
- View parent = (View) mCloseButton.getParent();
- parent.setTouchDelegate(new TouchDelegate(hitRect, mCloseButton));
- }
-}
diff --git a/src/com/android/launcher3/views/WorkEduView.java b/src/com/android/launcher3/views/WorkEduView.java
new file mode 100644
index 0000000..c3186f6
--- /dev/null
+++ b/src/com/android/launcher3/views/WorkEduView.java
@@ -0,0 +1,216 @@
+/*
+ * 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.views;
+
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.allapps.AllAppsPagedView;
+import com.android.launcher3.anim.AnimationSuccessListener;
+import com.android.launcher3.anim.Interpolators;
+import com.android.launcher3.userevent.nano.LauncherLogProto;
+
+/**
+ * On boarding flow for users right after setting up work profile
+ */
+public class WorkEduView extends AbstractSlideInView implements Insettable {
+
+ private static final int DEFAULT_CLOSE_DURATION = 200;
+ private static final String KEY_WORK_EDU_STEP = "showed_work_profile_edu";
+
+ private static final int WORK_EDU_NOT_STARTED = 0;
+ private static final int WORK_EDU_PERSONAL_APPS = 1;
+ private static final int WORK_EDU_WORK_APPS = 2;
+
+ private static LauncherStateManager.StateListener sStateListener;
+
+ private Rect mInsets = new Rect();
+ private View mViewWrapper;
+ private Button mProceedButton;
+ private TextView mContentText;
+ private AllAppsPagedView mAllAppsPagedView;
+ private int mNextWorkEduStep = WORK_EDU_PERSONAL_APPS;
+
+
+ public WorkEduView(Context context, AttributeSet attr) {
+ this(context, attr, 0);
+ }
+
+ public WorkEduView(Context context, AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mContent = this;
+ }
+
+ @Override
+ protected void handleClose(boolean animate) {
+ mLauncher.getSharedPrefs().edit().putInt(KEY_WORK_EDU_STEP, mNextWorkEduStep).apply();
+ handleClose(true, DEFAULT_CLOSE_DURATION);
+ }
+
+ @Override
+ public void logActionCommand(int command) {
+ // Since this is on-boarding popup, it is not a user controlled action.
+ }
+
+ @Override
+ public int getLogContainerType() {
+ return LauncherLogProto.ContainerType.TIP;
+ }
+
+ @Override
+ protected boolean isOfType(int type) {
+ return (type & TYPE_ON_BOARD_POPUP) != 0;
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mViewWrapper = findViewById(R.id.view_wrapper);
+ mProceedButton = findViewById(R.id.proceed);
+ mContentText = findViewById(R.id.content_text);
+
+ if (mLauncher.getAppsView().getContentView() instanceof AllAppsPagedView) {
+ mAllAppsPagedView = (AllAppsPagedView) mLauncher.getAppsView().getContentView();
+ }
+
+ mProceedButton.setOnClickListener(view -> {
+ if (mAllAppsPagedView != null) {
+ mAllAppsPagedView.snapToPage(AllAppsContainerView.AdapterHolder.WORK);
+ }
+ goToWorkTab(true);
+ });
+ }
+
+ private void goToWorkTab(boolean animate) {
+ mProceedButton.setText(R.string.work_profile_edu_accept);
+ if (animate) {
+ ObjectAnimator animator = ObjectAnimator.ofFloat(mContentText, ALPHA, 0);
+ animator.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ mContentText.setText(mLauncher.getString(R.string.work_profile_edu_work_apps));
+ ObjectAnimator.ofFloat(mContentText, ALPHA, 1).start();
+ }
+ });
+ animator.start();
+ } else {
+ mContentText.setText(mLauncher.getString(R.string.work_profile_edu_work_apps));
+ }
+ mNextWorkEduStep = WORK_EDU_WORK_APPS;
+ mProceedButton.setOnClickListener(v -> handleClose(true));
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ int leftInset = insets.left - mInsets.left;
+ int rightInset = insets.right - mInsets.right;
+ int bottomInset = insets.bottom - mInsets.bottom;
+ mInsets.set(insets);
+ setPadding(leftInset, getPaddingTop(), rightInset, 0);
+ mViewWrapper.setPaddingRelative(mViewWrapper.getPaddingStart(),
+ mViewWrapper.getPaddingTop(), mViewWrapper.getPaddingEnd(), bottomInset);
+ }
+
+ private void show() {
+ mLauncher.getDragLayer().addView(this);
+ animateOpen();
+ }
+
+ private void goToFirstPage() {
+ if (mAllAppsPagedView != null) {
+ mAllAppsPagedView.snapToPageImmediately(AllAppsContainerView.AdapterHolder.MAIN);
+ }
+ }
+
+ private void animateOpen() {
+ if (mIsOpen || mOpenCloseAnimator.isRunning()) {
+ return;
+ }
+ mIsOpen = true;
+ mOpenCloseAnimator.setValues(
+ PropertyValuesHolder.ofFloat(TRANSLATION_SHIFT, TRANSLATION_SHIFT_OPENED));
+ mOpenCloseAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
+ mOpenCloseAnimator.start();
+ }
+
+ /**
+ * Checks if user has not seen onboarding UI yet and shows it when user navigates to all apps
+ */
+ public static LauncherStateManager.StateListener showEduFlowIfNeeded(Launcher launcher,
+ @Nullable LauncherStateManager.StateListener oldListener) {
+ if (oldListener != null) {
+ launcher.getStateManager().removeStateListener(oldListener);
+ }
+ if (launcher.getSharedPrefs().getInt(KEY_WORK_EDU_STEP, WORK_EDU_NOT_STARTED)
+ != WORK_EDU_NOT_STARTED) {
+ return null;
+ }
+
+ LauncherStateManager.StateListener listener = new LauncherStateManager.StateListener() {
+ @Override
+ public void onStateTransitionStart(LauncherState toState) {
+
+ }
+
+ @Override
+ public void onStateTransitionComplete(LauncherState finalState) {
+ if (finalState != LauncherState.ALL_APPS) return;
+ LayoutInflater layoutInflater = LayoutInflater.from(launcher);
+ WorkEduView v = (WorkEduView) layoutInflater.inflate(
+ R.layout.work_profile_edu, launcher.getDragLayer(),
+ false);
+ v.show();
+ v.goToFirstPage();
+ launcher.getStateManager().removeStateListener(this);
+ }
+ };
+ launcher.getStateManager().addStateListener(listener);
+ return listener;
+ }
+
+ /**
+ * Shows work apps edu if user had dismissed full edu flow
+ */
+ public static void showWorkEduIfNeeded(Launcher launcher) {
+ if (launcher.getSharedPrefs().getInt(KEY_WORK_EDU_STEP, WORK_EDU_NOT_STARTED)
+ != WORK_EDU_PERSONAL_APPS) {
+ return;
+ }
+ LayoutInflater layoutInflater = LayoutInflater.from(launcher);
+ WorkEduView v = (WorkEduView) layoutInflater.inflate(
+ R.layout.work_profile_edu, launcher.getDragLayer(), false);
+ v.show();
+ v.goToWorkTab(false);
+ }
+}
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..25670e7 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.
@@ -281,9 +282,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 +431,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/ui/widget/AddConfigWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
index 62e2a53..de9757f 100644
--- a/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/AddConfigWidgetTest.java
@@ -36,8 +36,8 @@
import com.android.launcher3.testcomponent.WidgetConfigActivity;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.TestViewHelpers;
-import com.android.launcher3.util.Condition;
import com.android.launcher3.util.Wait;
+import com.android.launcher3.util.Wait.Condition;
import com.android.launcher3.util.rule.ShellCommandRule;
import org.junit.Before;
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index 59b861c..0246f95 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -41,8 +41,8 @@
import com.android.launcher3.testcomponent.AppWidgetWithConfig;
import com.android.launcher3.testcomponent.RequestPinItemActivity;
import com.android.launcher3.ui.AbstractLauncherUiTest;
-import com.android.launcher3.util.Condition;
import com.android.launcher3.util.Wait;
+import com.android.launcher3.util.Wait.Condition;
import com.android.launcher3.util.rule.ShellCommandRule;
import org.junit.Before;
diff --git a/tests/src/com/android/launcher3/util/Condition.java b/tests/src/com/android/launcher3/util/Condition.java
deleted file mode 100644
index d85dd3a..0000000
--- a/tests/src/com/android/launcher3/util/Condition.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package com.android.launcher3.util;
-
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
-
-import androidx.test.uiautomator.UiObject2;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-public interface Condition {
-
- boolean isTrue() throws Throwable;
-
- /**
- * Converts the condition to be run on UI thread.
- */
- static Condition runOnUiThread(final Condition condition) {
- final LooperExecutor executor = MAIN_EXECUTOR;
- return () -> {
- final AtomicBoolean value = new AtomicBoolean(false);
- final Throwable[] exceptions = new Throwable[1];
- final CountDownLatch latch = new CountDownLatch(1);
- executor.execute(() -> {
- try {
- value.set(condition.isTrue());
- } catch (Throwable e) {
- exceptions[0] = e;
- }
-
- });
- latch.await(1, TimeUnit.SECONDS);
- if (exceptions[0] != null) {
- throw exceptions[0];
- }
- return value.get();
- };
- }
-
- static Condition minChildCount(final UiObject2 obj, final int childCount) {
- return () -> obj.getChildCount() >= childCount;
- }
-}
diff --git a/tests/src/com/android/launcher3/util/Wait.java b/tests/src/com/android/launcher3/util/Wait.java
index 2ab1e00..fe6143c 100644
--- a/tests/src/com/android/launcher3/util/Wait.java
+++ b/tests/src/com/android/launcher3/util/Wait.java
@@ -55,4 +55,12 @@
launcher.checkForAnomaly();
Assert.fail(message.get());
}
+
+ /**
+ * Interface representing a generic condition
+ */
+ public interface Condition {
+
+ boolean isTrue() throws Throwable;
+ }
}
diff --git a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
index 831685a..c553b9b 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
@@ -29,6 +29,11 @@
}
static int getBugForFailure(CharSequence exception, String testsStartTime) {
+ if ("com.google.android.setupwizard".equals(
+ UiDevice.getInstance(getInstrumentation()).getLauncherPackageName())) {
+ return 145935261;
+ }
+
final String logSinceTestsStart;
try {
logSinceTestsStart =
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 0a3462f..5555eab 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -68,6 +68,7 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
+import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
@@ -93,10 +94,13 @@
private static final String TAG = "Tapl";
private static final int ZERO_BUTTON_STEPS_FROM_BACKGROUND_TO_HOME = 20;
private static final int GESTURE_STEP_MS = 16;
+ private static final SimpleDateFormat DATE_TIME_FORMAT =
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
private static long START_TIME = System.currentTimeMillis();
static final Pattern EVENT_LOG_ENTRY = Pattern.compile(
- "[0-9][0-9]-[0-9][0-9] [0-9][0-9]:[0-9][0-9]:[0-9][0-9]\\.[0-9][0-9][0-9]"
+ "(?<time>[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9] "
+ + "[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\\.[0-9][0-9][0-9])"
+ ".*" + TestProtocol.TAPL_EVENTS_TAG + ": (?<event>.*)");
private static final Pattern EVENT_TOUCH_DOWN = getTouchEventPattern("ACTION_DOWN");
@@ -167,7 +171,7 @@
// Not null when we are collecting expected events to compare with actual ones.
private List<Pattern> mExpectedEvents;
- private String mTimeBeforeFirstLogEvent;
+ private Date mStartRecordingTime;
private boolean mCheckEventsForSuccessfulGestures = false;
private static Pattern getTouchEventPattern(String action) {
@@ -1187,32 +1191,38 @@
private List<String> getEvents() {
final ArrayList<String> events = new ArrayList<>();
try {
- final String logcatTimeParameter =
- mTimeBeforeFirstLogEvent != null ? " -t " + mTimeBeforeFirstLogEvent : "";
final String logcatEvents = mDevice.executeShellCommand(
- "logcat -d --pid=" + getPid() + logcatTimeParameter
+ "logcat -d -v year --pid=" + getPid() + " -t "
+ + DATE_TIME_FORMAT.format(mStartRecordingTime).replaceAll(" ", "")
+ " -s " + TestProtocol.TAPL_EVENTS_TAG);
final Matcher matcher = EVENT_LOG_ENTRY.matcher(logcatEvents);
while (matcher.find()) {
+ // Skip events before recording start time.
+ if (DATE_TIME_FORMAT.parse(matcher.group("time"))
+ .compareTo(mStartRecordingTime) < 0) {
+ continue;
+ }
+
events.add(matcher.group("event"));
}
return events;
} catch (IOException e) {
throw new RuntimeException(e);
+ } catch (ParseException e) {
+ throw new AssertionError(e);
}
}
private void startRecordingEvents() {
Assert.assertTrue("Already recording events", mExpectedEvents == null);
mExpectedEvents = new ArrayList<>();
- mTimeBeforeFirstLogEvent = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS")
- .format(new Date())
- .replaceAll(" ", "");
- log("startRecordingEvents: " + mTimeBeforeFirstLogEvent);
+ mStartRecordingTime = new Date();
+ log("startRecordingEvents: " + DATE_TIME_FORMAT.format(mStartRecordingTime));
}
private void stopRecordingEvents() {
mExpectedEvents = null;
+ mStartRecordingTime = null;
}
Closable eventsCheck() {