Merge "Revert "Rotate quickswitch task thumbnails"" into ub-launcher3-master
diff --git a/Android.mk b/Android.mk
index 3a9a2c3..a099ada 100644
--- a/Android.mk
+++ b/Android.mk
@@ -78,9 +78,8 @@
LOCAL_USE_AAPT2 := true
LOCAL_MODULE_TAGS := optional
-LOCAL_STATIC_ANDROID_LIBRARIES := \
- Launcher3CommonDepsLib \
- SecondaryDisplayLauncherLib
+LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
+
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
$(call all-java-files-under, src_shortcuts_overrides) \
@@ -154,9 +153,7 @@
endif
LOCAL_MODULE := Launcher3QuickStepLib
LOCAL_PRIVILEGED_MODULE := true
-LOCAL_STATIC_ANDROID_LIBRARIES := \
- Launcher3CommonDepsLib \
- SecondaryDisplayLauncherLib
+LOCAL_STATIC_ANDROID_LIBRARIES := Launcher3CommonDepsLib
LOCAL_SRC_FILES := \
$(call all-java-files-under, src) \
diff --git a/AndroidManifest-common.xml b/AndroidManifest-common.xml
index 555cc73..26698eb 100644
--- a/AndroidManifest-common.xml
+++ b/AndroidManifest-common.xml
@@ -184,5 +184,20 @@
android:writePermission="android.permission.WRITE_SECURE_SETTINGS"
android:exported="true"
android:enabled="false" />
+
+ <!--
+ Launcher activity for secondary display
+ -->
+ <activity
+ android:name="com.android.launcher3.secondarydisplay.SecondaryDisplayLauncher"
+ android:theme="@style/AppTheme"
+ android:launchMode="singleTop"
+ android:enabled="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.SECONDARY_HOME" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/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..22f1f23 100644
--- a/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
+++ b/iconloaderlib/src/com/android/launcher3/icons/GraphicsUtils.java
@@ -21,15 +21,17 @@
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";
+ public static Runnable sOnNewBitmapRunnable = () -> { };
+
/**
* Set the alpha component of {@code color} to be {@code alpha}. Unlike the support lib version,
* it bounds the alpha in valid range instead of throwing an exception to allow for safer
@@ -73,4 +75,11 @@
}
return area;
}
+
+ /**
+ * Utility method to track new bitmap creation
+ */
+ public static void noteNewBitmapCreated() {
+ sOnNewBitmapRunnable.run();
+ }
}
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..d94c665 100644
--- a/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
+++ b/quickstep/recents_ui_overrides/res/layout/predicted_hotseat_edu.xml
@@ -24,21 +24,22 @@
<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
+ style="@style/TextHeadline"
android:layout_width="match_parent"
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/appprediction/PredictionUiStateManager.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
index 06b9f1f..632b9b5 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/appprediction/PredictionUiStateManager.java
@@ -317,8 +317,8 @@
&& itemInfo.itemType != LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT)) {
return;
}
- if (itemInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
- HotseatPredictionController.fillInHybridHotseatRank(itemInfo, target);
+ if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION) {
+ HotseatPredictionController.encodeHotseatLayoutIntoPredictionRank(itemInfo, target);
return;
}
@@ -327,7 +327,7 @@
IntStream.range(0, predictedApps.size())
.filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
.findFirst()
- .ifPresent((rank) -> target.predictedRank = rank);
+ .ifPresent((rank) -> target.predictedRank = 0 - rank);
}
public static class PredictionState {
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/hybridhotseat/HotseatPredictionController.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
index c2b55ab..cc6ec69 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java
@@ -39,6 +39,7 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
+import com.android.launcher3.FolderInfo;
import com.android.launcher3.Hotseat;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.ItemInfo;
@@ -68,6 +69,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.OptionalInt;
import java.util.stream.IntStream;
/**
@@ -442,6 +444,20 @@
mHotseat.invalidate();
}
+ /**
+ * Unpins pinned app when it's converted into a folder
+ */
+ public void folderCreatedFromIcon(ItemInfo info, FolderInfo folderInfo) {
+ AppTarget target = getAppTargetFromItemInfo(info);
+ if (folderInfo.container == LauncherSettings.Favorites.CONTAINER_HOTSEAT && !isInHotseat(
+ info)) {
+ notifyItemAction(target, APP_LOCATION_HOTSEAT, APPTARGET_ACTION_UNPIN);
+ } else if (folderInfo.container == LauncherSettings.Favorites.CONTAINER_DESKTOP
+ && folderInfo.screenId == Workspace.FIRST_SCREEN_ID && !isInFirstPage(info)) {
+ notifyItemAction(target, APP_LOCATION_WORKSPACE, APPTARGET_ACTION_UNPIN);
+ }
+ }
+
@Override
public void onDragEnd() {
if (mDragObject == null) {
@@ -548,12 +564,11 @@
* Fill in predicted_rank field based on app prediction.
* Only applicable when {@link ItemInfo#itemType} is PREDICTED_HOTSEAT
*/
- public static void fillInHybridHotseatRank(
+ public static void encodeHotseatLayoutIntoPredictionRank(
@NonNull ItemInfo itemInfo, @NonNull LauncherLogProto.Target target) {
QuickstepLauncher launcher = QuickstepLauncher.ACTIVITY_TRACKER.getCreatedActivity();
if (launcher == null || launcher.getHotseatPredictionController() == null
- || itemInfo.getTargetComponent() == null
- || itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
+ || itemInfo.getTargetComponent() == null) {
return;
}
HotseatPredictionController controller = launcher.getHotseatPredictionController();
@@ -561,11 +576,12 @@
final ComponentKey k = new ComponentKey(itemInfo.getTargetComponent(), itemInfo.user);
final List<ComponentKeyMapper> predictedApps = controller.mComponentKeyMappers;
- IntStream.range(0, predictedApps.size())
+ OptionalInt rank = IntStream.range(0, predictedApps.size())
.filter((i) -> k.equals(predictedApps.get(i).getComponentKey()))
- .findFirst()
- .ifPresent((rank) -> target.predictedRank =
- Integer.parseInt(controller.mPredictedSpotsCount + "0" + rank));
+ .findFirst();
+
+ target.predictedRank = 10000 + (controller.mPredictedSpotsCount * 100)
+ + (rank.isPresent() ? rank.getAsInt() + 1 : 0);
}
private static boolean isPredictedIcon(View view) {
diff --git a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
index bd89626..b2e1798 100644
--- a/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
+++ b/quickstep/recents_ui_overrides/src/com/android/launcher3/uioverrides/PredictedAppIcon.java
@@ -47,6 +47,7 @@
private static final float RING_EFFECT_RATIO = 0.11f;
+ boolean mIsDrawingDot = false;
private final DeviceProfile mDeviceProfile;
private final Paint mIconRingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private boolean mIsPinned = false;
@@ -81,6 +82,16 @@
}
@Override
+ protected void drawDotIfNecessary(Canvas canvas) {
+ mIsDrawingDot = true;
+ int count = canvas.save();
+ canvas.translate(-getWidth() * RING_EFFECT_RATIO, -getHeight() * RING_EFFECT_RATIO);
+ super.drawDotIfNecessary(canvas);
+ canvas.restoreToCount(count);
+ mIsDrawingDot = false;
+ }
+
+ @Override
public void applyFromWorkspaceItem(WorkspaceItemInfo info) {
super.applyFromWorkspaceItem(info);
int color = IconPalette.getMutedColor(info.bitmap.color, 0.54f);
@@ -112,7 +123,7 @@
@Override
public void getIconBounds(Rect outBounds) {
super.getIconBounds(outBounds);
- if (!mIsPinned) {
+ if (!mIsPinned && !mIsDrawingDot) {
int predictionInset = (int) (getIconSize() * RING_EFFECT_RATIO);
outBounds.inset(predictionInset, predictionInset);
}
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..b87fcf2 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
@@ -26,17 +26,21 @@
import android.view.Gravity;
import com.android.launcher3.BaseQuickstepLauncher;
+import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.hybridhotseat.HotseatPredictionController;
import com.android.launcher3.popup.SystemShortcut;
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;
@@ -168,6 +172,16 @@
}
@Override
+ public FolderIcon addFolder(CellLayout layout, WorkspaceItemInfo info, int container,
+ int screenId, int cellX, int cellY) {
+ FolderIcon fi = super.addFolder(layout, info, container, screenId, cellX, cellY);
+ if (mHotseatPredictionController != null) {
+ mHotseatPredictionController.folderCreatedFromIcon(info, fi.getFolder().getInfo());
+ }
+ return fi;
+ }
+
+ @Override
public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
if (mHotseatPredictionController != null) {
return Stream.concat(super.getSupportedShortcuts(),
@@ -224,7 +238,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..b80830a 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
@@ -43,6 +43,7 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppTransitionManagerImpl;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorSetBuilder;
import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action.Touch;
@@ -60,7 +61,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 +86,43 @@
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);
}
+
+ if (mAtomicAnim != null) {
+ mAtomicAnim.cancel();
+ }
+ }
+
+ 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 +169,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 +179,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 +191,36 @@
mMotionPauseDetector.clear();
}
+ protected void goToOverviewOnDragEnd(float velocity) {
+ if (mPeekAnim != null) {
+ mPeekAnim.cancel();
+ }
+
+ Animator overviewAnim = mLauncher.getAppTransitionManager().createStateElementAnimation(
+ INDEX_PAUSE_TO_OVERVIEW_ANIM);
+ mAtomicAnim = new AnimatorSet();
+ mAtomicAnim.addListener(new AnimationSuccessListener() {
+ @Override
+ public void onAnimationSuccess(Animator animator) {
+ onSwipeInteractionCompleted(OVERVIEW, Touch.SWIPE);
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ super.onAnimationEnd(animation);
+ if (mCancelled) {
+ mPeekAnim = mLauncher.getStateManager().createAtomicAnimation(mFromState,
+ mToState, new AnimatorSetBuilder(), ATOMIC_OVERVIEW_PEEK_COMPONENT,
+ PEEK_OUT_ANIM_DURATION);
+ mPeekAnim.start();
+ }
+ mAtomicAnim = null;
+ }
+ });
+ mAtomicAnim.play(overviewAnim);
+ mAtomicAnim.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/LauncherSwipeHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
index 8b5283e..1b60404 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/LauncherSwipeHandler.java
@@ -21,7 +21,7 @@
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.Interpolators.OVERSHOOT_1_2;
import static com.android.launcher3.config.FeatureFlags.ENABLE_QUICKSTEP_LIVE_TILE;
-import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import static com.android.launcher3.util.SystemUiController.UI_STATE_OVERVIEW;
import static com.android.quickstep.GestureState.GestureEndTarget.HOME;
@@ -973,7 +973,7 @@
}
mLauncherTransitionController.getAnimationPlayer().setDuration(Math.max(0, duration));
- if (UNSTABLE_SPRINGS.get()) {
+ if (QUICKSTEP_SPRINGS.get()) {
mLauncherTransitionController.dispatchOnStartWithVelocity(end, velocityPxPerMs.y);
}
mLauncherTransitionController.getAnimationPlayer().start();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 92c55da..3e6def3 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -111,6 +111,11 @@
@Override
protected boolean isLauncherInitialized() {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
+ "isLauncherInitialized.TouchInteractionService.isInitialized=" +
+ TouchInteractionService.isInitialized());
+ }
return super.isLauncherInitialized() && TouchInteractionService.isInitialized();
}
}
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
index 71568b3..edaef30 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/TouchInteractionService.java
@@ -61,6 +61,7 @@
import com.android.launcher3.logging.UserEventDispatcher;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.provider.RestoreDbTask;
+import com.android.launcher3.testing.TestLogging;
import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.TraceHelper;
@@ -135,12 +136,16 @@
TouchInteractionService.this.initInputMonitor();
preloadOverview(true /* fromInit */);
});
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "TIS initialized");
+ }
sIsInitialized = true;
}
@BinderThread
@Override
public void onOverviewToggle() {
+ TestLogging.recordEvent("onOverviewToggle");
mOverviewCommandHelper.onOverviewToggle();
}
@@ -392,6 +397,9 @@
@Override
public void onDestroy() {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "TIS destroyed");
+ }
sIsInitialized = false;
if (mDeviceState.isUserUnlocked()) {
mInputConsumer.unregisterInputConsumer();
diff --git a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
index 4a39e73..9e29238 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/util/AppWindowAnimationHelper.java
@@ -224,7 +224,6 @@
surfaceParams[i] = new SurfaceParams(app.leash, alpha, mTmpMatrix, crop, layer,
cornerRadius / scale);
}
- applySurfaceParams(params.syncTransactionApplier, surfaceParams);
return surfaceParams;
}
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/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
index 58e36bc..5954b86 100644
--- a/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
+++ b/quickstep/recents_ui_overrides/src/com/android/quickstep/views/TaskView.java
@@ -243,6 +243,7 @@
getResources().getDimensionPixelSize(R.dimen.overview_actions_height),
Gravity.BOTTOM);
addView(mActionsView, params);
+ mActionsView.setAlpha(0);
}
}
}
@@ -447,7 +448,8 @@
mIconView.setScaleX(scale);
mIconView.setScaleY(scale);
- if (mActionsView != null) {
+
+ if (mActionsView != null && isRunningTask()) {
mActionsView.setAlpha(scale);
}
@@ -523,9 +525,11 @@
public void onPageScroll(ScrollState scrollState) {
float curveInterpolation =
CURVE_INTERPOLATOR.getInterpolation(scrollState.linearInterpolation);
+ float curveScaleForCurveInterpolation = getCurveScaleForCurveInterpolation(
+ curveInterpolation);
mSnapshotView.setDimAlpha(curveInterpolation * MAX_PAGE_SCRIM_ALPHA);
- setCurveScale(getCurveScaleForCurveInterpolation(curveInterpolation));
+ setCurveScale(curveScaleForCurveInterpolation);
mFooterAlpha = Utilities.boundToRange(1.0f - 2 * scrollState.linearInterpolation, 0f, 1f);
for (FooterWrapper footer : mFooters) {
@@ -539,6 +543,28 @@
mMenuView.setScaleX(getScaleX());
mMenuView.setScaleY(getScaleY());
}
+
+ // This is not the proper implementation and will be replaced with a proper layout.
+ if (mActionsView != null) {
+ if (mFocusTransitionProgress == 1f) {
+ mActionsView.setAlpha(1 - curveInterpolation / MAX_PAGE_SCRIM_ALPHA);
+ }
+ maintainActionViewPosition(curveScaleForCurveInterpolation);
+ }
+
+ }
+
+ private void maintainActionViewPosition(float curveScaleForCurveInterpolation) {
+ float inverseCurveScaleFactor = curveScaleForCurveInterpolation == 0 ? 0 :
+ (1f / curveScaleForCurveInterpolation);
+ mActionsView.setScaleX(inverseCurveScaleFactor);
+ mActionsView.setScaleY(inverseCurveScaleFactor);
+ mActionsView.setTranslationX(inverseCurveScaleFactor * (-getX()
+ + getRecentsView().getScrollX() + getRecentsView().scrollOffsetLeft()));
+ mActionsView.setTranslationY(
+ (1f - curveScaleForCurveInterpolation) * (mSnapshotView.getHeight()
+ + mActionsView.getHeight()) / 2f
+ + inverseCurveScaleFactor * (-getTranslationY()));
}
diff --git a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
index 96ac489..31c1acf 100644
--- a/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
+++ b/quickstep/src/com/android/launcher3/LauncherAnimationRunner.java
@@ -55,7 +55,10 @@
RemoteAnimationTargetCompat[] wallpaperTargets, Runnable runnable) {
Runnable r = () -> {
finishExistingAnimation();
- mAnimationResult = new AnimationResult(runnable);
+ mAnimationResult = new AnimationResult(() -> {
+ runnable.run();
+ mAnimationResult = null;
+ });
onCreateAnimation(appTargets, wallpaperTargets, mAnimationResult);
};
if (mStartAtFrontOfQueue) {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
index d5ce734..99b2a81 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/touchcontrollers/PortraitStatesTouchController.java
@@ -25,7 +25,7 @@
import static com.android.launcher3.anim.Interpolators.ACCEL;
import static com.android.launcher3.anim.Interpolators.DEACCEL;
import static com.android.launcher3.anim.Interpolators.LINEAR;
-import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
import android.animation.TimeInterpolator;
@@ -277,7 +277,7 @@
private void handleFirstSwipeToOverview(final ValueAnimator animator,
final long expectedDuration, final LauncherState targetState, final float velocity,
final boolean isFling) {
- if (UNSTABLE_SPRINGS.get() && mFromState == OVERVIEW && mToState == ALL_APPS
+ if (QUICKSTEP_SPRINGS.get() && mFromState == OVERVIEW && mToState == ALL_APPS
&& targetState == OVERVIEW) {
mFinishFastOnSecondTouch = true;
} else if (mFromState == NORMAL && mToState == OVERVIEW && targetState == OVERVIEW) {
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/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/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index bd2c539..a726052 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -147,7 +147,7 @@
@Test
@Ignore // b/143488140
public void goToOverviewFromApp() {
- startAppFastAndWaitForRecentTask(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+ startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
mLauncher.getBackground().switchToOverview();
}
@@ -182,8 +182,8 @@
@Test
@Ignore // b/143488140
public void testOverview() {
- startAppFastAndWaitForRecentTask(getAppPackageName());
- startAppFastAndWaitForRecentTask(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+ startAppFast(getAppPackageName());
+ startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
startTestActivity(2);
Wait.atMost("Expected three apps in the task list",
() -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
@@ -236,25 +236,4 @@
private int getTaskCount(RecentsActivity recents) {
return recents.<RecentsView>getOverviewPanel().getTaskViewCount();
}
-
- /**
- * Workaround for b/141580748, there was an issue where the recent task is only updated when the
- * activity starting the task is resumed. In this case, we should wait until the task is in
- * the recents task list before continuing.
- */
- private void startAppFastAndWaitForRecentTask(String packageName) {
- startAppFast(packageName);
- Wait.atMost("Expected app in task list",
- () -> containsRecentTaskWithPackage(packageName), DEFAULT_ACTIVITY_TIMEOUT,
- mLauncher);
- }
-
- private boolean containsRecentTaskWithPackage(String packageName) {
- for (ComponentName cn : mLauncher.getRecentTasks()) {
- if (cn.getPackageName().equals(packageName)) {
- return true;
- }
- }
- return false;
- }
}
diff --git a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
index b786c8b..97424bb 100644
--- a/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
+++ b/quickstep/tests/src/com/android/quickstep/NavigationModeSwitchRule.java
@@ -73,6 +73,9 @@
private final LauncherInstrumentation mLauncher;
+ static final SysUINavigationMode SYS_UI_NAVIGATION_MODE =
+ SysUINavigationMode.INSTANCE.get(getInstrumentation().getTargetContext());
+
public NavigationModeSwitchRule(LauncherInstrumentation launcher) {
mLauncher = launcher;
}
@@ -83,32 +86,13 @@
description.getAnnotation(NavigationModeSwitch.class) != null) {
Mode mode = description.getAnnotation(NavigationModeSwitch.class).mode();
return new Statement() {
- private void assertTrue(String message, boolean condition) {
- if (mLauncher.getDevice().hasObject(By.textStartsWith(""))) {
- // The condition above is "screen is not empty". We are not treating
- // "Screen is empty" as an anomaly here. It's an acceptable state when
- // Launcher just starts under instrumentation.
- mLauncher.checkForAnomaly();
- }
- if (!condition) {
- final AssertionError assertionError = new AssertionError(message);
- FailureWatcher.onError(mLauncher.getDevice(), description, assertionError);
- throw assertionError;
- }
- }
-
@Override
public void evaluate() throws Throwable {
mLauncher.enableDebugTracing();
final Context context = getInstrumentation().getContext();
final int currentInteractionMode =
LauncherInstrumentation.getCurrentInteractionMode(context);
- final String prevOverlayPkg =
- QuickStepContract.isGesturalMode(currentInteractionMode)
- ? NAV_BAR_MODE_GESTURAL_OVERLAY
- : QuickStepContract.isSwipeUpMode(currentInteractionMode)
- ? NAV_BAR_MODE_2BUTTON_OVERLAY
- : NAV_BAR_MODE_3BUTTON_OVERLAY;
+ final String prevOverlayPkg = getCurrentOverlayPackage(currentInteractionMode);
final LauncherInstrumentation.NavigationModel originalMode =
mLauncher.getNavigationModel();
try {
@@ -125,104 +109,47 @@
Log.e(TAG, "Error", e);
throw e;
} finally {
- assertTrue("Couldn't set overlay",
- setActiveOverlay(prevOverlayPkg, originalMode));
+ Log.d(TAG, "In Finally block");
+ assertTrue(mLauncher, "Couldn't set overlay",
+ setActiveOverlay(mLauncher, prevOverlayPkg, originalMode,
+ description), description);
}
}
private void evaluateWithThreeButtons() throws Throwable {
- if (setActiveOverlay(NAV_BAR_MODE_3BUTTON_OVERLAY,
- LauncherInstrumentation.NavigationModel.THREE_BUTTON)) {
+ if (setActiveOverlay(mLauncher, NAV_BAR_MODE_3BUTTON_OVERLAY,
+ LauncherInstrumentation.NavigationModel.THREE_BUTTON, description)) {
base.evaluate();
}
}
private void evaluateWithTwoButtons() throws Throwable {
- if (setActiveOverlay(NAV_BAR_MODE_2BUTTON_OVERLAY,
- LauncherInstrumentation.NavigationModel.TWO_BUTTON)) {
+ if (setActiveOverlay(mLauncher, NAV_BAR_MODE_2BUTTON_OVERLAY,
+ LauncherInstrumentation.NavigationModel.TWO_BUTTON, description)) {
base.evaluate();
}
}
private void evaluateWithZeroButtons() throws Throwable {
- if (setActiveOverlay(NAV_BAR_MODE_GESTURAL_OVERLAY,
- LauncherInstrumentation.NavigationModel.ZERO_BUTTON)) {
+ if (setActiveOverlay(mLauncher, NAV_BAR_MODE_GESTURAL_OVERLAY,
+ LauncherInstrumentation.NavigationModel.ZERO_BUTTON, description)) {
base.evaluate();
}
}
-
- private boolean packageExists(String packageName) {
- try {
- PackageManager pm = getInstrumentation().getContext().getPackageManager();
- if (pm.getApplicationInfo(packageName, 0 /* flags */) == null) {
- return false;
- }
- } catch (PackageManager.NameNotFoundException e) {
- return false;
- }
- return true;
- }
-
- private boolean setActiveOverlay(String overlayPackage,
- LauncherInstrumentation.NavigationModel expectedMode) throws Exception {
- if (!packageExists(overlayPackage)) {
- Log.d(TAG, "setActiveOverlay: " + overlayPackage + " pkg does not exist");
- return false;
- }
-
- setOverlayPackageEnabled(NAV_BAR_MODE_3BUTTON_OVERLAY,
- overlayPackage == NAV_BAR_MODE_3BUTTON_OVERLAY);
- setOverlayPackageEnabled(NAV_BAR_MODE_2BUTTON_OVERLAY,
- overlayPackage == NAV_BAR_MODE_2BUTTON_OVERLAY);
- setOverlayPackageEnabled(NAV_BAR_MODE_GESTURAL_OVERLAY,
- overlayPackage == NAV_BAR_MODE_GESTURAL_OVERLAY);
-
- if (currentSysUiNavigationMode() != expectedMode) {
- final CountDownLatch latch = new CountDownLatch(1);
- final Context targetContext = getInstrumentation().getTargetContext();
- final SysUINavigationMode.NavigationModeChangeListener listener =
- newMode -> {
- if (LauncherInstrumentation.getNavigationModel(newMode.resValue)
- == expectedMode) {
- latch.countDown();
- }
- };
- final SysUINavigationMode sysUINavigationMode =
- SysUINavigationMode.INSTANCE.get(targetContext);
- targetContext.getMainExecutor().execute(() ->
- sysUINavigationMode.addModeChangeListener(listener));
- latch.await(60, TimeUnit.SECONDS);
- targetContext.getMainExecutor().execute(() ->
- sysUINavigationMode.removeModeChangeListener(listener));
- assertTrue("Navigation mode didn't change to " + expectedMode,
- currentSysUiNavigationMode() == expectedMode);
- }
-
- Wait.atMost("Couldn't switch to " + overlayPackage,
- () -> mLauncher.getNavigationModel() == expectedMode, WAIT_TIME_MS,
- mLauncher);
-
- Wait.atMost(() -> "Switching nav mode: "
- + mLauncher.getNavigationModeMismatchError(),
- () -> mLauncher.getNavigationModeMismatchError() == null, WAIT_TIME_MS,
- mLauncher);
-
- return true;
- }
-
- private void setOverlayPackageEnabled(String overlayPackage, boolean enable)
- throws Exception {
- Log.d(TAG, "setOverlayPackageEnabled: " + overlayPackage + " " + enable);
- final String action = enable ? "enable" : "disable";
- UiDevice.getInstance(getInstrumentation()).executeShellCommand(
- "cmd overlay " + action + " " + overlayPackage);
- }
};
} else {
return base;
}
}
+ public static String getCurrentOverlayPackage(int currentInteractionMode) {
+ return QuickStepContract.isGesturalMode(currentInteractionMode)
+ ? NAV_BAR_MODE_GESTURAL_OVERLAY
+ : QuickStepContract.isSwipeUpMode(currentInteractionMode)
+ ? NAV_BAR_MODE_2BUTTON_OVERLAY
+ : NAV_BAR_MODE_3BUTTON_OVERLAY;
+ }
+
private static LauncherInstrumentation.NavigationModel currentSysUiNavigationMode() {
return LauncherInstrumentation.getNavigationModel(
SysUINavigationMode.getMode(
@@ -230,4 +157,85 @@
getTargetContext()).
resValue);
}
+
+ public static boolean setActiveOverlay(LauncherInstrumentation launcher, String overlayPackage,
+ LauncherInstrumentation.NavigationModel expectedMode, Description description)
+ throws Exception {
+ if (!packageExists(overlayPackage)) {
+ Log.d(TAG, "setActiveOverlay: " + overlayPackage + " pkg does not exist");
+ return false;
+ }
+
+ setOverlayPackageEnabled(NAV_BAR_MODE_3BUTTON_OVERLAY,
+ overlayPackage == NAV_BAR_MODE_3BUTTON_OVERLAY);
+ setOverlayPackageEnabled(NAV_BAR_MODE_2BUTTON_OVERLAY,
+ overlayPackage == NAV_BAR_MODE_2BUTTON_OVERLAY);
+ setOverlayPackageEnabled(NAV_BAR_MODE_GESTURAL_OVERLAY,
+ overlayPackage == NAV_BAR_MODE_GESTURAL_OVERLAY);
+
+ if (currentSysUiNavigationMode() != expectedMode) {
+ final CountDownLatch latch = new CountDownLatch(1);
+ final Context targetContext = getInstrumentation().getTargetContext();
+ final SysUINavigationMode.NavigationModeChangeListener listener =
+ newMode -> {
+ if (LauncherInstrumentation.getNavigationModel(newMode.resValue)
+ == expectedMode) {
+ latch.countDown();
+ }
+ };
+ targetContext.getMainExecutor().execute(() ->
+ SYS_UI_NAVIGATION_MODE.addModeChangeListener(listener));
+ latch.await(60, TimeUnit.SECONDS);
+ targetContext.getMainExecutor().execute(() ->
+ SYS_UI_NAVIGATION_MODE.removeModeChangeListener(listener));
+ assertTrue(launcher, "Navigation mode didn't change to " + expectedMode,
+ currentSysUiNavigationMode() == expectedMode, description);
+ }
+
+ Wait.atMost("Couldn't switch to " + overlayPackage,
+ () -> launcher.getNavigationModel() == expectedMode, WAIT_TIME_MS, launcher);
+
+ Wait.atMost(() -> "Switching nav mode: "
+ + launcher.getNavigationModeMismatchError(),
+ () -> launcher.getNavigationModeMismatchError() == null, WAIT_TIME_MS, launcher);
+
+ return true;
+ }
+
+ private static void setOverlayPackageEnabled(String overlayPackage, boolean enable)
+ throws Exception {
+ Log.d(TAG, "setOverlayPackageEnabled: " + overlayPackage + " " + enable);
+ final String action = enable ? "enable" : "disable";
+ UiDevice.getInstance(getInstrumentation()).executeShellCommand(
+ "cmd overlay " + action + " " + overlayPackage);
+ }
+
+ private static boolean packageExists(String packageName) {
+ try {
+ PackageManager pm = getInstrumentation().getContext().getPackageManager();
+ if (pm.getApplicationInfo(packageName, 0 /* flags */) == null) {
+ return false;
+ }
+ } catch (PackageManager.NameNotFoundException e) {
+ return false;
+ }
+ return true;
+ }
+
+ private static void assertTrue(LauncherInstrumentation launcher, String message,
+ boolean condition, Description description) {
+ if (launcher.getDevice().hasObject(By.textStartsWith(""))) {
+ // The condition above is "screen is not empty". We are not treating
+ // "Screen is empty" as an anomaly here. It's an acceptable state when
+ // Launcher just starts under instrumentation.
+ launcher.checkForAnomaly();
+ }
+ if (!condition) {
+ final AssertionError assertionError = new AssertionError(message);
+ if (description != null) {
+ FailureWatcher.onError(launcher.getDevice(), description, assertionError);
+ }
+ throw assertionError;
+ }
+ }
}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 71d77fc..d2f5d8f 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -17,6 +17,8 @@
package com.android.quickstep;
import static com.android.launcher3.ui.TaplTestsLauncher3.getAppPackageName;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.UNBUNDLED_POSTSUBMIT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -40,6 +42,7 @@
import com.android.launcher3.tapl.OverviewTask;
import com.android.launcher3.tapl.TestHelpers;
import com.android.launcher3.ui.TaplTestsLauncher3;
+import com.android.launcher3.util.rule.TestStabilityRule;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import com.android.quickstep.views.RecentsView;
@@ -56,6 +59,7 @@
@Before
public void setUp() throws Exception {
+ mLauncherPid = 0;
super.setUp();
TaplTestsLauncher3.initialize(this);
mLauncherPid = mLauncher.getPid();
@@ -63,7 +67,9 @@
@After
public void teardown() {
- assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
+ if (mLauncherPid != 0) {
+ assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
+ }
}
private void startTestApps() throws Exception {
@@ -82,7 +88,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());
}
@@ -93,7 +100,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
@@ -112,7 +120,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));
@@ -121,14 +129,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));
@@ -147,7 +157,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);
@@ -162,29 +172,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)));
@@ -202,7 +212,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);
}
@@ -214,7 +225,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
@@ -226,7 +237,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() {
@@ -249,16 +260,21 @@
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());
}
@Test
@NavigationModeSwitch
@PortraitLandscape
- @Ignore // b/143285809
+ // b/143285809 Remove @Stability on 02/21/20 if the test doesn't flake.
+ @TestStabilityRule.Stability(flavors = LOCAL | UNBUNDLED_POSTSUBMIT)
+ // b/143285809
+ @Ignore
public void testQuickSwitchFromApp() throws Exception {
startTestActivity(2);
startTestActivity(3);
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/res/drawable/bg_all_apps_button.xml b/res/drawable/bg_all_apps_button.xml
new file mode 100644
index 0000000..169a468
--- /dev/null
+++ b/res/drawable/bg_all_apps_button.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+**
+** Copyright 2020, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+<ripple xmlns:android="http://schemas.android.com/apk/res/android"
+ android:color="?android:attr/colorControlHighlight">
+ <item android:id="@android:id/mask">
+ <shape android:shape="oval" >
+ <solid android:color="#ffffffff" />
+ </shape>
+ </item>
+
+ <item>
+ <shape android:shape="oval" >
+ <solid android:color="?android:attr/colorAccent" />
+ </shape>
+ </item>
+</ripple>
diff --git a/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/drawable/ic_apps.xml b/res/drawable/ic_apps.xml
new file mode 100644
index 0000000..db779c2
--- /dev/null
+++ b/res/drawable/ic_apps.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<vector android:height="24dp" android:tint="#FFFFFF"
+ android:viewportHeight="24.0" android:viewportWidth="24.0"
+ android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#FF000000" android:pathData="M4,8h4L8,4L4,4v4zM10,20h4v-4h-4v4zM4,20h4v-4L4,16v4zM4,14h4v-4L4,10v4zM10,14h4v-4h-4v4zM16,4v4h4L20,4h-4zM10,8h4L14,4h-4v4zM16,14h4v-4h-4v4zM16,20h4v-4h-4v4z"/>
+</vector>
diff --git a/res/drawable/ic_block.xml b/res/drawable/ic_block.xml
new file mode 100644
index 0000000..edeb4c6
--- /dev/null
+++ b/res/drawable/ic_block.xml
@@ -0,0 +1,27 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0"
+ android:tint="?android:attr/textColorPrimary">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="M12,2C6.48,2,2,6.48,2,12s4.48,10,10,10s10-4.48,10-10S17.52,2,12,2z M4,12c0-4.42,3.58-8,8-8c1.85,0,3.55,0.63,4.9,1.69
+ L5.69,16.9C4.63,15.55,4,13.85,4,12z M12,20c-1.85,0-3.55-0.63-4.9-1.69L18.31,7.1C19.37,8.45,20,10.15,20,12
+ C20,16.42,16.42,20,12,20z" />
+</vector>
diff --git a/res/drawable/ic_corp.xml b/res/drawable/ic_corp.xml
index b59113d..76dccd3 100644
--- a/res/drawable/ic_corp.xml
+++ b/res/drawable/ic_corp.xml
@@ -19,9 +19,5 @@
android:viewportWidth="24.0"
android:viewportHeight="24.0"
android:tint="?android:attr/textColorHint" >
- <path
- android:pathData="M20 6h-4V4c0-1.11-0.89-2-2-2h-4c-1.11 0-2 0.89-2 2v2H4c-1.11 0-1.99 0.89 -1.99
-2L2 19c0 1.11 0.89 2 2 2h16c1.11 0 2-0.89 2-2V8c0-1.11-0.89-2-2-2zM10
-4h4v2h-4V4zm10 15H4V8h16v11z"
- android:fillColor="@android:color/white"/>
+ <path android:fillColor="@android:color/white" android:pathData="M20 6h-4V4c0-1.11-.89-2-2-2h-4c-1.11 0-2 .89-2 2v2H4c-1.11 0-1.99.89-1.99 2L2 19c0 1.11.89 2 2 2h16c1.11 0 2-.89 2-2V8c0-1.11-.89-2-2-2zm-8 9c-1.1 0-2-.9-2-2s.9-2 2-2 2 .9 2 2-.9 2-2 2zm2-9h-4V4h4v2z"/>
</vector>
\ No newline at end of file
diff --git a/res/drawable/ic_corp_off.xml b/res/drawable/ic_corp_off.xml
new file mode 100644
index 0000000..62a9787
--- /dev/null
+++ b/res/drawable/ic_corp_off.xml
@@ -0,0 +1,25 @@
+<?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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24.0"
+ android:viewportHeight="24.0"
+ android:tint="?android:attr/textColorHint" >
+ <path
+ android:pathData="M22 7.95c.05-1.11-.84-2-1.95-1.95H16V3.95c0-1.11-.84-2-1.95-1.95h-4C8.94 1.95 8 2.84 8 3.95v.32l14 14V7.95zM14 6h-4V4h4v2zm7.54 14.28l-7.56-7.56v.01l-1.7-1.7h.01L7.21 5.95 3.25 1.99 1.99 3.27 4.69 6h-.64c-1.11 0-1.99.86-1.99 1.97l-.01 11.02c0 1.11.89 2.01 2 2.01h15.64l2.05 2.02L23 21.75l-1.46-1.47z"
+ android:fillColor="@android:color/white"/>
+</vector>
\ No newline at end of file
diff --git a/quickstep/res/drawable/ic_pin.xml b/res/drawable/ic_pin.xml
similarity index 100%
rename from quickstep/res/drawable/ic_pin.xml
rename to res/drawable/ic_pin.xml
diff --git a/res/layout/all_apps.xml b/res/layout/all_apps.xml
index 9427ae0..a41fb9a 100644
--- a/res/layout/all_apps.xml
+++ b/res/layout/all_apps.xml
@@ -16,7 +16,7 @@
<!-- The top and bottom paddings are defined in this container, but since we want
the list view to span the full width (for touch interception purposes), we
will bake the left/right padding into that view's background itself. -->
-<com.android.launcher3.allapps.AllAppsContainerView
+<com.android.launcher3.allapps.LauncherAllAppsContainerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/apps_view"
android:layout_width="match_parent"
@@ -57,7 +57,6 @@
android:layout_weight="1"
android:background="?android:attr/selectableItemBackground"
android:text="@string/all_apps_personal_tab"
- android:textAllCaps="true"
android:textColor="@color/all_apps_tab_text"
android:textSize="14sp" />
@@ -68,7 +67,6 @@
android:layout_weight="1"
android:background="?android:attr/selectableItemBackground"
android:text="@string/all_apps_work_tab"
- android:textAllCaps="true"
android:textColor="@color/all_apps_tab_text"
android:textSize="14sp" />
</com.android.launcher3.allapps.PersonalWorkSlidingTabStrip>
@@ -79,4 +77,4 @@
layout="@layout/search_container_all_apps"/>
<include layout="@layout/all_apps_fast_scroller" />
-</com.android.launcher3.allapps.AllAppsContainerView>
\ No newline at end of file
+</com.android.launcher3.allapps.LauncherAllAppsContainerView>
\ No newline at end of file
diff --git a/res/layout/secondary_launcher.xml b/res/layout/secondary_launcher.xml
new file mode 100644
index 0000000..98cfc34
--- /dev/null
+++ b/res/layout/secondary_launcher.xml
@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<com.android.launcher3.secondarydisplay.SecondaryDragLayer
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/drag_layer" >
+
+ <GridView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_marginBottom="100dp"
+ android:theme="@style/HomeScreenElementTheme"
+ android:layout_gravity="center_horizontal|top"
+ android:layout_margin="@dimen/dynamic_grid_edge_margin"
+ android:id="@+id/workspace_grid" />
+
+ <ImageButton
+ android:id="@+id/all_apps_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="bottom|end"
+ android:layout_margin="40dp"
+ android:padding="16dp"
+ android:src="@drawable/ic_apps"
+ android:background="@drawable/bg_all_apps_button"
+ android:contentDescription="@string/all_apps_button_label"
+ android:onClick="onAppsButtonClicked" />
+
+ <com.android.launcher3.allapps.AllAppsContainerView
+ android:id="@+id/apps_view"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="true"
+ android:clipToPadding="false"
+ android:focusable="false"
+ android:saveEnabled="false"
+ android:layout_gravity="bottom|end"
+ android:background="@drawable/round_rect_primary"
+ android:elevation="2dp"
+ android:visibility="invisible" >
+
+ <include
+ layout="@layout/all_apps_rv_layout"
+ android:visibility="gone" />
+
+ <com.android.launcher3.allapps.FloatingHeaderView
+ android:id="@+id/all_apps_header"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/search_container_all_apps"
+ android:clipToPadding="false"
+ android:paddingTop="@dimen/all_apps_header_top_padding"
+ android:orientation="vertical" >
+
+ <com.android.launcher3.allapps.PersonalWorkSlidingTabStrip
+ android:id="@+id/tabs"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/all_apps_header_tab_height"
+ android:layout_marginLeft="@dimen/all_apps_tabs_side_padding"
+ android:layout_marginRight="@dimen/all_apps_tabs_side_padding"
+ android:orientation="horizontal"
+ style="@style/TextHeadline">
+
+ <Button
+ android:id="@+id/tab_personal"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="?android:attr/selectableItemBackground"
+ android:text="@string/all_apps_personal_tab"
+ android:textAllCaps="true"
+ android:textColor="@color/all_apps_tab_text"
+ android:textSize="14sp" />
+
+ <Button
+ android:id="@+id/tab_work"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_weight="1"
+ android:background="?android:attr/selectableItemBackground"
+ android:text="@string/all_apps_work_tab"
+ android:textAllCaps="true"
+ android:textColor="@color/all_apps_tab_text"
+ android:textSize="14sp" />
+ </com.android.launcher3.allapps.PersonalWorkSlidingTabStrip>
+ </com.android.launcher3.allapps.FloatingHeaderView>
+
+ <com.android.launcher3.allapps.search.AppsSearchContainerLayout
+ android:id="@id/search_container_all_apps"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/all_apps_search_bar_field_height"
+ android:layout_centerHorizontal="true"
+ android:layout_gravity="top|center_horizontal"
+ android:background="@drawable/bg_all_apps_searchbox"
+ android:elevation="1dp"
+ android:focusableInTouchMode="true"
+ android:gravity="center"
+ android:hint="@string/all_apps_search_bar_hint"
+ android:imeOptions="actionSearch|flagNoExtractUi"
+ android:inputType="text|textNoSuggestions|textCapWords"
+ android:maxLines="1"
+ android:padding="8dp"
+ android:saveEnabled="false"
+ android:scrollHorizontally="true"
+ android:singleLine="true"
+ android:textColor="?android:attr/textColorSecondary"
+ android:textColorHint="@drawable/all_apps_search_hint"
+ android:textSize="16sp"
+ android:translationY="24dp" />
+
+ <include layout="@layout/all_apps_fast_scroller" />
+ </com.android.launcher3.allapps.AllAppsContainerView>
+</com.android.launcher3.secondarydisplay.SecondaryDragLayer>
\ No newline at end of file
diff --git a/res/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_apps_paused.xml b/res/layout/work_apps_paused.xml
new file mode 100644
index 0000000..5607e78
--- /dev/null
+++ b/res/layout/work_apps_paused.xml
@@ -0,0 +1,50 @@
+<?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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:padding="8dp"
+ android:orientation="vertical"
+ android:background="?attr/allAppsScrimColor"
+ android:gravity="center">
+
+ <ImageView
+ android:id="@+id/icon"
+ android:contentDescription="@string/work_apps_paused_title"
+ android:layout_width="48dp"
+ android:layout_height="48dp"
+ android:tint="?attr/folderTextColor"
+ android:src="@drawable/ic_corp_off" />
+
+ <TextView
+ style="@style/TextHeadline"
+ android:textColor="?attr/folderTextColor"
+ android:id="@+id/work_apps_paused_title"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="8dp"
+ android:text="@string/work_apps_paused_title"
+ android:textAlignment="center"
+ android:textSize="24sp" />
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:textColor="?attr/folderTextColor"
+ android:text="@string/work_apps_paused_body"
+ android:textAlignment="center"
+ android:textSize="16sp" />
+</LinearLayout>
\ No newline at end of file
diff --git a/res/layout/work_profile_edu.xml b/res/layout/work_profile_edu.xml
new file mode 100644
index 0000000..f7a529d
--- /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
+ style="@style/TextHeadline"
+ android:id="@+id/content_text"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="48dp"
+ android:layout_marginBottom="48dp"
+ 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/layout/work_tab_footer.xml b/res/layout/work_tab_footer.xml
index 379e9d0..db95416 100644
--- a/res/layout/work_tab_footer.xml
+++ b/res/layout/work_tab_footer.xml
@@ -17,63 +17,31 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:id="@+id/work_toggle_container"
android:focusable="true"
- android:paddingBottom="@dimen/all_apps_work_profile_tab_footer_bottom_padding"
- android:paddingLeft="@dimen/dynamic_grid_cell_padding_x"
- android:paddingRight="@dimen/dynamic_grid_cell_padding_x"
- android:paddingTop="@dimen/all_apps_work_profile_tab_footer_top_padding">
-
- <ImageView
- android:id="@+id/work_footer_divider"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:focusable="false"
- android:importantForAccessibility="no"
- android:paddingBottom="@dimen/all_apps_divider_margin_vertical"
- android:paddingTop="@dimen/all_apps_divider_margin_vertical"
- android:scaleType="fitXY"
- android:src="@drawable/all_apps_divider"/>
-
- <com.android.launcher3.allapps.WorkModeSwitch
- android:id="@+id/work_mode_toggle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_alignParentEnd="true"
- android:layout_below="@id/work_footer_divider"/>
+ android:paddingBottom="@dimen/all_apps_work_profile_tab_footer_padding_vertical"
+ android:orientation="horizontal"
+ android:paddingLeft="@dimen/all_apps_work_profile_tab_footer_padding_horizontal"
+ android:background="?attr/allAppsScrimColor"
+ android:paddingRight="@dimen/all_apps_work_profile_tab_footer_padding_horizontal"
+ android:paddingTop="@dimen/all_apps_work_profile_tab_footer_padding_vertical">
<TextView
- android:id="@android:id/title"
- android:layout_width="wrap_content"
+ android:id="@+id/work_mode_label"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:drawableStart="@drawable/ic_corp"
+ android:drawablePadding="3dp"
android:layout_height="wrap_content"
- android:layout_alignBaseline="@id/work_mode_toggle"
- android:layout_alignParentStart="true"
- android:ellipsize="end"
- android:lines="1"
- android:text="@string/work_profile_toggle_label"
- android:textColor="?android:attr/textColorTertiary"
- android:textSize="16sp"/>
-
- <ImageView
- android:id="@android:id/icon"
- android:layout_width="24dp"
- android:layout_height="24dp"
- android:layout_below="@android:id/title"
- android:layout_marginTop="8dp"
- android:src="@drawable/ic_corp"/>
-
- <TextView
- android:id="@+id/managed_by_label"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@android:id/title"
- android:layout_marginTop="8dp"
- android:layout_toEndOf="@android:id/icon"
android:ellipsize="end"
android:gravity="center_vertical"
android:lines="1"
android:minHeight="24dp"
- android:paddingStart="12dp"
- android:textColor="?android:attr/textColorHint"
- android:textSize="13sp"/>
+ android:paddingEnd="12dp"
+ android:textSize="16sp"/>
+ <com.android.launcher3.allapps.WorkModeSwitch
+ android:id="@+id/work_mode_toggle"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
</com.android.launcher3.views.WorkFooterContainer>
\ 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..ef34dcd 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>
@@ -107,6 +108,7 @@
<item type="id" name="action_shortcuts_and_notifications"/>
<item type="id" name="action_dismiss_notification" />
<item type="id" name="action_remote_action_shortcut" />
+ <item type="id" name="action_dismiss_prediction" />
<!-- QSB IDs. DO not change -->
<item type="id" name="search_container_workspace" />
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 4bcb8a7..edae7f4 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -85,6 +85,9 @@
<dimen name="all_apps_tabs_side_padding">12dp</dimen>
<dimen name="all_apps_divider_height">1dp</dimen>
+ <dimen name="all_apps_work_profile_tab_footer_padding_vertical">20dp</dimen>
+ <dimen name="all_apps_work_profile_tab_footer_padding_horizontal">24dp</dimen>
+
<!-- Search bar in All Apps -->
<dimen name="all_apps_header_max_elevation">3dp</dimen>
<dimen name="all_apps_header_scroll_to_elevation">16dp</dimen>
@@ -235,4 +238,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..bfa92f7 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -327,17 +327,23 @@
<!-- 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>
+ <string name="work_mode_on_label">Work apps: On</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>
+ <string name="work_mode_off_label">Work apps: Paused</string>
+
+ <string name="work_apps_paused_title">Work apps are paused</string>
+ <string name="work_apps_paused_body">You won\'t get any work notifications, and your IT admin can\'t see your location</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/res/values/styles.xml b/res/values/styles.xml
index 80c791c..35ae49c 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -55,6 +55,8 @@
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:statusBarColor">#00000000</item>
<item name="android:navigationBarColor">#00000000</item>
+
+
</style>
<style name="LauncherTheme.DarkMainColor" parent="@style/LauncherTheme">
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/BaseDraggingActivity.java b/src/com/android/launcher3/BaseDraggingActivity.java
index 0283eac..dda38b3 100644
--- a/src/com/android/launcher3/BaseDraggingActivity.java
+++ b/src/com/android/launcher3/BaseDraggingActivity.java
@@ -29,6 +29,7 @@
import android.util.Log;
import android.view.ActionMode;
import android.view.View;
+import android.view.View.OnClickListener;
import android.widget.Toast;
import androidx.annotation.Nullable;
@@ -36,6 +37,7 @@
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.uioverrides.DisplayRotationListener;
import com.android.launcher3.uioverrides.WallpaperColorInfo;
import com.android.launcher3.util.PackageManagerHelper;
@@ -262,5 +264,9 @@
}
}
+ public OnClickListener getItemOnClickListener() {
+ return ItemClickHandler.INSTANCE;
+ }
+
protected abstract void reapplyUi();
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index bc6fa6e..0d71da4 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -245,7 +245,7 @@
allAppsIconTextSizePx = originalProfile.iconTextSizePx;
allAppsCellHeightPx = originalProfile.allAppsCellHeightPx;
allAppsIconDrawablePaddingPx = originalProfile.iconDrawablePaddingOriginalPx;
- allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx;
+ allAppsCellWidthPx = allAppsIconSizePx + 2 * allAppsIconDrawablePaddingPx;
}
updateWorkspacePadding();
@@ -360,7 +360,7 @@
allAppsIconTextSizePx = iconTextSizePx;
allAppsIconDrawablePaddingPx = iconDrawablePaddingPx;
allAppsCellHeightPx = getCellSize().y;
- allAppsCellWidthPx = allAppsIconSizePx + allAppsIconDrawablePaddingPx;
+ allAppsCellWidthPx = allAppsIconSizePx + 2 * allAppsIconDrawablePaddingPx;
if (isVerticalBarLayout()) {
// Always hide the Workspace text with vertical bar layout.
diff --git a/src/com/android/launcher3/DropTarget.java b/src/com/android/launcher3/DropTarget.java
index 763432d..a32fd12 100644
--- a/src/com/android/launcher3/DropTarget.java
+++ b/src/com/android/launcher3/DropTarget.java
@@ -16,11 +16,14 @@
package com.android.launcher3;
+import android.content.Context;
import android.graphics.Rect;
import com.android.launcher3.accessibility.DragViewStateAnnouncer;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.dragndrop.DragView;
+import com.android.launcher3.folder.FolderNameProvider;
/**
* Interface defining an object that can receive a drag.
@@ -67,7 +70,12 @@
public DragViewStateAnnouncer stateAnnouncer;
- public DragObject() {
+ public FolderNameProvider folderNameProvider;
+
+ public DragObject(Context context) {
+ if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
+ folderNameProvider = FolderNameProvider.newInstance(context);
+ }
}
/**
diff --git a/src/com/android/launcher3/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/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 9d87152..a807e4f 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -18,8 +18,8 @@
import static com.android.launcher3.Utilities.getDevicePrefs;
import static com.android.launcher3.config.FeatureFlags.APPLY_CONFIG_AT_RUNTIME;
-import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.settings.SettingsActivity.GRID_OPTIONS_PREFERENCE_KEY;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.PackageManagerHelper.getPackageFilter;
import android.annotation.TargetApi;
@@ -41,6 +41,7 @@
import android.util.SparseArray;
import android.util.TypedValue;
import android.util.Xml;
+import android.view.Display;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@@ -48,6 +49,7 @@
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.util.ConfigMonitor;
import com.android.launcher3.util.DefaultDisplay;
+import com.android.launcher3.util.DefaultDisplay.Info;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Themes;
@@ -172,6 +174,13 @@
}
/**
+ * This constructor should NOT have any monitors by design.
+ */
+ public InvariantDeviceProfile(Context context, Display display) {
+ initGrid(context, null, new Info(display));
+ }
+
+ /**
* Retrieve system defined or RRO overriden icon shape.
*/
private static String getIconShapePath(Context context) {
@@ -183,8 +192,10 @@
}
private String initGrid(Context context, String gridName) {
- DefaultDisplay.Info displayInfo = DefaultDisplay.INSTANCE.get(context).getInfo();
+ return initGrid(context, gridName, DefaultDisplay.INSTANCE.get(context).getInfo());
+ }
+ private String initGrid(Context context, String gridName, DefaultDisplay.Info displayInfo) {
Point smallestSize = new Point(displayInfo.smallestSize);
Point largestSize = new Point(displayInfo.largestSize);
diff --git a/src/com/android/launcher3/ItemInfo.java b/src/com/android/launcher3/ItemInfo.java
index 3f723d1..c99465c 100644
--- a/src/com/android/launcher3/ItemInfo.java
+++ b/src/com/android/launcher3/ItemInfo.java
@@ -214,4 +214,11 @@
return id;
}
+ /**
+ * Returns if an Item is a predicted item
+ */
+ public boolean isPredictedItem() {
+ return container == LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION
+ || container == LauncherSettings.Favorites.CONTAINER_PREDICTION;
+ }
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 06f3453..43540ce 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -32,7 +32,6 @@
import static com.android.launcher3.logging.LoggerUtils.newContainerTarget;
import static com.android.launcher3.logging.LoggerUtils.newTarget;
import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
-import static com.android.launcher3.popup.SystemShortcut.DISMISS_PREDICTION;
import static com.android.launcher3.popup.SystemShortcut.INSTALL;
import static com.android.launcher3.popup.SystemShortcut.WIDGETS;
import static com.android.launcher3.states.RotationHelper.REQUEST_NONE;
@@ -103,7 +102,6 @@
import com.android.launcher3.dragndrop.DragView;
import com.android.launcher3.folder.FolderGridOrganizer;
import com.android.launcher3.folder.FolderIcon;
-import com.android.launcher3.folder.FolderNameProvider;
import com.android.launcher3.graphics.RotationMode;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.keyboard.CustomActionsPopup;
@@ -372,7 +370,7 @@
mLauncherView = LayoutInflater.from(this).inflate(R.layout.launcher, null);
setupViews();
- mPopupDataProvider = new PopupDataProvider(this);
+ mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
mAppTransitionManager = LauncherAppTransitionManager.newInstance(this);
mAppTransitionManager.registerRemoteAnimations();
@@ -616,10 +614,6 @@
return mStateManager;
}
- public FolderNameProvider getFolderNameProvider() {
- return new FolderNameProvider();
- }
-
@Override
public <T extends View> T findViewById(int id) {
return mLauncherView.findViewById(id);
@@ -1180,6 +1174,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 +1253,13 @@
cellXY[1] = cellY;
foundCellSpan = true;
+ DragObject dragObject = new DragObject(getApplicationContext());
+ dragObject.dragInfo = info;
// If appropriate, either create a folder or add to an existing folder
if (mWorkspace.createUserFolderIfNecessary(view, container, layout, cellXY, 0,
- true, null)) {
+ true, dragObject)) {
return;
}
- DragObject dragObject = new DragObject();
- dragObject.dragInfo = info;
if (mWorkspace.addToExistingFolderIfNecessary(view, layout, cellXY, 0, dragObject,
true)) {
return;
@@ -1344,7 +1343,7 @@
}
};
- public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
+ private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
mWorkspace.updateNotificationDots(updatedDots);
mAppsView.getAppsStore().updateNotificationDots(updatedDots);
}
@@ -1721,8 +1720,11 @@
}
}
- FolderIcon addFolder(CellLayout layout, int container, final int screenId, int cellX,
- int cellY) {
+ /**
+ * Creates and adds new folder to CellLayout
+ */
+ public FolderIcon addFolder(CellLayout layout, WorkspaceItemInfo info, int container,
+ final int screenId, int cellX, int cellY) {
final FolderInfo folderInfo = new FolderInfo();
folderInfo.title = "";
@@ -1730,7 +1732,8 @@
getModelWriter().addItemToDatabase(folderInfo, container, screenId, cellX, cellY);
// Create the view
- FolderIcon newFolder = FolderIcon.inflateFolderAndIcon(R.layout.folder_icon, this, layout, folderInfo);
+ FolderIcon newFolder = FolderIcon.inflateFolderAndIcon(R.layout.folder_icon, this, layout,
+ folderInfo);
mWorkspace.addInScreen(newFolder, folderInfo);
// Force measure the new folder icon
CellLayout parent = mWorkspace.getParentCellLayoutForView(newFolder);
@@ -1807,7 +1810,6 @@
// Note: There should be at most one log per method call. This is enforced implicitly
// by using if-else statements.
- UserEventDispatcher ued = getUserEventDispatcher();
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
if (topView != null && topView.onBackPressed()) {
// Handled by the floating view.
@@ -1875,6 +1877,7 @@
}
}
+ @Override
public boolean startActivitySafely(View v, Intent intent, ItemInfo item,
@Nullable String sourceContainer) {
if (!hasBeenResumed()) {
@@ -2691,7 +2694,7 @@
}
public Stream<SystemShortcut.Factory> getSupportedShortcuts() {
- return Stream.of(APP_INFO, WIDGETS, INSTALL, DISMISS_PREDICTION);
+ return Stream.of(APP_INFO, WIDGETS, INSTALL);
}
public static Launcher getLauncher(Context context) {
diff --git a/src/com/android/launcher3/LauncherAnimUtils.java b/src/com/android/launcher3/LauncherAnimUtils.java
index 74362ed..cdfd257 100644
--- a/src/com/android/launcher3/LauncherAnimUtils.java
+++ b/src/com/android/launcher3/LauncherAnimUtils.java
@@ -16,6 +16,8 @@
package com.android.launcher3;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_OVERVIEW_ACTIONS;
+
import android.graphics.drawable.Drawable;
import android.util.FloatProperty;
import android.util.Property;
@@ -28,9 +30,10 @@
* easier access from static classes and enums
*/
public static final int ALL_APPS_TRANSITION_MS = 320;
- public static final int OVERVIEW_TRANSITION_MS = 250;
+ public static final int OVERVIEW_TRANSITION_MS = ENABLE_OVERVIEW_ACTIONS.get() ? 380 : 250;
public static final int SPRING_LOADED_TRANSITION_MS = 150;
public static final int SPRING_LOADED_EXIT_DELAY = 500;
+ public static final int HINT_TRANSITION_MS = 80;
// The progress of an animation to all apps must be at least this far along to snap to all apps.
public static final float MIN_PROGRESS_TO_ALL_APPS = 0.5f;
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index cf978b5..04a7ecd 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -56,6 +56,7 @@
import com.android.launcher3.pm.PackageInstallInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.IntSparseArrayMap;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LooperExecutor;
@@ -96,6 +97,10 @@
private boolean mModelLoaded;
public boolean isModelLoaded() {
synchronized (mLock) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
+ "isModelLoaded: " + mModelLoaded + ", " + mLoaderTask);
+ }
return mModelLoaded && mLoaderTask == null;
}
}
@@ -368,6 +373,9 @@
public boolean stopLoader() {
synchronized (mLock) {
LoaderTask oldTask = mLoaderTask;
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "LauncherModel.stopLoader");
+ }
mLoaderTask = null;
if (oldTask != null) {
oldTask.stopLocked();
@@ -381,6 +389,10 @@
synchronized (mLock) {
stopLoader();
mLoaderTask = new LoaderTask(mApp, mBgAllAppsList, mBgDataModel, results);
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
+ "LauncherModel.startLoaderForResults " + mLoaderTask);
+ }
// Always post the loader task, instead of running directly (even on same thread) so
// that we exit any nested synchronized blocks
@@ -482,6 +494,10 @@
public void close() {
synchronized (mLock) {
// If we are still the last one to be scheduled, remove ourselves.
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
+ "LauncherModel.close " + mLoaderTask + ", " + mTask);
+ }
if (mLoaderTask == mTask) {
mLoaderTask = null;
}
diff --git a/src/com/android/launcher3/LauncherRootView.java b/src/com/android/launcher3/LauncherRootView.java
index ce1795a..2b2224a 100644
--- a/src/com/android/launcher3/LauncherRootView.java
+++ b/src/com/android/launcher3/LauncherRootView.java
@@ -8,7 +8,6 @@
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
-import android.graphics.Insets;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Build;
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/MainProcessInitializer.java b/src/com/android/launcher3/MainProcessInitializer.java
index 95ee687..5f6ecb5 100644
--- a/src/com/android/launcher3/MainProcessInitializer.java
+++ b/src/com/android/launcher3/MainProcessInitializer.java
@@ -19,6 +19,7 @@
import android.content.Context;
import com.android.launcher3.config.FeatureFlags;
+import com.android.launcher3.graphics.BitmapCreationCheck;
import com.android.launcher3.graphics.IconShape;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.util.ResourceBasedOverride;
@@ -39,5 +40,9 @@
FeatureFlags.initialize(context);
SessionCommitReceiver.applyDefaultUserPrefs(context);
IconShape.init(context);
+
+ if (BitmapCreationCheck.ENABLED) {
+ BitmapCreationCheck.startTracking(context);
+ }
}
}
diff --git a/src/com/android/launcher3/PagedView.java b/src/com/android/launcher3/PagedView.java
index a1888bf..ae4eae9 100644
--- a/src/com/android/launcher3/PagedView.java
+++ b/src/com/android/launcher3/PagedView.java
@@ -245,6 +245,13 @@
forceFinishScroller(true);
}
+ /**
+ * Returns left offset of a page. This is the gap between pages and prevents overlap.
+ */
+ public int scrollOffsetLeft() {
+ return mInsets.left + getPaddingLeft();
+ }
+
private void abortScrollerAnimation(boolean resetNextPage) {
mScroller.abortAnimation();
// We need to clean up the next page here to avoid computeScrollHelper from
diff --git a/src/com/android/launcher3/SecondaryDropTarget.java b/src/com/android/launcher3/SecondaryDropTarget.java
index 3c2ed72..1dbe195 100644
--- a/src/com/android/launcher3/SecondaryDropTarget.java
+++ b/src/com/android/launcher3/SecondaryDropTarget.java
@@ -6,6 +6,7 @@
import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_MASK;
import static com.android.launcher3.ItemInfoWithIcon.FLAG_SYSTEM_NO;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP;
+import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.DISMISS_PREDICTION;
import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.RECONFIGURE;
import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.UNINSTALL;
@@ -29,9 +30,11 @@
import android.widget.Toast;
import com.android.launcher3.Launcher.OnResumeCallback;
+import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragOptions;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.logging.LoggerUtils;
+import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.PackageManagerHelper;
@@ -43,6 +46,7 @@
* Drop target which provides a secondary option for an item.
* For app targets: shows as uninstall
* For configurable widgets: shows as setup
+ * For predicted app icons: don't suggest app
*/
public class SecondaryDropTarget extends ButtonDropTarget implements OnAlarmListener {
@@ -81,7 +85,11 @@
mHoverColor = getResources().getColor(R.color.uninstall_target_hover_tint);
setDrawable(R.drawable.ic_uninstall_shadow);
updateText(R.string.uninstall_drop_target_label);
- } else {
+ } else if (action == DISMISS_PREDICTION) {
+ mHoverColor = Themes.getColorAccent(getContext());
+ setDrawable(R.drawable.ic_block);
+ updateText(R.string.dismiss_prediction_label);
+ } else if (action == RECONFIGURE) {
mHoverColor = Themes.getColorAccent(getContext());
setDrawable(R.drawable.ic_setup_shadow);
updateText(R.string.gadget_setup_text);
@@ -101,8 +109,13 @@
@Override
public Target getDropTargetForLogging() {
Target t = LoggerUtils.newTarget(Target.Type.CONTROL);
- t.controlType = mCurrentAccessibilityAction == UNINSTALL ? ControlType.UNINSTALL_TARGET
- : ControlType.SETTINGS_BUTTON;
+ if (mCurrentAccessibilityAction == UNINSTALL) {
+ t.controlType = ControlType.UNINSTALL_TARGET;
+ } else if (mCurrentAccessibilityAction == DISMISS_PREDICTION) {
+ t.controlType = ControlType.DISMISS_PREDICTION;
+ } else {
+ t.controlType = ControlType.SETTINGS_BUTTON;
+ }
return t;
}
@@ -119,6 +132,9 @@
return true;
}
return false;
+ } else if (FeatureFlags.ENABLE_PREDICTION_DISMISS.get() && info.isPredictedItem()) {
+ setupUi(DISMISS_PREDICTION);
+ return true;
}
setupUi(UNINSTALL);
@@ -229,6 +245,11 @@
}
return null;
}
+ if (mCurrentAccessibilityAction == DISMISS_PREDICTION) {
+ AppLaunchTracker.INSTANCE.get(getContext()).onDismissApp(info.getTargetComponent(),
+ info.user, AppLaunchTracker.CONTAINER_PREDICTIONS);
+ return null;
+ }
// else: mCurrentAccessibilityAction == UNINSTALL
ComponentName cn = getUninstallTarget(info);
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..b53889a 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]);
@@ -1703,22 +1703,21 @@
float scale = mLauncher.getDragLayer().getDescendantRectRelativeToSelf(v, folderLocation);
target.removeView(v);
- FolderIcon fi =
- mLauncher.addFolder(target, container, screenId, targetCell[0], targetCell[1]);
+ FolderIcon fi = mLauncher.addFolder(target, sourceInfo, container, screenId,
+ targetCell[0], targetCell[1]);
destInfo.cellX = -1;
destInfo.cellY = -1;
sourceInfo.cellX = -1;
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/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index a7ef9ef..0b439ec 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -54,6 +54,7 @@
public static final int REMOVE = R.id.action_remove;
public static final int UNINSTALL = R.id.action_uninstall;
+ public static final int DISMISS_PREDICTION = R.id.action_dismiss_prediction;
public static final int RECONFIGURE = R.id.action_reconfigure;
protected static final int ADD_TO_WORKSPACE = R.id.action_add_to_workspace;
protected static final int MOVE = R.id.action_move;
@@ -86,6 +87,8 @@
launcher.getText(R.string.remove_drop_target_label)));
mActions.put(UNINSTALL, new AccessibilityAction(UNINSTALL,
launcher.getText(R.string.uninstall_drop_target_label)));
+ mActions.put(DISMISS_PREDICTION, new AccessibilityAction(DISMISS_PREDICTION,
+ launcher.getText(R.string.dismiss_prediction_label)));
mActions.put(RECONFIGURE, new AccessibilityAction(RECONFIGURE,
launcher.getText(R.string.gadget_setup_text)));
mActions.put(ADD_TO_WORKSPACE, new AccessibilityAction(ADD_TO_WORKSPACE,
@@ -166,13 +169,22 @@
}
public boolean performAction(final View host, final ItemInfo item, int action) {
- if (action == ACTION_LONG_CLICK && ShortcutUtil.isDeepShortcut(item)) {
- CustomActionsPopup popup = new CustomActionsPopup(mLauncher, host);
- if (popup.canShow()) {
- popup.show();
+ if (action == ACTION_LONG_CLICK) {
+ if (ShortcutUtil.isDeepShortcut(item)) {
+ CustomActionsPopup popup = new CustomActionsPopup(mLauncher, host);
+ if (popup.canShow()) {
+ popup.show();
+ return true;
+ }
+ } else if (host instanceof BubbleTextView) {
+ // Long press should be consumed for workspace items, and it should invoke the
+ // Shortcuts / Notifications / Actions pop-up menu, and not start a drag as the
+ // standard long press path does.
+ PopupContainerWithArrow.showForIcon((BubbleTextView) host);
return true;
}
}
+
if (action == MOVE) {
beginAccessibleDrag(host, item);
} else if (action == ADD_TO_WORKSPACE) {
diff --git a/src/com/android/launcher3/allapps/AllAppsContainerView.java b/src/com/android/launcher3/allapps/AllAppsContainerView.java
index 13b7b54..56bd1b6 100644
--- a/src/com/android/launcher3/allapps/AllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsContainerView.java
@@ -15,6 +15,9 @@
*/
package com.android.launcher3.allapps;
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
@@ -40,6 +43,7 @@
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.AppInfo;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.DragSource;
@@ -47,20 +51,19 @@
import com.android.launcher3.Insettable;
import com.android.launcher3.InsettableFrameLayout;
import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.keyboard.FocusedItemDecorator;
+import com.android.launcher3.pm.UserCache;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
import com.android.launcher3.util.ItemInfoMatcher;
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.WorkFooterContainer;
/**
* The all apps view container.
@@ -73,8 +76,8 @@
private static final float FLING_ANIMATION_THRESHOLD = 0.55f;
private static final int ALPHA_CHANNEL_COUNT = 2;
- private final Launcher mLauncher;
- private final AdapterHolder[] mAH;
+ protected final BaseDraggingActivity mLauncher;
+ protected final AdapterHolder[] mAH;
private final ItemInfoMatcher mPersonalMatcher = ItemInfoMatcher.ofUser(Process.myUserHandle());
private final ItemInfoMatcher mWorkMatcher = ItemInfoMatcher.not(mPersonalMatcher);
private final AllAppsStore mAllAppsStore = new AllAppsStore();
@@ -82,14 +85,17 @@
private final Paint mNavBarScrimPaint;
private int mNavBarScrimHeight = 0;
- private SearchUiManager mSearchUiManager;
+ protected SearchUiManager mSearchUiManager;
private View mSearchContainer;
private AllAppsPagedView mViewPager;
+
private FloatingHeaderView mHeader;
+ private WorkFooterContainer mWorkFooterContainer;
+
private SpannableStringBuilder mSearchQueryBuilder = null;
- private boolean mUsingTabs;
+ protected boolean mUsingTabs;
private boolean mSearchModeWhileUsingTabs = false;
private RecyclerViewFastScroller mTouchHandler;
@@ -108,7 +114,7 @@
public AllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- mLauncher = Launcher.getLauncher(context);
+ mLauncher = BaseDraggingActivity.fromContext(context);
mLauncher.addOnDeviceProfileChangeListener(this);
mSearchQueryBuilder = new SpannableStringBuilder();
@@ -130,6 +136,15 @@
mMultiValueAlpha = new MultiValueAlpha(this, ALPHA_CHANNEL_COUNT);
}
+ /**
+ * Sets the long click listener for icons
+ */
+ public void setOnIconLongClickListener(OnLongClickListener listener) {
+ for (AdapterHolder holder : mAH) {
+ holder.adapter.setOnIconLongClickListener(listener);
+ }
+ }
+
public AllAppsStore getAppsStore() {
return mAllAppsStore;
}
@@ -166,6 +181,15 @@
}
}
rebindAdapters(hasWorkApps);
+ if (hasWorkApps) {
+ resetWorkProfile();
+ }
+ }
+
+ private void resetWorkProfile() {
+ mWorkFooterContainer.refresh();
+ mAH[AdapterHolder.WORK].setupOverlay();
+ mAH[AdapterHolder.WORK].applyPadding();
}
/**
@@ -190,11 +214,6 @@
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
-
- // The AllAppsContainerView houses the QSB and is hence visible from the Workspace
- // Overview states. We shouldn't intercept for the scrubber in these cases.
- if (!mLauncher.isInState(LauncherState.ALL_APPS)) return false;
-
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
AllAppsRecyclerView rv = getActiveRecyclerView();
if (rv != null &&
@@ -287,7 +306,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) {
@@ -305,10 +325,10 @@
+ grid.cellLayoutPaddingLeftRightPx;
for (int i = 0; i < mAH.length; i++) {
- mAH[i].adapter.setAppsPerRow(grid.inv.numAllAppsColumns);
mAH[i].padding.bottom = insets.bottom;
mAH[i].padding.left = mAH[i].padding.right = leftRightPadding;
mAH[i].applyPadding();
+ mAH[i].setupOverlay();
}
ViewGroup.MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
@@ -323,8 +343,6 @@
setLayoutParams(mlp);
InsettableFrameLayout.dispatchInsets(this, insets);
- mLauncher.getAllAppsController()
- .setScrollRangeDelta(mSearchUiManager.getScrollRangeDelta(insets));
}
@Override
@@ -368,12 +386,17 @@
mAllAppsStore.unregisterIconContainer(mAH[AdapterHolder.WORK].recyclerView);
if (mUsingTabs) {
+ setupWorkToggle();
mAH[AdapterHolder.MAIN].setup(mViewPager.getChildAt(0), mPersonalMatcher);
mAH[AdapterHolder.WORK].setup(mViewPager.getChildAt(1), mWorkMatcher);
onTabChanged(mViewPager.getNextPage());
} else {
mAH[AdapterHolder.MAIN].setup(findViewById(R.id.apps_list_view), null);
mAH[AdapterHolder.WORK].recyclerView = null;
+ if (mWorkFooterContainer != null) {
+ ((ViewGroup) mWorkFooterContainer.getParent()).removeView(mWorkFooterContainer);
+ mWorkFooterContainer = null;
+ }
}
setupHeader();
@@ -381,6 +404,16 @@
mAllAppsStore.registerIconContainer(mAH[AdapterHolder.WORK].recyclerView);
}
+ private void setupWorkToggle() {
+ mWorkFooterContainer = (WorkFooterContainer) mLauncher.getLayoutInflater().inflate(
+ R.layout.work_tab_footer, findViewById(R.id.work_toggle_container));
+ mWorkFooterContainer.setLayoutParams(
+ new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ this.addView(mWorkFooterContainer);
+ mWorkFooterContainer.post(() -> mAH[AdapterHolder.WORK].applyPadding());
+ }
+
private void replaceRVContainer(boolean showTabs) {
for (int i = 0; i < mAH.length; i++) {
if (mAH[i].recyclerView != null) {
@@ -416,10 +449,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 (mWorkFooterContainer != null) {
+ mWorkFooterContainer.setWorkTabVisible(pos == AdapterHolder.WORK);
}
}
@@ -566,6 +598,7 @@
public static final int MAIN = 0;
public static final int WORK = 1;
+ private final boolean mIsWork;
public final AllAppsGridAdapter adapter;
final LinearLayoutManager layoutManager;
final AlphabeticalAppsList appsList;
@@ -573,7 +606,10 @@
AllAppsRecyclerView recyclerView;
boolean verticalFadingEdge;
+ boolean mWorkDisabled;
+
AdapterHolder(boolean isWork) {
+ mIsWork = isWork;
appsList = new AlphabeticalAppsList(mLauncher, mAllAppsStore, isWork);
adapter = new AllAppsGridAdapter(mLauncher, appsList);
appsList.setAdapter(adapter);
@@ -584,7 +620,7 @@
appsList.updateItemFilter(matcher);
recyclerView = (AllAppsRecyclerView) rv;
recyclerView.setEdgeEffectFactory(createEdgeEffectFactory());
- recyclerView.setApps(appsList, mUsingTabs);
+ recyclerView.setApps(appsList);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
recyclerView.setHasFixedSize(true);
@@ -595,11 +631,36 @@
adapter.setIconFocusListener(focusedItemDecorator.getFocusListener());
applyVerticalFadingEdgeEnabled(verticalFadingEdge);
applyPadding();
+ setupOverlay();
+ }
+
+ void setupOverlay() {
+ if (!mIsWork || recyclerView == null) return;
+ boolean workDisabled = UserCache.INSTANCE.get(mLauncher).isAnyProfileQuietModeEnabled();
+ recyclerView.getOverlay().clear();
+ if (workDisabled) {
+ View pausedOverlay = mLauncher.getLayoutInflater().inflate(
+ R.layout.work_apps_paused, null);
+ recyclerView.post(() -> {
+ int width = recyclerView.getWidth();
+ int height = recyclerView.getHeight();
+ pausedOverlay.measure(makeMeasureSpec(width, EXACTLY),
+ makeMeasureSpec(height, EXACTLY));
+ pausedOverlay.layout(0, 0, width, height);
+ applyPadding();
+ });
+ recyclerView.getOverlay().add(pausedOverlay);
+ }
+ mWorkDisabled = workDisabled;
}
void applyPadding() {
if (recyclerView != null) {
- recyclerView.setPadding(padding.left, padding.top, padding.right, padding.bottom);
+ int bottomOffset =
+ mWorkFooterContainer != null && mIsWork ? mWorkFooterContainer.getHeight()
+ : 0;
+ recyclerView.setPadding(padding.left, padding.top, padding.right,
+ padding.bottom + bottomOffset);
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 80ea1eb..1f861bc 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -15,17 +15,22 @@
*/
package com.android.launcher3.allapps;
+import static com.android.launcher3.touch.ItemLongClickListener.INSTANCE_ALL_APPS;
+
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
+import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
import android.widget.TextView;
+import androidx.annotation.Nullable;
import androidx.core.view.accessibility.AccessibilityEventCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityRecordCompat;
@@ -33,14 +38,11 @@
import androidx.recyclerview.widget.RecyclerView;
import com.android.launcher3.AppInfo;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.BubbleTextView;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AlphabeticalAppsList.AdapterItem;
import com.android.launcher3.model.AppLaunchTracker;
-import com.android.launcher3.pm.UserCache;
-import com.android.launcher3.touch.ItemClickHandler;
-import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.PackageManagerHelper;
import java.util.List;
@@ -64,7 +66,6 @@
// A divider that separates the apps list and the search market button
public static final int VIEW_TYPE_ALL_APPS_DIVIDER = 1 << 4;
- public static final int VIEW_TYPE_WORK_TAB_FOOTER = 1 << 5;
// Common view type masks
public static final int VIEW_TYPE_MASK_DIVIDER = VIEW_TYPE_ALL_APPS_DIVIDER;
@@ -174,23 +175,26 @@
}
}
- private final Launcher mLauncher;
+ private final BaseDraggingActivity mLauncher;
private final LayoutInflater mLayoutInflater;
private final AlphabeticalAppsList mApps;
private final GridLayoutManager mGridLayoutMgr;
private final GridSpanSizer mGridSizer;
+ private final OnClickListener mOnIconClickListener;
+ private OnLongClickListener mOnIconLongClickListener = INSTANCE_ALL_APPS;
+
private int mAppsPerRow;
private BindViewCallback mBindViewCallback;
private OnFocusChangeListener mIconFocusListener;
// The text to show when there are no search results and no market search handler.
- private String mEmptySearchMessage;
+ protected String mEmptySearchMessage;
// The intent to send off to the market app, updated each time the search query changes.
private Intent mMarketSearchIntent;
- public AllAppsGridAdapter(Launcher launcher, AlphabeticalAppsList apps) {
+ public AllAppsGridAdapter(BaseDraggingActivity launcher, AlphabeticalAppsList apps) {
Resources res = launcher.getResources();
mLauncher = launcher;
mApps = apps;
@@ -200,6 +204,8 @@
mGridLayoutMgr.setSpanSizeLookup(mGridSizer);
mLayoutInflater = LayoutInflater.from(launcher);
+ mOnIconClickListener = launcher.getItemOnClickListener();
+
setAppsPerRow(mLauncher.getDeviceProfile().inv.numAllAppsColumns);
}
@@ -208,6 +214,13 @@
mGridLayoutMgr.setSpanCount(mAppsPerRow);
}
+ /**
+ * Sets the long click listener for icons
+ */
+ public void setOnIconLongClickListener(@Nullable OnLongClickListener listener) {
+ mOnIconLongClickListener = listener;
+ }
+
public static boolean isDividerViewType(int viewType) {
return isViewType(viewType, VIEW_TYPE_MASK_DIVIDER);
}
@@ -254,8 +267,8 @@
case VIEW_TYPE_ICON:
BubbleTextView icon = (BubbleTextView) mLayoutInflater.inflate(
R.layout.all_apps_icon, parent, false);
- icon.setOnClickListener(ItemClickHandler.INSTANCE);
- icon.setOnLongClickListener(ItemLongClickListener.INSTANCE_ALL_APPS);
+ icon.setOnClickListener(mOnIconClickListener);
+ icon.setOnLongClickListener(mOnIconLongClickListener);
icon.setLongPressTimeoutFactor(1f);
icon.setOnFocusChangeListener(mIconFocusListener);
@@ -274,9 +287,6 @@
case VIEW_TYPE_ALL_APPS_DIVIDER:
return new ViewHolder(mLayoutInflater.inflate(
R.layout.all_apps_divider, parent, false));
- case VIEW_TYPE_WORK_TAB_FOOTER:
- View footer = mLayoutInflater.inflate(R.layout.work_tab_footer, parent, false);
- return new ViewHolder(footer);
default:
throw new RuntimeException("Unexpected view type");
}
@@ -308,15 +318,6 @@
case VIEW_TYPE_ALL_APPS_DIVIDER:
// nothing to do
break;
- case VIEW_TYPE_WORK_TAB_FOOTER:
- WorkModeSwitch workModeToggle = holder.itemView.findViewById(R.id.work_mode_toggle);
- workModeToggle.refresh();
- TextView managedByLabel = holder.itemView.findViewById(R.id.managed_by_label);
- boolean anyProfileQuietModeEnabled = UserCache.INSTANCE.get(
- managedByLabel.getContext()).isAnyProfileQuietModeEnabled();
- managedByLabel.setText(anyProfileQuietModeEnabled
- ? R.string.work_mode_off_label : R.string.work_mode_on_label);
- break;
}
if (mBindViewCallback != null) {
mBindViewCallback.onBindView(holder);
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index f82e380..b6744cf 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -28,14 +28,13 @@
import androidx.recyclerview.widget.RecyclerView;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.BaseRecyclerView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.allapps.AllAppsGridAdapter.AppsGridLayoutManager;
-import com.android.launcher3.compat.AccessibilityManagerCompat;
import com.android.launcher3.logging.StatsLogUtils.LogContainerProvider;
import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.Target;
@@ -84,7 +83,7 @@
/**
* Sets the list of apps in this view, used to determine the fastscroll position.
*/
- public void setApps(AlphabeticalAppsList apps, boolean usingTabs) {
+ public void setApps(AlphabeticalAppsList apps) {
mApps = apps;
mFastScrollHelper = new AllAppsFastScrollHelper(this, apps);
}
@@ -94,7 +93,7 @@
}
private void updatePoolSize() {
- DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
+ DeviceProfile grid = BaseDraggingActivity.fromContext(getContext()).getDeviceProfile();
RecyclerView.RecycledViewPool pool = getRecycledViewPool();
int approxRows = (int) Math.ceil(grid.availableHeightPx / grid.allAppsIconSizePx);
pool.setMaxRecycledViews(AllAppsGridAdapter.VIEW_TYPE_EMPTY_SEARCH, 1);
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 0681919..1bde138 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -11,7 +11,7 @@
import static com.android.launcher3.anim.Interpolators.FAST_OUT_SLOW_IN;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.anim.PropertySetter.NO_ANIM_PROPERTY_SETTER;
-import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
import static com.android.launcher3.util.SystemUiController.UI_STATE_ALL_APPS;
import android.animation.Animator;
@@ -46,7 +46,7 @@
*/
public class AllAppsTransitionController implements StateHandler, OnDeviceProfileChangeListener {
- private static final float SPRING_DAMPING_RATIO = 0.9f;
+ private static final float SPRING_DAMPING_RATIO = 0.75f;
private static final float SPRING_STIFFNESS = 600f;
public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS =
@@ -185,7 +185,7 @@
}
public Animator createSpringAnimation(float... progressValues) {
- if (UNSTABLE_SPRINGS.get()) {
+ if (QUICKSTEP_SPRINGS.get()) {
return new SpringObjectAnimator<>(this, ALL_APPS_PROGRESS, 1f / mShiftRange,
SPRING_DAMPING_RATIO, SPRING_STIFFNESS, progressValues);
}
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 10e2821..b501c82 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -15,14 +15,11 @@
*/
package com.android.launcher3.allapps;
-import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
import android.content.Context;
-import android.content.pm.PackageManager;
import com.android.launcher3.AppInfo;
-import com.android.launcher3.Launcher;
-import com.android.launcher3.Utilities;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.LabelComparator;
@@ -117,16 +114,9 @@
item.position = pos;
return item;
}
-
- public static AdapterItem asWorkTabFooter(int pos) {
- AdapterItem item = new AdapterItem();
- item.viewType = AllAppsGridAdapter.VIEW_TYPE_WORK_TAB_FOOTER;
- item.position = pos;
- return item;
- }
}
- private final Launcher mLauncher;
+ private final BaseDraggingActivity mLauncher;
// The set of apps from the system
private final List<AppInfo> mApps = new ArrayList<>();
@@ -151,7 +141,7 @@
public AlphabeticalAppsList(Context context, AllAppsStore appsStore, boolean isWork) {
mAllAppsStore = appsStore;
- mLauncher = Launcher.getLauncher(context);
+ mLauncher = BaseDraggingActivity.fromContext(context);
mAppNameComparator = new AppInfoComparator(context);
mIsWork = isWork;
mNumAppsPerRow = mLauncher.getDeviceProfile().inv.numColumns;
@@ -390,18 +380,6 @@
break;
}
}
-
- // Add the work profile footer if required.
- if (shouldShowWorkFooter()) {
- mAdapterItems.add(AdapterItem.asWorkTabFooter(position++));
- }
- }
-
- private boolean shouldShowWorkFooter() {
- return mIsWork && Utilities.ATLEAST_P &&
- (hasShortcutsPermission(mLauncher)
- || mLauncher.checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
- == PackageManager.PERMISSION_GRANTED);
}
private List<AppInfo> getFiltersAppInfos() {
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 42a0eee..cc33af9 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -31,9 +31,9 @@
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
@@ -393,7 +393,7 @@
@Override
public void setInsets(Rect insets) {
- DeviceProfile grid = Launcher.getLauncher(getContext()).getDeviceProfile();
+ DeviceProfile grid = BaseDraggingActivity.fromContext(getContext()).getDeviceProfile();
for (FloatingHeaderRow row : mAllRows) {
row.setInsets(insets, grid);
}
diff --git a/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
new file mode 100644
index 0000000..9d0ecd3
--- /dev/null
+++ b/src/com/android/launcher3/allapps/LauncherAllAppsContainerView.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.allapps;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.LauncherStateManager;
+import com.android.launcher3.views.WorkEduView;
+
+/**
+ * AllAppsContainerView with launcher specific callbacks
+ */
+public class LauncherAllAppsContainerView extends AllAppsContainerView {
+
+ private final Launcher mLauncher;
+
+ private LauncherStateManager.StateListener mWorkTabListener;
+
+ public LauncherAllAppsContainerView(Context context) {
+ this(context, null);
+ }
+
+ public LauncherAllAppsContainerView(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public LauncherAllAppsContainerView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mLauncher = Launcher.getLauncher(context);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ // The AllAppsContainerView houses the QSB and is hence visible from the Workspace
+ // Overview states. We shouldn't intercept for the scrubber in these cases.
+ if (!mLauncher.isInState(LauncherState.ALL_APPS)) return false;
+
+ return super.onInterceptTouchEvent(ev);
+ }
+
+ @Override
+ public void setInsets(Rect insets) {
+ super.setInsets(insets);
+ mLauncher.getAllAppsController()
+ .setScrollRangeDelta(mSearchUiManager.getScrollRangeDelta(insets));
+ }
+
+ @Override
+ public void onTabChanged(int pos) {
+ super.onTabChanged(pos);
+ if (mUsingTabs) {
+ if (pos == AdapterHolder.WORK) {
+ WorkEduView.showWorkEduIfNeeded(mLauncher);
+ } else {
+ mWorkTabListener = WorkEduView.showEduFlowIfNeeded(mLauncher, mWorkTabListener);
+ }
+ }
+ }
+}
diff --git a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
index decdcc0..6204f31 100644
--- a/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
+++ b/src/com/android/launcher3/allapps/PersonalWorkSlidingTabStrip.java
@@ -24,15 +24,14 @@
import android.widget.Button;
import android.widget.LinearLayout;
-import com.android.launcher3.Launcher;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.pageindicators.PageIndicator;
import com.android.launcher3.util.Themes;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
/**
* Supports two indicator colors, dedicated for personal and work tabs.
*/
@@ -73,7 +72,7 @@
mDividerPaint.setStrokeWidth(
getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height));
- mSharedPreferences = Launcher.getLauncher(getContext()).getSharedPrefs();
+ mSharedPreferences = Utilities.getPrefs(context);
mIsRtl = Utilities.isRtl(getResources());
}
diff --git a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
index 4515dde..ed45749 100644
--- a/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
+++ b/src/com/android/launcher3/allapps/search/AllAppsSearchBarController.java
@@ -25,8 +25,8 @@
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ExtendedEditText;
-import com.android.launcher3.Launcher;
import com.android.launcher3.Utilities;
import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.util.ComponentKey;
@@ -41,7 +41,7 @@
implements TextWatcher, OnEditorActionListener, ExtendedEditText.OnBackKeyListener,
OnFocusChangeListener {
- protected Launcher mLauncher;
+ protected BaseDraggingActivity mLauncher;
protected Callbacks mCb;
protected ExtendedEditText mInput;
protected String mQuery;
@@ -56,7 +56,7 @@
*/
public final void initialize(
SearchAlgorithm searchAlgorithm, ExtendedEditText input,
- Launcher launcher, Callbacks cb) {
+ BaseDraggingActivity launcher, Callbacks cb) {
mCb = cb;
mLauncher = launcher;
diff --git a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
index 31fcc8c..d497c3a 100644
--- a/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
+++ b/src/com/android/launcher3/allapps/search/AppsSearchContainerLayout.java
@@ -26,8 +26,6 @@
import android.content.Context;
import android.graphics.Rect;
import android.text.Selection;
-import android.text.Spannable;
-import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.method.TextKeyListener;
import android.util.AttributeSet;
@@ -36,18 +34,16 @@
import android.view.ViewGroup.MarginLayoutParams;
import android.view.animation.Interpolator;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Insettable;
-import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.AllAppsContainerView;
import com.android.launcher3.allapps.AllAppsStore;
import com.android.launcher3.allapps.AlphabeticalAppsList;
import com.android.launcher3.allapps.SearchUiManager;
import com.android.launcher3.anim.PropertySetter;
-import com.android.launcher3.graphics.TintedDrawableSpan;
import com.android.launcher3.util.ComponentKey;
import java.util.ArrayList;
@@ -59,8 +55,7 @@
implements SearchUiManager, AllAppsSearchBarController.Callbacks,
AllAppsStore.OnUpdateListener, Insettable {
-
- private final Launcher mLauncher;
+ private final BaseDraggingActivity mLauncher;
private final AllAppsSearchBarController mSearchBarController;
private final SpannableStringBuilder mSearchQueryBuilder;
@@ -82,7 +77,7 @@
public AppsSearchContainerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
- mLauncher = Launcher.getLauncher(context);
+ mLauncher = BaseDraggingActivity.fromContext(context);
mSearchBarController = new AllAppsSearchBarController();
mSearchQueryBuilder = new SpannableStringBuilder();
@@ -97,13 +92,13 @@
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- mLauncher.getAppsView().getAppsStore().addUpdateListener(this);
+ mAppsView.getAppsStore().addUpdateListener(this);
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mLauncher.getAppsView().getAppsStore().removeUpdateListener(this);
+ mAppsView.getAppsStore().removeUpdateListener(this);
}
@Override
diff --git a/src/com/android/launcher3/anim/AnimatorPlaybackController.java b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
index 4a52795..1c277ab 100644
--- a/src/com/android/launcher3/anim/AnimatorPlaybackController.java
+++ b/src/com/android/launcher3/anim/AnimatorPlaybackController.java
@@ -26,6 +26,7 @@
import android.animation.ValueAnimator;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
@@ -250,14 +251,24 @@
}
}
+ /** @see #dispatchOnCancelWithoutCancelRunnable(Runnable) */
+ public void dispatchOnCancelWithoutCancelRunnable() {
+ dispatchOnCancelWithoutCancelRunnable(null);
+ }
+
/**
* Sets mOnCancelRunnable = null before dispatching the cancel and restoring the runnable. This
* is intended to be used only if you need to cancel but want to defer cleaning up yourself.
+ * @param callback An optional callback to run after dispatching the cancel but before resetting
+ * the onCancelRunnable.
*/
- public void dispatchOnCancelWithoutCancelRunnable() {
+ public void dispatchOnCancelWithoutCancelRunnable(@Nullable Runnable callback) {
Runnable onCancel = mOnCancelRunnable;
setOnCancelRunnable(null);
dispatchOnCancel();
+ if (callback != null) {
+ callback.run();
+ }
setOnCancelRunnable(onCancel);
}
diff --git a/src/com/android/launcher3/anim/SpringObjectAnimator.java b/src/com/android/launcher3/anim/SpringObjectAnimator.java
index 91a3106..27b9c18 100644
--- a/src/com/android/launcher3/anim/SpringObjectAnimator.java
+++ b/src/com/android/launcher3/anim/SpringObjectAnimator.java
@@ -29,12 +29,13 @@
import android.util.FloatProperty;
import android.util.Log;
-import java.util.ArrayList;
-
import androidx.dynamicanimation.animation.DynamicAnimation.OnAnimationEndListener;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
+import java.util.ArrayList;
+
+
/**
* This animator allows for an object's property to be be controlled by an {@link ObjectAnimator} or
* a {@link SpringAnimation}. It extends ValueAnimator so it can be used in an AnimatorSet.
@@ -137,11 +138,13 @@
mProperty.switchToSpring();
- mSpring.setStartVelocity(velocity);
-
float startValue = end == 0 ? mValues[1] : mValues[0];
float endValue = end == 0 ? mValues[0] : mValues[1];
- mSpring.setStartValue(startValue);
+
+ // Ensures that the velocity matches the direction of the values.
+ velocity = Math.signum(endValue - startValue) * Math.abs(velocity);
+ mSpring.setStartVelocity(velocity);
+
new Handler(Looper.getMainLooper()).postDelayed(() -> {
mSpring.animateToFinalPosition(endValue);
}, getStartDelay());
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..1b7b015 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);
@@ -567,11 +565,13 @@
/**
* Since accessible drag and drop won't cause the same sequence of touch events, we manually
- * inject the appropriate state.
+ * inject the appropriate state which would have been otherwise initiated via touch events.
*/
public void prepareAccessibleDrag(int x, int y) {
mMotionDownX = x;
mMotionDownY = y;
+ mLastTouch[0] = x;
+ mLastTouch[1] = y;
}
/**
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/BitmapCreationCheck.java b/src/com/android/launcher3/graphics/BitmapCreationCheck.java
new file mode 100644
index 0000000..e63e542
--- /dev/null
+++ b/src/com/android/launcher3/graphics/BitmapCreationCheck.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2019 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.graphics;
+
+import android.annotation.TargetApi;
+import android.app.Activity;
+import android.app.Application;
+import android.app.Application.ActivityLifecycleCallbacks;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.os.Handler;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
+import android.view.ViewTreeObserver.OnDrawListener;
+
+import com.android.launcher3.Utilities;
+import com.android.launcher3.icons.GraphicsUtils;
+
+/**
+ * Utility class to check bitmap creation during draw pass.
+ */
+public class BitmapCreationCheck {
+
+ private static final String TAG = "BitmapCreationCheck";
+
+ public static final boolean ENABLED = false;
+
+ /**
+ * Starts tracking bitmap creations during {@link View#draw(Canvas)} calls
+ */
+ public static void startTracking(Context context) {
+ MyTracker tracker = new MyTracker();
+ ((Application) context.getApplicationContext()).registerActivityLifecycleCallbacks(tracker);
+ GraphicsUtils.sOnNewBitmapRunnable = tracker::onBitmapCreated;
+ }
+
+ @TargetApi(VERSION_CODES.Q)
+ private static class MyTracker
+ implements ActivityLifecycleCallbacks, OnAttachStateChangeListener {
+
+ private final ThreadLocal<Boolean> mCurrentThreadDrawing =
+ ThreadLocal.withInitial(() -> false);
+
+ @Override
+ public void onActivityCreated(Activity activity, Bundle bundle) {
+ activity.getWindow().getDecorView().addOnAttachStateChangeListener(this);
+ }
+
+ @Override
+ public void onActivityStarted(Activity activity) { }
+
+ @Override
+ public void onActivityResumed(Activity activity) { }
+
+ @Override
+ public void onActivityPaused(Activity activity) { }
+
+ @Override
+ public void onActivityStopped(Activity activity) { }
+
+ @Override
+ public void onActivitySaveInstanceState(Activity activity, Bundle bundle) { }
+
+ @Override
+ public void onActivityDestroyed(Activity activity) { }
+
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ view.getViewTreeObserver().addOnDrawListener(new MyViewDrawListener(view.getHandler()));
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View view) { }
+
+ private class MyViewDrawListener implements OnDrawListener, Runnable {
+
+ private final Handler mHandler;
+
+ MyViewDrawListener(Handler handler) {
+ mHandler = handler;
+ }
+
+ @Override
+ public void onDraw() {
+ mCurrentThreadDrawing.set(true);
+ Utilities.postAsyncCallback(mHandler, this);
+ }
+
+ @Override
+ public void run() {
+ mCurrentThreadDrawing.set(false);
+ }
+ }
+
+ private void onBitmapCreated() {
+ if (mCurrentThreadDrawing.get()) {
+ Log.e(TAG, "Bitmap created during draw pass", new Exception());
+ }
+ }
+ }
+
+}
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/logging/LoggerUtils.java b/src/com/android/launcher3/logging/LoggerUtils.java
index f352b46..b004edf 100644
--- a/src/com/android/launcher3/logging/LoggerUtils.java
+++ b/src/com/android/launcher3/logging/LoggerUtils.java
@@ -44,7 +44,7 @@
public class LoggerUtils {
private static final ArrayMap<Class, SparseArray<String>> sNameCache = new ArrayMap<>();
private static final String UNKNOWN = "UNKNOWN";
- private static final int DEFAULT_PREDICTED_RANK = -100;
+ private static final int DEFAULT_PREDICTED_RANK = 10000;
public static String getFieldName(int value, Class c) {
SparseArray<String> cache;
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 571d41a..23ec459 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -60,6 +60,7 @@
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderGridOrganizer;
+import com.android.launcher3.folder.FolderNameProvider;
import com.android.launcher3.icons.ComponentWithLabel;
import com.android.launcher3.icons.ComponentWithLabel.ComponentCachingLogic;
import com.android.launcher3.icons.IconCache;
@@ -76,6 +77,7 @@
import com.android.launcher3.shortcuts.ShortcutKey;
import com.android.launcher3.shortcuts.ShortcutRequest;
import com.android.launcher3.shortcuts.ShortcutRequest.QueryResult;
+import com.android.launcher3.testing.TestProtocol;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.IOUtils;
import com.android.launcher3.util.LooperIdleLock;
@@ -168,15 +170,32 @@
}
public void run() {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
+ "LoaderTask1 " + this);
+ }
synchronized (this) {
// Skip fast if we are already stopped.
if (mStopped) {
return;
}
}
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
+ "LoaderTask2 " + this);
+ }
Object traceToken = TraceHelper.INSTANCE.beginSection(TAG);
- TimingLogger logger = new TimingLogger(TAG, "run");
+ TimingLogger logger = TestProtocol.sDebugTracing ?
+ new TimingLogger(TAG, "run") {
+ @Override
+ public void addSplit(String splitLabel) {
+ super.addSplit(splitLabel);
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
+ "LoaderTask.addSplit " + splitLabel);
+ }
+ }
+ : new TimingLogger(TAG, "run");
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
loadWorkspace(allShortcuts);
@@ -256,10 +275,19 @@
mApp.getContext(), true), mApp.getModel()::onWidgetLabelsUpdated);
logger.addSplit("save widgets in icon cache");
+ // fifth step
+ if (FeatureFlags.FOLDER_NAME_SUGGEST.get()) {
+ loadFolderNames();
+ }
+
verifyNotStopped();
updateHandler.finish();
logger.addSplit("finish icon update");
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
+ "LoaderTask3 " + this);
+ }
transaction.commit();
} catch (CancellationException e) {
// Loader stopped, ignore
@@ -898,6 +926,21 @@
return allShortcuts;
}
+ private void loadFolderNames() {
+ FolderNameProvider provider = FolderNameProvider.newInstance(mApp.getContext());
+
+ synchronized (mBgDataModel) {
+ for (int i = 0; i < mBgDataModel.folders.size(); i++) {
+ String[] suggestedOut = new String[FolderNameProvider.SUGGEST_MAX];
+ FolderInfo info = mBgDataModel.folders.valueAt(i);
+ if (info.suggestedFolderNames == null) {
+ provider.getSuggestedFolderName(mApp.getContext(), info.contents, suggestedOut);
+ info.suggestedFolderNames = new Intent().putExtra("suggest", suggestedOut);
+ }
+ }
+ }
+ }
+
public static boolean isValidProvider(AppWidgetProviderInfo provider) {
return (provider != null) && (provider.provider != null)
&& (provider.provider.getPackageName() != null);
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index 98f7fd8..d9bd714 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -40,8 +40,8 @@
import android.widget.FrameLayout;
import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.InsettableFrameLayout;
-import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
@@ -57,14 +57,16 @@
/**
* A container for shortcuts to deep links and notifications associated with an app.
+ *
+ * @param <T> The activity on with the popup shows
*/
-public abstract class ArrowPopup extends AbstractFloatingView {
+public abstract class ArrowPopup<T extends BaseDraggingActivity> extends AbstractFloatingView {
private final Rect mTempRect = new Rect();
protected final LayoutInflater mInflater;
private final float mOutlineRadius;
- protected final Launcher mLauncher;
+ protected final T mLauncher;
protected final boolean mIsRtl;
private final int mArrowOffset;
@@ -83,7 +85,7 @@
super(context, attrs, defStyleAttr);
mInflater = LayoutInflater.from(context);
mOutlineRadius = Themes.getDialogCornerRadius(context);
- mLauncher = Launcher.getLauncher(context);
+ mLauncher = BaseDraggingActivity.fromContext(context);
mIsRtl = Utilities.isRtl(getResources());
setClipToOutline(true);
@@ -120,16 +122,22 @@
}
}
- public <T extends View> T inflateAndAdd(int resId, ViewGroup container) {
+ /**
+ * Utility method for inflating and adding a view
+ */
+ public <R extends View> R inflateAndAdd(int resId, ViewGroup container) {
View view = mInflater.inflate(resId, container, false);
container.addView(view);
- return (T) view;
+ return (R) view;
}
- public <T extends View> T inflateAndAdd(int resId, ViewGroup container, int index) {
+ /**
+ * Utility method for inflating and adding a view
+ */
+ public <R extends View> R inflateAndAdd(int resId, ViewGroup container, int index) {
View view = mInflater.inflate(resId, container, false);
container.addView(view, index);
- return (T) view;
+ return (R) view;
}
/**
diff --git a/src/com/android/launcher3/popup/PopupContainerWithArrow.java b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
index b764a07..05ea694 100644
--- a/src/com/android/launcher3/popup/PopupContainerWithArrow.java
+++ b/src/com/android/launcher3/popup/PopupContainerWithArrow.java
@@ -44,6 +44,7 @@
import android.widget.ImageView;
import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
@@ -65,7 +66,6 @@
import com.android.launcher3.popup.PopupDataProvider.PopupDataChangeListener;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
-import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.touch.ItemLongClickListener;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.util.ShortcutUtil;
@@ -74,22 +74,22 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* A container for shortcuts to deep links and notifications associated with an app.
+ *
+ * @param <T> The activity on with the popup shows
*/
-public class PopupContainerWithArrow extends ArrowPopup implements DragSource,
- DragController.DragListener, View.OnLongClickListener,
- View.OnTouchListener, PopupDataChangeListener {
+public class PopupContainerWithArrow<T extends BaseDraggingActivity> extends ArrowPopup<T>
+ implements DragSource, DragController.DragListener {
private final List<DeepShortcutView> mShortcuts = new ArrayList<>();
private final PointF mInterceptTouchDown = new PointF();
- protected final Point mIconLastTouchPos = new Point();
private final int mStartDragThreshold;
- private final LauncherAccessibilityDelegate mAccessibilityDelegate;
private BubbleTextView mOriginalIcon;
private NotificationItemView mNotificationItemView;
@@ -97,11 +97,13 @@
private ViewGroup mSystemShortcutContainer;
+ protected PopupItemDragHandler mPopupItemDragHandler;
+ protected LauncherAccessibilityDelegate mAccessibilityDelegate;
+
public PopupContainerWithArrow(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
mStartDragThreshold = getResources().getDimensionPixelSize(
R.dimen.deep_shortcuts_start_drag_threshold);
- mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(mLauncher);
}
public PopupContainerWithArrow(Context context, AttributeSet attrs) {
@@ -117,18 +119,6 @@
}
@Override
- protected void onAttachedToWindow() {
- super.onAttachedToWindow();
- mLauncher.getPopupDataProvider().setChangeListener(this);
- }
-
- @Override
- protected void onDetachedFromWindow() {
- super.onDetachedFromWindow();
- mLauncher.getPopupDataProvider().setChangeListener(null);
- }
-
- @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
mInterceptTouchDown.set(ev.getX(), ev.getY());
@@ -168,11 +158,15 @@
public OnClickListener getItemClickListener() {
return (view) -> {
- ItemClickHandler.INSTANCE.onClick(view);
+ mLauncher.getItemOnClickListener().onClick(view);
close(true);
};
}
+ public PopupItemDragHandler getItemDragHandler() {
+ return mPopupItemDragHandler;
+ }
+
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
@@ -201,18 +195,35 @@
icon.clearFocus();
return null;
}
- ItemInfo itemInfo = (ItemInfo) icon.getTag();
- if (!ShortcutUtil.supportsShortcuts(itemInfo)) {
+ ItemInfo item = (ItemInfo) icon.getTag();
+ if (!ShortcutUtil.supportsShortcuts(item)) {
return null;
}
final PopupContainerWithArrow container =
(PopupContainerWithArrow) launcher.getLayoutInflater().inflate(
R.layout.popup_container, launcher.getDragLayer(), false);
- container.populateAndShow(icon, itemInfo);
+ container.configureForLauncher(launcher);
+
+ PopupDataProvider popupDataProvider = launcher.getPopupDataProvider();
+ container.populateAndShow(icon,
+ popupDataProvider.getShortcutCountForItem(item),
+ popupDataProvider.getNotificationKeysForItem(item),
+ launcher.getSupportedShortcuts()
+ .map(s -> s.getShortcut(launcher, item))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList()));
+ launcher.refreshAndBindWidgetsForPackageUser(PackageUserKey.fromItemInfo(item));
return container;
}
+ private void configureForLauncher(Launcher launcher) {
+ addOnAttachStateChangeListener(new LiveUpdateHandler(launcher));
+ mPopupItemDragHandler = new LauncherPopupItemDragHandler(launcher, this);
+ mAccessibilityDelegate = new ShortcutMenuAccessibilityDelegate(launcher);
+ launcher.getDragController().addDragListener(this);
+ }
+
@Override
protected void onInflationComplete(boolean isReversed) {
if (isReversed && mNotificationItemView != null) {
@@ -234,23 +245,8 @@
}
}
- protected void populateAndShow(BubbleTextView icon, ItemInfo item) {
- PopupDataProvider popupDataProvider = mLauncher.getPopupDataProvider();
- populateAndShow(icon,
- popupDataProvider.getShortcutCountForItem(item),
- popupDataProvider.getNotificationKeysForItem(item),
- mLauncher.getSupportedShortcuts()
- .map(s -> s.getShortcut(mLauncher, item))
- .filter(s -> s != null)
- .collect(Collectors.toList()));
- }
-
- public ViewGroup getSystemShortcutContainerForTesting() {
- return mSystemShortcutContainer;
- }
-
@TargetApi(Build.VERSION_CODES.P)
- protected void populateAndShow(final BubbleTextView originalIcon, int shortcutCount,
+ public void populateAndShow(final BubbleTextView originalIcon, int shortcutCount,
final List<NotificationKeyData> notificationKeys, List<SystemShortcut> systemShortcuts) {
mNumNotifications = notificationKeys.size();
mOriginalIcon = originalIcon;
@@ -316,7 +312,6 @@
setAccessibilityPaneTitle(getTitleForAccessibility());
}
- mLauncher.getDragController().addDragListener(this);
mOriginalIcon.setForceHideDot(true);
// All views are added. Animate layout from now on.
@@ -390,44 +385,6 @@
}
}
- @Override
- public void onWidgetsBound() {
- ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
- SystemShortcut widgetInfo = SystemShortcut.WIDGETS.getShortcut(mLauncher, itemInfo);
- View widgetsView = null;
- int count = mSystemShortcutContainer.getChildCount();
- for (int i = 0; i < count; i++) {
- View systemShortcutView = mSystemShortcutContainer.getChildAt(i);
- if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) {
- widgetsView = systemShortcutView;
- break;
- }
- }
-
- if (widgetInfo != null && widgetsView == null) {
- // We didn't have any widgets cached but now there are some, so enable the shortcut.
- if (mSystemShortcutContainer != this) {
- initializeSystemShortcut(
- R.layout.system_shortcut_icon_only, mSystemShortcutContainer, widgetInfo);
- } else {
- // If using the expanded system shortcut (as opposed to just the icon), we need to
- // reopen the container to ensure measurements etc. all work out. While this could
- // be quite janky, in practice the user would typically see a small flicker as the
- // animation restarts partway through, and this is a very rare edge case anyway.
- close(false);
- PopupContainerWithArrow.showForIcon(mOriginalIcon);
- }
- } else if (widgetInfo == null && widgetsView != null) {
- // No widgets exist, but we previously added the shortcut so remove it.
- if (mSystemShortcutContainer != this) {
- mSystemShortcutContainer.removeView(widgetsView);
- } else {
- close(false);
- PopupContainerWithArrow.showForIcon(mOriginalIcon);
- }
- }
- }
-
private void initializeSystemShortcut(int resId, ViewGroup container, SystemShortcut info) {
View view = inflateAndAdd(
resId, container, getInsertIndexForSystemShortcut(container, info));
@@ -498,18 +455,6 @@
};
}
- /**
- * Updates the notification header if the original icon's dot updated.
- */
- @Override
- public void onNotificationDotsUpdated(Predicate<PackageUserKey> updatedDots) {
- ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
- PackageUserKey packageUser = PackageUserKey.fromItemInfo(itemInfo);
- if (updatedDots.test(packageUser)) {
- updateNotificationHeader();
- }
- }
-
private void updateNotificationHeader() {
ItemInfoWithIcon itemInfo = (ItemInfoWithIcon) mOriginalIcon.getTag();
DotInfo dotInfo = mLauncher.getDotInfoForItem(itemInfo);
@@ -520,25 +465,6 @@
}
@Override
- public void trimNotifications(Map<PackageUserKey, DotInfo> updatedDots) {
- if (mNotificationItemView == null) {
- return;
- }
- ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag();
- DotInfo dotInfo = updatedDots.get(PackageUserKey.fromItemInfo(originalInfo));
- if (dotInfo == null || dotInfo.getNotificationKeys().size() == 0) {
- // No more notifications, remove the notification views and expand all shortcuts.
- mNotificationItemView.removeAllViews();
- mNotificationItemView = null;
- updateHiddenShortcuts();
- updateDividers();
- } else {
- mNotificationItemView.trimNotifications(
- NotificationKeyData.extractKeysOnly(dotInfo.getNotificationKeys()));
- }
- }
-
- @Override
public void onDropCompleted(View target, DragObject d, boolean success) { }
@Override
@@ -592,47 +518,164 @@
super.closeComplete();
}
- @Override
- public boolean onTouch(View v, MotionEvent ev) {
- // Touched a shortcut, update where it was touched so we can drag from there on long click.
- switch (ev.getAction()) {
- case MotionEvent.ACTION_DOWN:
- case MotionEvent.ACTION_MOVE:
- mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
- break;
- }
- return false;
- }
-
- @Override
- public boolean onLongClick(View v) {
- if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
- // Return early if not the correct view
- if (!(v.getParent() instanceof DeepShortcutView)) return false;
-
- // Long clicked on a shortcut.
- DeepShortcutView sv = (DeepShortcutView) v.getParent();
- sv.setWillDrawIcon(false);
-
- // Move the icon to align with the center-top of the touch point
- Point iconShift = new Point();
- iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
- iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
-
- DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
- this, sv.getFinalInfo(),
- new ShortcutDragPreviewProvider(sv.getIconView(), iconShift), new DragOptions());
- dv.animateShift(-iconShift.x, -iconShift.y);
-
- // TODO: support dragging from within folder without having to close it
- AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
- return false;
- }
-
/**
* Returns a PopupContainerWithArrow which is already open or null
*/
- public static PopupContainerWithArrow getOpen(Launcher launcher) {
+ public static PopupContainerWithArrow getOpen(BaseDraggingActivity launcher) {
return getOpenView(launcher, TYPE_ACTION_POPUP);
}
+
+ /**
+ * Utility class to handle updates while the popup is visible (like widgets and
+ * notification changes)
+ */
+ private class LiveUpdateHandler implements
+ PopupDataChangeListener, View.OnAttachStateChangeListener {
+
+ private final Launcher mLauncher;
+
+ LiveUpdateHandler(Launcher launcher) {
+ mLauncher = launcher;
+ }
+
+ @Override
+ public void onViewAttachedToWindow(View view) {
+ mLauncher.getPopupDataProvider().setChangeListener(this);
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(View view) {
+ mLauncher.getPopupDataProvider().setChangeListener(null);
+ }
+
+ @Override
+ public void onWidgetsBound() {
+ ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
+ SystemShortcut widgetInfo = SystemShortcut.WIDGETS.getShortcut(mLauncher, itemInfo);
+ View widgetsView = null;
+ int count = mSystemShortcutContainer.getChildCount();
+ for (int i = 0; i < count; i++) {
+ View systemShortcutView = mSystemShortcutContainer.getChildAt(i);
+ if (systemShortcutView.getTag() instanceof SystemShortcut.Widgets) {
+ widgetsView = systemShortcutView;
+ break;
+ }
+ }
+
+ if (widgetInfo != null && widgetsView == null) {
+ // We didn't have any widgets cached but now there are some, so enable the shortcut.
+ if (mSystemShortcutContainer != PopupContainerWithArrow.this) {
+ initializeSystemShortcut(R.layout.system_shortcut_icon_only,
+ mSystemShortcutContainer, widgetInfo);
+ } else {
+ // If using the expanded system shortcut (as opposed to just the icon), we need
+ // to reopen the container to ensure measurements etc. all work out. While this
+ // could be quite janky, in practice the user would typically see a small
+ // flicker as the animation restarts partway through, and this is a very rare
+ // edge case anyway.
+ close(false);
+ PopupContainerWithArrow.showForIcon(mOriginalIcon);
+ }
+ } else if (widgetInfo == null && widgetsView != null) {
+ // No widgets exist, but we previously added the shortcut so remove it.
+ if (mSystemShortcutContainer != PopupContainerWithArrow.this) {
+ mSystemShortcutContainer.removeView(widgetsView);
+ } else {
+ close(false);
+ PopupContainerWithArrow.showForIcon(mOriginalIcon);
+ }
+ }
+ }
+
+ /**
+ * Updates the notification header if the original icon's dot updated.
+ */
+ @Override
+ public void onNotificationDotsUpdated(Predicate<PackageUserKey> updatedDots) {
+ ItemInfo itemInfo = (ItemInfo) mOriginalIcon.getTag();
+ PackageUserKey packageUser = PackageUserKey.fromItemInfo(itemInfo);
+ if (updatedDots.test(packageUser)) {
+ updateNotificationHeader();
+ }
+ }
+
+
+ @Override
+ public void trimNotifications(Map<PackageUserKey, DotInfo> updatedDots) {
+ if (mNotificationItemView == null) {
+ return;
+ }
+ ItemInfo originalInfo = (ItemInfo) mOriginalIcon.getTag();
+ DotInfo dotInfo = updatedDots.get(PackageUserKey.fromItemInfo(originalInfo));
+ if (dotInfo == null || dotInfo.getNotificationKeys().size() == 0) {
+ // No more notifications, remove the notification views and expand all shortcuts.
+ mNotificationItemView.removeAllViews();
+ mNotificationItemView = null;
+ updateHiddenShortcuts();
+ updateDividers();
+ } else {
+ mNotificationItemView.trimNotifications(
+ NotificationKeyData.extractKeysOnly(dotInfo.getNotificationKeys()));
+ }
+ }
+ }
+
+ /**
+ * Handler to control drag-and-drop for popup items
+ */
+ public interface PopupItemDragHandler extends OnLongClickListener, OnTouchListener { }
+
+ /**
+ * Drag and drop handler for popup items in Launcher activity
+ */
+ public static class LauncherPopupItemDragHandler implements PopupItemDragHandler {
+
+ protected final Point mIconLastTouchPos = new Point();
+ private final Launcher mLauncher;
+ private final PopupContainerWithArrow mContainer;
+
+ LauncherPopupItemDragHandler(Launcher launcher, PopupContainerWithArrow container) {
+ mLauncher = launcher;
+ mContainer = container;
+ }
+
+ @Override
+ public boolean onTouch(View v, MotionEvent ev) {
+ // Touched a shortcut, update where it was touched so we can drag from there on
+ // long click.
+ switch (ev.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ case MotionEvent.ACTION_MOVE:
+ mIconLastTouchPos.set((int) ev.getX(), (int) ev.getY());
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onLongClick(View v) {
+ if (!ItemLongClickListener.canStartDrag(mLauncher)) return false;
+ // Return early if not the correct view
+ if (!(v.getParent() instanceof DeepShortcutView)) return false;
+
+ // Long clicked on a shortcut.
+ DeepShortcutView sv = (DeepShortcutView) v.getParent();
+ sv.setWillDrawIcon(false);
+
+ // Move the icon to align with the center-top of the touch point
+ Point iconShift = new Point();
+ iconShift.x = mIconLastTouchPos.x - sv.getIconCenter().x;
+ iconShift.y = mIconLastTouchPos.y - mLauncher.getDeviceProfile().iconSizePx;
+
+ DragView dv = mLauncher.getWorkspace().beginDragShared(sv.getIconView(),
+ mContainer, sv.getFinalInfo(),
+ new ShortcutDragPreviewProvider(sv.getIconView(), iconShift),
+ new DragOptions());
+ dv.animateShift(-iconShift.x, -iconShift.y);
+
+ // TODO: support dragging from within folder without having to close it
+ AbstractFloatingView.closeOpenContainer(mLauncher, AbstractFloatingView.TYPE_FOLDER);
+ return false;
+ }
+ }
}
diff --git a/src/com/android/launcher3/popup/PopupDataProvider.java b/src/com/android/launcher3/popup/PopupDataProvider.java
index c5aa836..1092c7b 100644
--- a/src/com/android/launcher3/popup/PopupDataProvider.java
+++ b/src/com/android/launcher3/popup/PopupDataProvider.java
@@ -24,7 +24,6 @@
import androidx.annotation.Nullable;
import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
import com.android.launcher3.dot.DotInfo;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.notification.NotificationKeyData;
@@ -41,6 +40,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
@@ -52,7 +52,7 @@
private static final boolean LOGD = false;
private static final String TAG = "PopupDataProvider";
- private final Launcher mLauncher;
+ private final Consumer<Predicate<PackageUserKey>> mNotificationDotsChangeListener;
/** Maps launcher activity components to a count of how many shortcuts they have. */
private HashMap<ComponentKey, Integer> mDeepShortcutMap = new HashMap<>();
@@ -63,12 +63,12 @@
private PopupDataChangeListener mChangeListener = PopupDataChangeListener.INSTANCE;
- public PopupDataProvider(Launcher launcher) {
- mLauncher = launcher;
+ public PopupDataProvider(Consumer<Predicate<PackageUserKey>> notificationDotsChangeListener) {
+ mNotificationDotsChangeListener = notificationDotsChangeListener;
}
private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
- mLauncher.updateNotificationDots(updatedDots);
+ mNotificationDotsChangeListener.accept(updatedDots);
mChangeListener.onNotificationDotsUpdated(updatedDots);
}
diff --git a/src/com/android/launcher3/popup/PopupPopulator.java b/src/com/android/launcher3/popup/PopupPopulator.java
index 947f49d..9faeb40 100644
--- a/src/com/android/launcher3/popup/PopupPopulator.java
+++ b/src/com/android/launcher3/popup/PopupPopulator.java
@@ -24,8 +24,8 @@
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
+import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ItemInfo;
-import com.android.launcher3.Launcher;
import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.icons.LauncherIcons;
import com.android.launcher3.notification.NotificationInfo;
@@ -33,7 +33,6 @@
import com.android.launcher3.notification.NotificationListener;
import com.android.launcher3.shortcuts.DeepShortcutView;
import com.android.launcher3.shortcuts.ShortcutRequest;
-import com.android.launcher3.util.PackageUserKey;
import java.util.ArrayList;
import java.util.Collections;
@@ -123,7 +122,11 @@
return filteredShortcuts;
}
- public static Runnable createUpdateRunnable(final Launcher launcher, final ItemInfo originalInfo,
+ /**
+ * Returns a runnable to update the provided shortcuts and notifications
+ */
+ public static Runnable createUpdateRunnable(final BaseDraggingActivity launcher,
+ final ItemInfo originalInfo,
final Handler uiHandler, final PopupContainerWithArrow container,
final List<DeepShortcutView> shortcutViews,
final List<NotificationKeyData> notificationKeys) {
@@ -162,11 +165,6 @@
final DeepShortcutView view = shortcutViews.get(i);
uiHandler.post(() -> view.applyShortcutInfo(si, shortcut, container));
}
-
- // This ensures that mLauncher.getWidgetsForPackageUser()
- // doesn't return null (it puts all the widgets in memory).
- uiHandler.post(() -> launcher.refreshAndBindWidgetsForPackageUser(
- PackageUserKey.fromItemInfo(originalInfo)));
};
}
}
diff --git a/src/com/android/launcher3/popup/SystemShortcut.java b/src/com/android/launcher3/popup/SystemShortcut.java
index 48f1c49..21c5ac5 100644
--- a/src/com/android/launcher3/popup/SystemShortcut.java
+++ b/src/com/android/launcher3/popup/SystemShortcut.java
@@ -16,14 +16,10 @@
import com.android.launcher3.BaseDraggingActivity;
import com.android.launcher3.ItemInfo;
import com.android.launcher3.Launcher;
-import com.android.launcher3.LauncherSettings;
import com.android.launcher3.R;
import com.android.launcher3.WorkspaceItemInfo;
-import com.android.launcher3.config.FeatureFlags;
-import com.android.launcher3.model.AppLaunchTracker;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.userevent.nano.LauncherLogProto.Action;
-import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
import com.android.launcher3.userevent.nano.LauncherLogProto.ControlType;
import com.android.launcher3.util.InstantAppResolver;
import com.android.launcher3.util.PackageManagerHelper;
@@ -176,33 +172,6 @@
}
}
- public static final Factory<Launcher> DISMISS_PREDICTION = (launcher, itemInfo) -> {
- if (!FeatureFlags.ENABLE_PREDICTION_DISMISS.get()) return null;
- if (itemInfo.container != LauncherSettings.Favorites.CONTAINER_PREDICTION
- && itemInfo.container != LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION) {
- return null;
- }
- return new DismissPrediction(launcher, itemInfo);
- };
-
- public static class DismissPrediction extends SystemShortcut<Launcher> {
- public DismissPrediction(Launcher launcher, ItemInfo itemInfo) {
- super(R.drawable.ic_remove_no_shadow, R.string.dismiss_prediction_label, launcher,
- itemInfo);
- }
-
- @Override
- public void onClick(View view) {
- PopupContainerWithArrow.closeAllOpenViews(mTarget);
- mTarget.getUserEventDispatcher().logActionOnControl(Action.Touch.TAP,
- ControlType.DISMISS_PREDICTION, ContainerType.DEEPSHORTCUTS);
- AppLaunchTracker.INSTANCE.get(view.getContext()).onDismissApp(
- mItemInfo.getTargetComponent(),
- mItemInfo.user,
- AppLaunchTracker.CONTAINER_PREDICTIONS);
- }
- }
-
public static void dismissTaskMenuView(BaseDraggingActivity activity) {
AbstractFloatingView.closeOpenViews(activity, true,
AbstractFloatingView.TYPE_ALL & ~AbstractFloatingView.TYPE_REBIND_SAFE);
diff --git a/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
new file mode 100644
index 0000000..54b7fb9
--- /dev/null
+++ b/src/com/android/launcher3/secondarydisplay/PinnedAppsAdapter.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.secondarydisplay;
+
+import static android.content.Context.MODE_PRIVATE;
+
+import android.content.ComponentName;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.os.Process;
+import android.os.UserHandle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsStore;
+import com.android.launcher3.allapps.AppInfoComparator;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.popup.SystemShortcut;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.Executors;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * Adapter to manage pinned apps and show then in a grid.
+ */
+public class PinnedAppsAdapter extends BaseAdapter implements OnSharedPreferenceChangeListener {
+
+ private static final String PINNED_APPS_KEY = "pinned_apps";
+
+ private final SecondaryDisplayLauncher mLauncher;
+ private final OnClickListener mOnClickListener;
+ private final OnLongClickListener mOnLongClickListener;
+ private final SharedPreferences mPrefs;
+ private final AllAppsStore mAllAppsList;
+ private final AppInfoComparator mAppNameComparator;
+
+ private final Set<ComponentKey> mPinnedApps = new HashSet<>();
+ private final ArrayList<AppInfo> mItems = new ArrayList<>();
+
+ public PinnedAppsAdapter(SecondaryDisplayLauncher launcher, AllAppsStore allAppsStore,
+ OnLongClickListener onLongClickListener) {
+ mLauncher = launcher;
+ mOnClickListener = launcher.getItemOnClickListener();
+ mOnLongClickListener = onLongClickListener;
+ mAllAppsList = allAppsStore;
+ mPrefs = launcher.getSharedPreferences(PINNED_APPS_KEY, MODE_PRIVATE);
+ mAppNameComparator = new AppInfoComparator(launcher);
+
+ mAllAppsList.addUpdateListener(this::createFilteredAppsList);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
+ if (PINNED_APPS_KEY.equals(key)) {
+ Executors.MODEL_EXECUTOR.submit(() -> {
+ Set<ComponentKey> apps = prefs.getStringSet(key, Collections.emptySet())
+ .stream()
+ .map(this::parseComponentKey)
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Executors.MAIN_EXECUTOR.submit(() -> {
+ mPinnedApps.clear();
+ mPinnedApps.addAll(apps);
+ createFilteredAppsList();
+ });
+ });
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getCount() {
+ return mItems.size();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public AppInfo getItem(int position) {
+ return mItems.get(position);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public View getView(int position, View view, ViewGroup parent) {
+ BubbleTextView icon;
+ if (view instanceof BubbleTextView) {
+ icon = (BubbleTextView) view;
+ } else {
+ icon = (BubbleTextView) LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.app_icon, parent, false);
+ icon.setOnClickListener(mOnClickListener);
+ icon.setOnLongClickListener(mOnLongClickListener);
+ icon.setLongPressTimeoutFactor(1f);
+ int padding = mLauncher.getDeviceProfile().edgeMarginPx;
+ icon.setPadding(padding, padding, padding, padding);
+ }
+
+ icon.applyFromApplicationInfo(mItems.get(position));
+ return icon;
+ }
+
+ private void createFilteredAppsList() {
+ mItems.clear();
+ mPinnedApps.stream().map(mAllAppsList::getApp)
+ .filter(Objects::nonNull).forEach(mItems::add);
+ mItems.sort(mAppNameComparator);
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Initialized the pinned apps list and starts listening for changes
+ */
+ public void init() {
+ mPrefs.registerOnSharedPreferenceChangeListener(this);
+ onSharedPreferenceChanged(mPrefs, PINNED_APPS_KEY);
+ }
+
+ /**
+ * Stops listening for any pinned apps changes
+ */
+ public void destroy() {
+ mPrefs.unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ private void update(ItemInfo info, Function<ComponentKey, Boolean> op) {
+ ComponentKey key = new ComponentKey(info.getTargetComponent(), info.user);
+ if (op.apply(key)) {
+ createFilteredAppsList();
+ Set<ComponentKey> copy = new HashSet<>(mPinnedApps);
+ Executors.MODEL_EXECUTOR.submit(() ->
+ mPrefs.edit().putStringSet(PINNED_APPS_KEY,
+ copy.stream().map(this::encode).collect(Collectors.toSet()))
+ .apply());
+ }
+ }
+
+ private ComponentKey parseComponentKey(String string) {
+ try {
+ String[] parts = string.split("#");
+ UserHandle user;
+ if (parts.length > 2) {
+ user = UserCache.INSTANCE.get(mLauncher)
+ .getUserForSerialNumber(Long.parseLong(parts[2]));
+ } else {
+ user = Process.myUserHandle();
+ }
+ ComponentName cn = ComponentName.unflattenFromString(parts[0]);
+ return new ComponentKey(cn, user);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ private String encode(ComponentKey key) {
+ return key.componentName.flattenToShortString() + "#"
+ + UserCache.INSTANCE.get(mLauncher).getSerialNumberForUser(key.user);
+ }
+
+ /**
+ * Returns a system shortcut to pin/unpin a shortcut
+ */
+ public SystemShortcut getSystemShortcut(ItemInfo info) {
+ return new PinUnPinShortcut(mLauncher, info,
+ mPinnedApps.contains(new ComponentKey(info.getTargetComponent(), info.user)));
+ }
+
+ private class PinUnPinShortcut extends SystemShortcut<SecondaryDisplayLauncher> {
+
+ private final boolean mIsPinned;
+
+ PinUnPinShortcut(SecondaryDisplayLauncher target, ItemInfo info, boolean isPinned) {
+ super(isPinned ? R.drawable.ic_remove_no_shadow : R.drawable.ic_pin,
+ isPinned ? R.string.remove_drop_target_label : R.string.action_add_to_workspace,
+ target, info);
+ mIsPinned = isPinned;
+ }
+
+ @Override
+ public void onClick(View view) {
+ if (mIsPinned) {
+ update(mItemInfo, mPinnedApps::remove);
+ } else {
+ update(mItemInfo, mPinnedApps::add);
+ }
+ AbstractFloatingView.closeAllOpenViews(mLauncher);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
new file mode 100644
index 0000000..1cc01f4
--- /dev/null
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDisplayLauncher.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.secondarydisplay;
+
+import static com.android.launcher3.model.AppLaunchTracker.CONTAINER_ALL_APPS;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewAnimationUtils;
+import android.view.inputmethod.InputMethodManager;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.AppInfo;
+import com.android.launcher3.BaseDraggingActivity;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.LauncherAppState;
+import com.android.launcher3.LauncherAppWidgetInfo;
+import com.android.launcher3.LauncherModel;
+import com.android.launcher3.PromiseAppInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.WorkspaceItemInfo;
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.model.BgDataModel;
+import com.android.launcher3.popup.PopupDataProvider;
+import com.android.launcher3.util.ComponentKey;
+import com.android.launcher3.util.IntArray;
+import com.android.launcher3.util.ItemInfoMatcher;
+import com.android.launcher3.util.Themes;
+import com.android.launcher3.util.ViewOnDrawExecutor;
+import com.android.launcher3.views.BaseDragLayer;
+import com.android.launcher3.widget.WidgetListRowEntry;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Launcher activity for secondary displays
+ */
+public class SecondaryDisplayLauncher extends BaseDraggingActivity
+ implements BgDataModel.Callbacks {
+
+ private LauncherModel mModel;
+
+ private BaseDragLayer mDragLayer;
+ private AllAppsContainerView mAppsView;
+ private View mAppsButton;
+
+ private PopupDataProvider mPopupDataProvider;
+
+ private boolean mAppDrawerShown = false;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mModel = LauncherAppState.getInstance(this).getModel();
+ if (getWindow().getDecorView().isAttachedToWindow()) {
+ initUi();
+ }
+ }
+
+ @Override
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ initUi();
+ }
+
+ private void initUi() {
+ if (mDragLayer != null) {
+ return;
+ }
+ InvariantDeviceProfile mainIdp = LauncherAppState.getIDP(this);
+ InvariantDeviceProfile currentDisplayIdp =
+ new InvariantDeviceProfile(this, getWindow().getDecorView().getDisplay());
+
+ // Pick the device profile with the smaller icon size so that the cached icons are
+ // shown properly
+ if (mainIdp.iconBitmapSize <= currentDisplayIdp.iconBitmapSize) {
+ mDeviceProfile = mainIdp.getDeviceProfile(this).copy(this);
+ } else {
+ mDeviceProfile = currentDisplayIdp.getDeviceProfile(this);
+ }
+
+ setContentView(R.layout.secondary_launcher);
+ mDragLayer = findViewById(R.id.drag_layer);
+ mAppsView = findViewById(R.id.apps_view);
+ mAppsButton = findViewById(R.id.all_apps_button);
+
+ mPopupDataProvider = new PopupDataProvider(
+ mAppsView.getAppsStore()::updateNotificationDots);
+
+ mModel.addCallbacksAndLoad(this);
+ }
+
+ @Override
+ public void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+
+ if (Intent.ACTION_MAIN.equals(intent.getAction())) {
+ // Hide keyboard.
+ final View v = getWindow().peekDecorView();
+ if (v != null && v.getWindowToken() != null) {
+ getSystemService(InputMethodManager.class).hideSoftInputFromWindow(
+ v.getWindowToken(), 0);
+ }
+ }
+
+ // A new intent will bring the launcher to top. Hide the app drawer to reset the state.
+ showAppDrawer(false);
+ }
+
+ @Override
+ public void onBackPressed() {
+ if (finishAutoCancelActionMode()) {
+ return;
+ }
+
+ // Note: There should be at most one log per method call. This is enforced implicitly
+ // by using if-else statements.
+ AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(this);
+ if (topView != null && topView.onBackPressed()) {
+ // Handled by the floating view.
+ } else {
+ showAppDrawer(false);
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mModel.removeCallbacks(this);
+ }
+
+ public boolean isAppDrawerShown() {
+ return mAppDrawerShown;
+ }
+
+ public AllAppsContainerView getAppsView() {
+ return mAppsView;
+ }
+
+ @Override
+ public <T extends View> T getOverviewPanel() {
+ return null;
+ }
+
+ @Override
+ public View getRootView() {
+ return mDragLayer;
+ }
+
+ @Override
+ public ActivityOptions getActivityLaunchOptions(View v) {
+ return null;
+ }
+
+ @Override
+ protected void reapplyUi() { }
+
+ @Override
+ public BaseDragLayer getDragLayer() {
+ return mDragLayer;
+ }
+
+ @Override
+ public int getPageToBindSynchronously() {
+ return 0;
+ }
+
+ @Override
+ public void clearPendingBinds() { }
+
+ @Override
+ public void startBinding() { }
+
+ @Override
+ public void bindItems(List<ItemInfo> shortcuts, boolean forceAnimateIcons) { }
+
+ @Override
+ public void bindScreens(IntArray orderedScreenIds) { }
+
+ @Override
+ public void finishFirstPageBind(ViewOnDrawExecutor executor) {
+ if (executor != null) {
+ executor.onLoadAnimationCompleted();
+ }
+ }
+
+ @Override
+ public void finishBindingItems(int pageBoundFirst) { }
+
+ @Override
+ public void preAddApps() { }
+
+ @Override
+ public void bindAppsAdded(IntArray newScreens, ArrayList<ItemInfo> addNotAnimated,
+ ArrayList<ItemInfo> addAnimated) { }
+
+ @Override
+ public void bindPromiseAppProgressUpdated(PromiseAppInfo app) {
+ mAppsView.getAppsStore().updatePromiseAppProgress(app);
+ }
+
+ @Override
+ public void bindWorkspaceItemsChanged(ArrayList<WorkspaceItemInfo> updated) { }
+
+ @Override
+ public void bindWidgetsRestored(ArrayList<LauncherAppWidgetInfo> widgets) { }
+
+ @Override
+ public void bindRestoreItemsChange(HashSet<ItemInfo> updates) { }
+
+ @Override
+ public void bindWorkspaceComponentsRemoved(ItemInfoMatcher matcher) { }
+
+ @Override
+ public void bindAllWidgets(ArrayList<WidgetListRowEntry> widgets) { }
+
+ @Override
+ public void onPageBoundSynchronously(int page) { }
+
+ @Override
+ public void executeOnNextDraw(ViewOnDrawExecutor executor) {
+ executor.attachTo(getDragLayer(), false, null);
+ }
+
+ /**
+ * Called when apps-button is clicked
+ */
+ public void onAppsButtonClicked(View v) {
+ showAppDrawer(true);
+ }
+
+ /**
+ * Show/hide app drawer card with animation.
+ */
+ public void showAppDrawer(boolean show) {
+ if (show == mAppDrawerShown) {
+ return;
+ }
+
+ float openR = (float) Math.hypot(mAppsView.getWidth(), mAppsView.getHeight());
+ float closeR = Themes.getDialogCornerRadius(this);
+ float startR = mAppsButton.getWidth() / 2f;
+
+ float[] buttonPos = new float[] { startR, startR};
+ mDragLayer.getDescendantCoordRelativeToSelf(mAppsButton, buttonPos);
+ mDragLayer.mapCoordInSelfToDescendant(mAppsView, buttonPos);
+ final Animator animator = ViewAnimationUtils.createCircularReveal(mAppsView,
+ (int) buttonPos[0], (int) buttonPos[1],
+ show ? closeR : openR, show ? openR : closeR);
+
+ if (show) {
+ mAppDrawerShown = true;
+ mAppsView.setVisibility(View.VISIBLE);
+ mAppsButton.setVisibility(View.INVISIBLE);
+ } else {
+ mAppDrawerShown = false;
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mAppsView.setVisibility(View.INVISIBLE);
+ mAppsButton.setVisibility(View.VISIBLE);
+ mAppsView.getSearchUiManager().resetSearch();
+ }
+ });
+ }
+ animator.start();
+ }
+
+ @Override
+ public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMap) {
+ mPopupDataProvider.setDeepShortcutMap(deepShortcutMap);
+ }
+
+ @Override
+ public void bindAllApplications(AppInfo[] apps) {
+ mAppsView.getAppsStore().setApps(apps);
+ }
+
+ public PopupDataProvider getPopupDataProvider() {
+ return mPopupDataProvider;
+ }
+
+ @Override
+ public OnClickListener getItemOnClickListener() {
+ return this::onIconClicked;
+ }
+
+ private void onIconClicked(View v) {
+ // Make sure that rogue clicks don't get through while allapps is launching, or after the
+ // view has detached (it's possible for this to happen if the view is removed mid touch).
+ if (v.getWindowToken() == null) return;
+
+ Object tag = v.getTag();
+ if (tag instanceof ItemInfo) {
+ ItemInfo item = (ItemInfo) tag;
+ Intent intent;
+ if (item instanceof PromiseAppInfo) {
+ PromiseAppInfo promiseAppInfo = (PromiseAppInfo) item;
+ intent = promiseAppInfo.getMarketIntent(this);
+ } else {
+ intent = item.getIntent();
+ }
+ if (intent == null) {
+ throw new IllegalArgumentException("Input must have a valid intent");
+ }
+ startActivitySafely(v, intent, item, CONTAINER_ALL_APPS);
+ }
+ }
+}
diff --git a/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
new file mode 100644
index 0000000..8fffee8
--- /dev/null
+++ b/src/com/android/launcher3/secondarydisplay/SecondaryDragLayer.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.secondarydisplay;
+
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.GridView;
+
+import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BubbleTextView;
+import com.android.launcher3.DeviceProfile;
+import com.android.launcher3.InvariantDeviceProfile;
+import com.android.launcher3.ItemInfo;
+import com.android.launcher3.R;
+import com.android.launcher3.allapps.AllAppsContainerView;
+import com.android.launcher3.popup.PopupContainerWithArrow;
+import com.android.launcher3.util.ShortcutUtil;
+import com.android.launcher3.util.TouchController;
+import com.android.launcher3.views.BaseDragLayer;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+/**
+ * DragLayer for Secondary launcher
+ */
+public class SecondaryDragLayer extends BaseDragLayer<SecondaryDisplayLauncher> {
+
+ private View mAllAppsButton;
+ private AllAppsContainerView mAppsView;
+
+ private GridView mWorkspace;
+ private PinnedAppsAdapter mPinnedAppsAdapter;
+
+ public SecondaryDragLayer(Context context, AttributeSet attrs) {
+ super(context, attrs, 1 /* alphaChannelCount */);
+ mControllers = new TouchController[] {new CloseAllAppsTouchController()};
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mAllAppsButton = findViewById(R.id.all_apps_button);
+
+ mAppsView = findViewById(R.id.apps_view);
+ mAppsView.setOnIconLongClickListener(this::onIconLongClicked);
+
+ // Setup workspace
+ mWorkspace = findViewById(R.id.workspace_grid);
+ mPinnedAppsAdapter = new PinnedAppsAdapter(mActivity, mAppsView.getAppsStore(),
+ this::onIconLongClicked);
+ mWorkspace.setAdapter(mPinnedAppsAdapter);
+ mWorkspace.setNumColumns(mActivity.getDeviceProfile().inv.numColumns);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mPinnedAppsAdapter.init();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+ mPinnedAppsAdapter.destroy();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int width = MeasureSpec.getSize(widthMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+ setMeasuredDimension(width, height);
+
+ DeviceProfile grid = mActivity.getDeviceProfile();
+ InvariantDeviceProfile idp = grid.inv;
+
+ int count = getChildCount();
+ for (int i = 0; i < count; i++) {
+ final View child = getChildAt(i);
+ if (child == mAppsView) {
+ int padding = 2 * (grid.desiredWorkspaceLeftRightMarginPx
+ + grid.cellLayoutPaddingLeftRightPx);
+ int maxWidth = grid.allAppsCellWidthPx * idp.numAllAppsColumns + padding;
+
+ int appsWidth = Math.min(width, maxWidth);
+ int appsHeight = Math.round(appsWidth * (float) height / (float) width);
+
+ mAppsView.measure(
+ makeMeasureSpec(appsWidth, EXACTLY), makeMeasureSpec(appsHeight, EXACTLY));
+
+ } else if (child == mAllAppsButton) {
+ int appsButtonSpec = makeMeasureSpec(grid.iconSizePx, EXACTLY);
+ mAllAppsButton.measure(appsButtonSpec, appsButtonSpec);
+
+ } else if (child == mWorkspace) {
+ measureChildWithMargins(mWorkspace, widthMeasureSpec, 0, heightMeasureSpec,
+ grid.iconSizePx + grid.edgeMarginPx);
+
+ } else {
+ measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
+ }
+ }
+ }
+
+ private class CloseAllAppsTouchController implements TouchController {
+
+ @Override
+ public boolean onControllerTouchEvent(MotionEvent ev) {
+ return false;
+ }
+
+ @Override
+ public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
+ if (!mActivity.isAppDrawerShown()) {
+ return false;
+ }
+
+ if (AbstractFloatingView.getTopOpenView(mActivity) != null) {
+ return false;
+ }
+
+ if (ev.getAction() == MotionEvent.ACTION_DOWN
+ && !isEventOverView(mActivity.getAppsView(), ev)) {
+ mActivity.showAppDrawer(false);
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private boolean onIconLongClicked(View v) {
+ if (!(v instanceof BubbleTextView)) {
+ return false;
+ }
+ if (PopupContainerWithArrow.getOpen(mActivity) != null) {
+ // There is already an items container open, so don't open this one.
+ v.clearFocus();
+ return false;
+ }
+ ItemInfo item = (ItemInfo) v.getTag();
+ if (!ShortcutUtil.supportsShortcuts(item)) {
+ return false;
+ }
+ final PopupContainerWithArrow container =
+ (PopupContainerWithArrow) mActivity.getLayoutInflater().inflate(
+ R.layout.popup_container, mActivity.getDragLayer(), false);
+
+ container.populateAndShow((BubbleTextView) v,
+ mActivity.getPopupDataProvider().getShortcutCountForItem(item),
+ Collections.emptyList(),
+ Arrays.asList(mPinnedAppsAdapter.getSystemShortcut(item),
+ APP_INFO.getShortcut(mActivity, item)));
+ v.getParent().requestDisallowInterceptTouchEvent(true);
+ return true;
+ }
+}
diff --git a/src/com/android/launcher3/shortcuts/DeepShortcutView.java b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
index 9274d44..9cc7d8f 100644
--- a/src/com/android/launcher3/shortcuts/DeepShortcutView.java
+++ b/src/com/android/launcher3/shortcuts/DeepShortcutView.java
@@ -27,8 +27,8 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
-import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.Utilities;
+import com.android.launcher3.WorkspaceItemInfo;
import com.android.launcher3.popup.PopupContainerWithArrow;
/**
@@ -111,8 +111,8 @@
// TODO: Add the click handler to this view directly and not the child view.
mBubbleText.setOnClickListener(container.getItemClickListener());
- mBubbleText.setOnLongClickListener(container);
- mBubbleText.setOnTouchListener(container);
+ mBubbleText.setOnLongClickListener(container.getItemDragHandler());
+ mBubbleText.setOnTouchListener(container.getItemDragHandler());
}
/**
diff --git a/src/com/android/launcher3/states/HintState.java b/src/com/android/launcher3/states/HintState.java
new file mode 100644
index 0000000..cb56097
--- /dev/null
+++ b/src/com/android/launcher3/states/HintState.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.launcher3.states;
+
+import static com.android.launcher3.LauncherAnimUtils.HINT_TRANSITION_MS;
+
+import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
+import com.android.launcher3.Workspace;
+import com.android.launcher3.userevent.nano.LauncherLogProto.ContainerType;
+
+/**
+ * Scale down workspace/hotseat to hint at going to either overview (on pause) or first home screen.
+ */
+public class HintState extends LauncherState {
+
+ private static final int STATE_FLAGS = FLAG_DISABLE_ACCESSIBILITY | FLAG_DISABLE_RESTORE
+ | FLAG_HAS_SYS_UI_SCRIM;
+
+ public HintState(int id) {
+ super(id, ContainerType.DEFAULT_CONTAINERTYPE, HINT_TRANSITION_MS, STATE_FLAGS);
+ }
+
+ @Override
+ public ScaleAndTranslation getWorkspaceScaleAndTranslation(Launcher launcher) {
+ return new ScaleAndTranslation(0.9f, 0, 0);
+ }
+
+ @Override
+ public ScaleAndTranslation getQsbScaleAndTranslation(Launcher launcher) {
+ // Treat the QSB as part of the hotseat so they move together.
+ return getHotseatScaleAndTranslation(launcher);
+ }
+
+ @Override
+ public void onStateTransitionEnd(Launcher launcher) {
+ launcher.getStateManager().goToState(NORMAL);
+ Workspace workspace = launcher.getWorkspace();
+ workspace.post(workspace::moveToDefaultScreen);
+ }
+}
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 506830d..4af5e0a 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -25,6 +25,7 @@
import android.os.Bundle;
import android.os.Debug;
import android.system.Os;
+import android.util.Log;
import android.view.View;
import androidx.annotation.Keep;
@@ -190,6 +191,11 @@
}
protected boolean isLauncherInitialized() {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
+ "isLauncherInitialized " + Launcher.ACTIVITY_TRACKER.getCreatedActivity() + ", "
+ + LauncherAppState.getInstance(mContext).getModel().isModelLoaded());
+ }
return Launcher.ACTIVITY_TRACKER.getCreatedActivity() == null
|| LauncherAppState.getInstance(mContext).getModel().isModelLoaded();
}
diff --git a/src/com/android/launcher3/testing/TestProtocol.java b/src/com/android/launcher3/testing/TestProtocol.java
index 01c207f..2f053c9 100644
--- a/src/com/android/launcher3/testing/TestProtocol.java
+++ b/src/com/android/launcher3/testing/TestProtocol.java
@@ -31,6 +31,7 @@
public static final int QUICK_SWITCH_STATE_ORDINAL = 4;
public static final int ALL_APPS_STATE_ORDINAL = 5;
public static final int BACKGROUND_APP_STATE_ORDINAL = 6;
+ public static final int HINT_STATE_ORDINAL = 7;
public static final String TAPL_EVENTS_TAG = "TaplEvents";
public static String stateOrdinalToString(int ordinal) {
@@ -49,6 +50,8 @@
return "AllApps";
case BACKGROUND_APP_STATE_ORDINAL:
return "Background";
+ case HINT_STATE_ORDINAL:
+ return "Hint";
default:
return null;
}
@@ -89,4 +92,5 @@
public static final String NO_BACKGROUND_TO_OVERVIEW_TAG = "b/138251824";
public static final String APP_NOT_DISABLED = "b/139891609";
+ public static final String LAUNCHER_DIDNT_INITIALIZE = "b/148313079";
}
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index d193bef..7ae0526 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -23,7 +23,7 @@
import static com.android.launcher3.LauncherStateManager.ATOMIC_OVERVIEW_SCALE_COMPONENT;
import static com.android.launcher3.LauncherStateManager.NON_ATOMIC_COMPONENT;
import static com.android.launcher3.anim.Interpolators.scrollInterpolatorForVelocity;
-import static com.android.launcher3.config.FeatureFlags.UNSTABLE_SPRINGS;
+import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS;
import static com.android.launcher3.util.DefaultDisplay.getSingleFrameMs;
import android.animation.Animator;
@@ -380,6 +380,7 @@
final LauncherState targetState;
final float progress = mCurrentAnimation.getProgressFraction();
+ final float progressVelocity = velocity * mProgressMultiplier;
final float interpolatedProgress = mCurrentAnimation.getInterpolatedProgress();
if (fling) {
targetState =
@@ -406,7 +407,7 @@
startProgress = 1;
} else {
startProgress = Utilities.boundToRange(progress
- + velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f);
+ + progressVelocity * getSingleFrameMs(mLauncher), 0f, 1f);
duration = BaseSwipeDetector.calculateDuration(velocity,
endProgress - Math.max(progress, 0)) * durationMultiplier;
}
@@ -421,7 +422,7 @@
startProgress = 0;
} else {
startProgress = Utilities.boundToRange(progress
- + velocity * getSingleFrameMs(mLauncher) * mProgressMultiplier, 0f, 1f);
+ + progressVelocity * getSingleFrameMs(mLauncher), 0f, 1f);
duration = BaseSwipeDetector.calculateDuration(velocity,
Math.min(progress, 1) - endProgress) * durationMultiplier;
}
@@ -433,8 +434,8 @@
maybeUpdateAtomicAnim(mFromState, targetState, targetState == mToState ? 1f : 0f);
updateSwipeCompleteAnimation(anim, Math.max(duration, getRemainingAtomicDuration()),
targetState, velocity, fling);
- mCurrentAnimation.dispatchOnStartWithVelocity(endProgress, velocity);
- if (fling && targetState == LauncherState.ALL_APPS && !UNSTABLE_SPRINGS.get()) {
+ mCurrentAnimation.dispatchOnStartWithVelocity(endProgress, progressVelocity);
+ if (fling && targetState == LauncherState.ALL_APPS && !QUICKSTEP_SPRINGS.get()) {
mLauncher.getAppsView().addSpringFromFlingUpdateListener(anim, velocity);
}
anim.start();
@@ -525,7 +526,11 @@
if (targetState != mStartState) {
logReachedState(logAction, targetState);
}
- mLauncher.getStateManager().goToState(targetState, false /* animated */);
+ if (!mLauncher.isInState(targetState)) {
+ // If we're already in the target state, don't jump to it at the end of the animation in
+ // case the user started interacting with it before the animation finished.
+ mLauncher.getStateManager().goToState(targetState, false /* animated */);
+ }
mLauncher.getDragLayer().getScrim().animateToSysuiMultiplier(1, 0, 0);
}
diff --git a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
index f2ebc45..9d406f3 100644
--- a/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
+++ b/src/com/android/launcher3/touch/SingleAxisSwipeDetector.java
@@ -54,8 +54,8 @@
}
@Override
- boolean canScrollStart(PointF displacement, float touchSlop) {
- return Math.abs(displacement.y) >= Math.max(Math.abs(displacement.x), touchSlop);
+ float extractOrthogonalDirection(PointF direction) {
+ return direction.x;
}
};
@@ -80,9 +80,10 @@
}
@Override
- boolean canScrollStart(PointF displacement, float touchSlop) {
- return Math.abs(displacement.x) >= Math.max(Math.abs(displacement.y), touchSlop);
+ float extractOrthogonalDirection(PointF direction) {
+ return direction.y;
}
+
};
private final Direction mDir;
@@ -126,7 +127,9 @@
@Override
protected boolean shouldScrollStart(PointF displacement) {
// Reject cases where the angle or slop condition is not met.
- if (!mDir.canScrollStart(displacement, mTouchSlop)) {
+ float minDisplacement = Math.max(mTouchSlop,
+ Math.abs(mDir.extractOrthogonalDirection(displacement)));
+ if (Math.abs(mDir.extractDirection(displacement)) < minDisplacement) {
return false;
}
@@ -150,7 +153,8 @@
@Override
protected void reportDraggingInternal(PointF displacement, MotionEvent event) {
- mListener.onDrag(mDir.extractDirection(displacement), event);
+ mListener.onDrag(mDir.extractDirection(displacement),
+ mDir.extractOrthogonalDirection(displacement), event);
}
@Override
@@ -164,13 +168,16 @@
/** @param start whether this was the original drag start, as opposed to a recatch. */
void onDragStart(boolean start);
- // TODO remove
boolean onDrag(float displacement);
default boolean onDrag(float displacement, MotionEvent event) {
return onDrag(displacement);
}
+ default boolean onDrag(float displacement, float orthogonalDisplacement, MotionEvent ev) {
+ return onDrag(displacement, ev);
+ }
+
void onDragEnd(float velocity);
}
@@ -183,8 +190,7 @@
/** Returns the part of the given {@link PointF} that is relevant to this direction. */
abstract float extractDirection(PointF point);
- /** Reject cases where the angle or slop condition is not met. */
- abstract boolean canScrollStart(PointF displacement, float touchSlop);
+ abstract float extractOrthogonalDirection(PointF point);
}
}
diff --git a/src/com/android/launcher3/util/ActivityTracker.java b/src/com/android/launcher3/util/ActivityTracker.java
index 499f655..b83c8fc 100644
--- a/src/com/android/launcher3/util/ActivityTracker.java
+++ b/src/com/android/launcher3/util/ActivityTracker.java
@@ -45,7 +45,13 @@
}
public void onActivityDestroyed(T activity) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "onActivityDestroyed");
+ }
if (mCurrentActivity.get() == activity) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE, "onActivityDestroyed: clear");
+ }
mCurrentActivity.clear();
}
}
@@ -110,6 +116,10 @@
}
public boolean handleCreate(T activity) {
+ if (TestProtocol.sDebugTracing) {
+ Log.d(TestProtocol.LAUNCHER_DIDNT_INITIALIZE,
+ "ActivityTracker.handleCreate " + mCurrentActivity.get() + " => " + activity);
+ }
mCurrentActivity = new WeakReference<>(activity);
return handleIntent(activity, activity.getIntent(), false, false);
}
diff --git a/src/com/android/launcher3/util/DefaultDisplay.java b/src/com/android/launcher3/util/DefaultDisplay.java
index 8529d50..d3dac04 100644
--- a/src/com/android/launcher3/util/DefaultDisplay.java
+++ b/src/com/android/launcher3/util/DefaultDisplay.java
@@ -128,8 +128,10 @@
public final DisplayMetrics metrics;
private Info(Context context) {
- Display display = context.getSystemService(WindowManager.class).getDefaultDisplay();
+ this(context.getSystemService(WindowManager.class).getDefaultDisplay());
+ }
+ public Info(Display display) {
id = display.getDisplayId();
rotation = display.getRotation();
diff --git a/src/com/android/launcher3/util/ViewOnDrawExecutor.java b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
index 451ae28..82e24c2 100644
--- a/src/com/android/launcher3/util/ViewOnDrawExecutor.java
+++ b/src/com/android/launcher3/util/ViewOnDrawExecutor.java
@@ -29,6 +29,7 @@
import java.util.ArrayList;
import java.util.concurrent.Executor;
+import java.util.function.Consumer;
/**
* An executor which runs all the tasks after the first onDraw is called on the target view.
@@ -38,7 +39,7 @@
private final ArrayList<Runnable> mTasks = new ArrayList<>();
- private Launcher mLauncher;
+ private Consumer<ViewOnDrawExecutor> mOnClearCallback;
private View mAttachedView;
private boolean mCompleted;
@@ -46,11 +47,16 @@
private boolean mFirstDrawCompleted;
public void attachTo(Launcher launcher) {
- attachTo(launcher, launcher.getWorkspace(), true /* waitForLoadAnimation */);
+ attachTo(launcher.getWorkspace(), true /* waitForLoadAnimation */,
+ launcher::clearPendingExecutor);
}
- public void attachTo(Launcher launcher, View attachedView, boolean waitForLoadAnimation) {
- mLauncher = launcher;
+ /**
+ * Attached the executor to the existence of the view
+ */
+ public void attachTo(View attachedView, boolean waitForLoadAnimation,
+ Consumer<ViewOnDrawExecutor> onClearCallback) {
+ mOnClearCallback = onClearCallback;
mAttachedView = attachedView;
mAttachedView.addOnAttachStateChangeListener(this);
if (!waitForLoadAnimation) {
@@ -110,8 +116,8 @@
mAttachedView.getViewTreeObserver().removeOnDrawListener(this);
mAttachedView.removeOnAttachStateChangeListener(this);
}
- if (mLauncher != null) {
- mLauncher.clearPendingExecutor(this);
+ if (mOnClearCallback != null) {
+ mOnClearCallback.accept(this);
}
MODEL_EXECUTOR.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
}
diff --git a/src/com/android/launcher3/views/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/FloatingIconView.java b/src/com/android/launcher3/views/FloatingIconView.java
index 0c9a28b..fa625ed 100644
--- a/src/com/android/launcher3/views/FloatingIconView.java
+++ b/src/com/android/launcher3/views/FloatingIconView.java
@@ -399,7 +399,8 @@
Drawable drawable = null;
Drawable badge = null;
boolean supportsAdaptiveIcons = ADAPTIVE_ICON_WINDOW_ANIM.get()
- && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
+ && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
+ && !info.isDisabled(); // Use original icon for disabled icons.
Drawable btvIcon = originalView instanceof BubbleTextView
? ((BubbleTextView) originalView).getIcon() : null;
if (info instanceof SystemShortcut) {
diff --git a/src/com/android/launcher3/views/OptionsPopupView.java b/src/com/android/launcher3/views/OptionsPopupView.java
index 5ba931d..880f123 100644
--- a/src/com/android/launcher3/views/OptionsPopupView.java
+++ b/src/com/android/launcher3/views/OptionsPopupView.java
@@ -126,7 +126,8 @@
popup.mTargetRect = targetRect;
for (OptionItem item : items) {
- DeepShortcutView view = popup.inflateAndAdd(R.layout.system_shortcut, popup);
+ DeepShortcutView view =
+ (DeepShortcutView) popup.inflateAndAdd(R.layout.system_shortcut, popup);
view.getIconView().setBackgroundResource(item.mIconRes);
view.getBubbleText().setText(item.mLabelRes);
view.setDividerVisibility(View.INVISIBLE);
diff --git a/src/com/android/launcher3/views/WorkEduView.java b/src/com/android/launcher3/views/WorkEduView.java
new file mode 100644
index 0000000..b6c81ae
--- /dev/null
+++ b/src/com/android/launcher3/views/WorkEduView.java
@@ -0,0 +1,214 @@
+/*
+ * 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 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/src/com/android/launcher3/views/WorkFooterContainer.java b/src/com/android/launcher3/views/WorkFooterContainer.java
index fb17b4f..f8add9a 100644
--- a/src/com/android/launcher3/views/WorkFooterContainer.java
+++ b/src/com/android/launcher3/views/WorkFooterContainer.java
@@ -15,32 +15,61 @@
*/
package com.android.launcher3.views;
+import static com.android.launcher3.util.PackageManagerHelper.hasShortcutsPermission;
+
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
-import android.widget.RelativeLayout;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.launcher3.Insettable;
+import com.android.launcher3.Launcher;
+import com.android.launcher3.R;
+import com.android.launcher3.Utilities;
+import com.android.launcher3.allapps.WorkModeSwitch;
+import com.android.launcher3.pm.UserCache;
/**
* Container to show work footer in all-apps.
*/
-public class WorkFooterContainer extends RelativeLayout {
+public class WorkFooterContainer extends LinearLayout implements Insettable {
+ private Rect mInsets = new Rect();
+
+ private WorkModeSwitch mWorkModeSwitch;
+ private TextView mWorkModeLabel;
+
+ protected final ObjectAnimator mOpenCloseAnimator;
public WorkFooterContainer(Context context) {
- super(context);
+ this(context, null, 0);
}
public WorkFooterContainer(Context context, AttributeSet attrs) {
- super(context, attrs);
+ this(context, attrs, 0);
}
public WorkFooterContainer(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
+ mOpenCloseAnimator = ObjectAnimator.ofPropertyValuesHolder(this);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
updateTranslation();
+ this.setVisibility(shouldShowWorkFooter() ? VISIBLE : GONE);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mWorkModeSwitch = findViewById(R.id.work_mode_toggle);
+ mWorkModeLabel = findViewById(R.id.work_mode_label);
}
@Override
@@ -56,4 +85,44 @@
setTranslationY(Math.max(0, availableBot - getBottom()));
}
}
+
+ @Override
+ public void setInsets(Rect insets) {
+ int bottomInset = insets.bottom - mInsets.bottom;
+ mInsets.set(insets);
+ setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
+ getPaddingBottom() + bottomInset);
+ }
+
+ /**
+ * Animates in/out work profile toggle panel based on the tab user is on
+ */
+ public void setWorkTabVisible(boolean workTabVisible) {
+ if (!shouldShowWorkFooter()) return;
+
+ mOpenCloseAnimator.setValues(PropertyValuesHolder.ofFloat(ALPHA, workTabVisible ? 1 : 0));
+ mOpenCloseAnimator.start();
+ }
+
+ /**
+ * Refreshes views based on current work profile enabled status
+ */
+ public void refresh() {
+ if (!shouldShowWorkFooter()) return;
+ boolean anyProfileQuietModeEnabled = UserCache.INSTANCE.get(
+ getContext()).isAnyProfileQuietModeEnabled();
+
+ mWorkModeLabel.setText(anyProfileQuietModeEnabled
+ ? R.string.work_mode_off_label : R.string.work_mode_on_label);
+ mWorkModeLabel.setCompoundDrawablesWithIntrinsicBounds(
+ anyProfileQuietModeEnabled ? R.drawable.ic_corp_off : R.drawable.ic_corp, 0, 0, 0);
+ mWorkModeSwitch.refresh();
+ }
+
+ private boolean shouldShowWorkFooter() {
+ Launcher launcher = Launcher.getLauncher(getContext());
+ return Utilities.ATLEAST_P && (hasShortcutsPermission(launcher)
+ || launcher.checkSelfPermission("android.permission.MODIFY_QUIET_MODE")
+ == PackageManager.PERMISSION_GRANTED);
+ }
}
diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java
index f713b33..f055adf 100644
--- a/src/com/android/launcher3/widget/WidgetCell.java
+++ b/src/com/android/launcher3/widget/WidgetCell.java
@@ -104,7 +104,7 @@
}
private void setContainerWidth() {
- mCellSize = (int) (mDeviceProfile.allAppsCellWidthPx * WIDTH_SCALE);
+ mCellSize = (int) (mDeviceProfile.cellWidthPx * WIDTH_SCALE);
mPresetPreviewSize = (int) (mCellSize * PREVIEW_SCALE);
}
diff --git a/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
index efbd9c9..33066e4 100644
--- a/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
+++ b/tests/src/com/android/launcher3/compat/PromiseIconUiTest.java
@@ -49,7 +49,7 @@
super.setUp();
mDevice.pressHome();
waitForLauncherCondition("Launcher didn't start", launcher -> launcher != null);
- waitForState("Launcher internal state didn't switch to Home", LauncherState.NORMAL);
+ waitForState("Launcher internal state didn't switch to Home", () -> LauncherState.NORMAL);
mSessionId = -1;
}
diff --git a/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
index 6d463b5..b0ece77 100644
--- a/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
+++ b/tests/src/com/android/launcher3/touch/SingleAxisSwipeDetectorTest.java
@@ -22,15 +22,16 @@
import static com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyFloat;
-import static org.mockito.Matchers.anyObject;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import android.util.Log;
+import android.view.MotionEvent;
import android.view.ViewConfiguration;
import androidx.test.InstrumentationRegistry;
@@ -158,7 +159,7 @@
mGenerator.put(0, 100, 100);
mGenerator.move(0, 100, 100 + mTouchSlop);
// TODO: actually calculate the following parameters and do exact value checks.
- verify(mMockListener).onDrag(anyFloat(), anyObject());
+ verify(mMockListener).onDrag(anyFloat(), anyFloat(), any(MotionEvent.class));
}
@Test
diff --git a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
index 19997eb..4ffc251 100644
--- a/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
+++ b/tests/src/com/android/launcher3/ui/AbstractLauncherUiTest.java
@@ -84,6 +84,7 @@
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
+import java.util.function.Supplier;
/**
* Base class for all instrumentation tests providing various utility methods.
@@ -183,16 +184,8 @@
@After
public void verifyLauncherState() {
- try {
- // Limits UI tests affecting tests running after them.
- mLauncher.waitForLauncherInitialized();
- } catch (Throwable t) {
- Log.e(TAG,
- "Couldn't deinit after a test, exiting tests, see logs for failures that "
- + "could have caused this",
- t);
- exit(1);
- }
+ // Limits UI tests affecting tests running after them.
+ mLauncher.waitForLauncherInitialized();
}
protected void clearLauncherData() throws IOException, InterruptedException {
@@ -281,9 +274,9 @@
// Cannot be used in TaplTests between a Tapl call injecting a gesture and a tapl call expecting
// the results of that gesture because the wait can hide flakeness.
- protected void waitForState(String message, LauncherState state) {
+ protected void waitForState(String message, Supplier<LauncherState> state) {
waitForLauncherCondition(message,
- launcher -> launcher.getStateManager().getCurrentStableState() == state);
+ launcher -> launcher.getStateManager().getCurrentStableState() == state.get());
}
protected void waitForResumed(String message) {
@@ -430,9 +423,9 @@
return !launcher.hasBeenResumed();
}
- protected boolean isInState(LauncherState state) {
+ protected boolean isInState(Supplier<LauncherState> state) {
if (!TestHelpers.isInLauncherProcess()) return true;
- return getFromLauncher(launcher -> launcher.getStateManager().getState() == state);
+ return getFromLauncher(launcher -> launcher.getStateManager().getState() == state.get());
}
protected int getAllAppsScroll(Launcher launcher) {
diff --git a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
index 61f5150..4b72882 100644
--- a/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
+++ b/tests/src/com/android/launcher3/ui/TaplTestsLauncher3.java
@@ -59,6 +59,7 @@
@Before
public void setUp() throws Exception {
+ mLauncherPid = 0;
super.setUp();
initialize(this);
mLauncherPid = mLauncher.getPid();
@@ -66,14 +67,17 @@
@After
public void teardown() {
- assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
+ if (mLauncherPid != 0) {
+ assertEquals("Launcher crashed, pid mismatch:", mLauncherPid, mLauncher.getPid());
+ }
}
public static void initialize(AbstractLauncherUiTest test) throws Exception {
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();
@@ -120,7 +124,7 @@
mDevice.pressMenu();
mDevice.waitForIdle();
mLauncher.getOptionsPopupMenu().getMenuItem("Home settings")
- .launch(mDevice.getLauncherPackageName());
+ .launch(mDevice.getLauncherPackageName());
}
@Test
@@ -143,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(
@@ -152,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(
@@ -162,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",
@@ -179,7 +183,7 @@
assertTrue(
"Launcher internal state is not All Apps",
- test.isInState(LauncherState.ALL_APPS));
+ test.isInState(() -> LauncherState.ALL_APPS));
} finally {
allApps.unfreeze();
}
@@ -190,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
@@ -216,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)));
@@ -225,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");
@@ -251,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..d6dfdd9 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureInvestigator.java
@@ -18,9 +18,13 @@
import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import android.os.SystemClock;
+
import androidx.test.uiautomator.UiDevice;
import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
import java.util.regex.Pattern;
class FailureInvestigator {
@@ -28,38 +32,83 @@
return Pattern.compile(regex).matcher(string).find();
}
- static int getBugForFailure(CharSequence exception, String testsStartTime) {
- final String logSinceTestsStart;
+ static class LogcatMatch {
+ String logcatPattern;
+ int bug;
+
+ LogcatMatch(String logcatPattern, int bug) {
+ this.logcatPattern = logcatPattern;
+ this.bug = bug;
+ }
+ }
+
+ static class ExceptionMatch {
+ String exceptionPattern;
+ LogcatMatch[] logcatMatches;
+
+ ExceptionMatch(String exceptionPattern, LogcatMatch[] logcatMatches) {
+ this.exceptionPattern = exceptionPattern;
+ this.logcatMatches = logcatMatches;
+ }
+ }
+
+ private static final ExceptionMatch[] EXCEPTION_MATCHES = {
+ new ExceptionMatch(
+ "java.lang.AssertionError: http://go/tapl : Tests are broken by a "
+ + "non-Launcher system error: Phone is locked",
+ new LogcatMatch[]{
+ new LogcatMatch(
+ "BroadcastQueue: Can't deliver broadcast to com.android"
+ + ".systemui.*Crashing it",
+ 147845913),
+ new LogcatMatch(
+ "Attempt to invoke virtual method 'boolean android\\"
+ + ".graphics\\.Bitmap\\.isRecycled\\(\\)' on a null "
+ + "object reference",
+ 148424291),
+ new LogcatMatch(
+ "java\\.lang\\.IllegalArgumentException\\: Ranking map "
+ + "doesn't contain key",
+ 148570537),
+ }),
+ new ExceptionMatch("Launcher didn't initialize",
+ new LogcatMatch[]{
+ new LogcatMatch(
+ "ActivityManager: Reason: executing service com.google"
+ + ".android.apps.nexuslauncher/com.android.launcher3"
+ + ".notification.NotificationListener",
+ 148238677),
+ }),
+ };
+
+ static int getBugForFailure(CharSequence exception) {
+ if ("com.google.android.setupwizard".equals(
+ UiDevice.getInstance(getInstrumentation()).getLauncherPackageName())) {
+ return 145935261;
+ }
+
+
+ final String logSinceBoot;
try {
- logSinceTestsStart =
+ final String systemBootTime =
+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(
+ new Date(System.currentTimeMillis() - SystemClock.elapsedRealtime()));
+
+ logSinceBoot =
UiDevice.getInstance(getInstrumentation())
- .executeShellCommand("logcat -d -t " + testsStartTime.replace(" ", ""));
+ .executeShellCommand("logcat -d -t " + systemBootTime.replace(" ", ""));
} catch (IOException e) {
return 0;
}
- if (matches(
- "java.lang.AssertionError: http://go/tapl : Tests are broken by a non-Launcher "
- + "system error: Phone is locked",
- exception)) {
- if (matches(
- "BroadcastQueue: Can't deliver broadcast to com.android.systemui.*Crashing it",
- logSinceTestsStart)) {
- return 147845913;
- }
- } else if (matches("java.lang.AssertionError: Launcher build match not found", exception)) {
- if (matches(
- "TestStabilityRule: Launcher package: com.google.android.setupwizard",
- logSinceTestsStart)) {
- return 145935261;
- }
- } else if (matches("Launcher didn't initialize", exception)) {
- if (matches(
- "ActivityManager: Reason: executing service com.google.android.apps"
- + ".nexuslauncher/com.android.launcher3.notification"
- + ".NotificationListener",
- logSinceTestsStart)) {
- return 148238677;
+ for (ExceptionMatch exceptionMatch : EXCEPTION_MATCHES) {
+ if (matches(exceptionMatch.exceptionPattern, exception)) {
+ for (LogcatMatch logcatMatch : exceptionMatch.logcatMatches) {
+ if (matches(logcatMatch.logcatPattern, logSinceBoot)) {
+ return logcatMatch.bug;
+ }
+ }
+ break;
}
}
diff --git a/tests/src/com/android/launcher3/util/rule/FailureRewriterRule.java b/tests/src/com/android/launcher3/util/rule/FailureRewriterRule.java
index 4cb309a..99ddee4 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureRewriterRule.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureRewriterRule.java
@@ -22,15 +22,9 @@
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-
public class FailureRewriterRule implements TestRule {
private static final String TAG = "FailureRewriter";
- private static final String testsStartTime =
- new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date());
-
@Override
public Statement apply(Statement base, Description description) {
return new Statement() {
@@ -39,8 +33,7 @@
try {
base.evaluate();
} catch (Throwable e) {
- final int bug =
- FailureInvestigator.getBugForFailure(e.toString(), testsStartTime);
+ final int bug = FailureInvestigator.getBugForFailure(e.toString());
if (bug == 0) throw e;
Log.e(TAG, "Known bug found for the original failure "
diff --git a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
index cdda0f0..7763cc2 100644
--- a/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
+++ b/tests/src/com/android/launcher3/util/rule/FailureWatcher.java
@@ -8,6 +8,7 @@
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
import java.io.ByteArrayOutputStream;
import java.io.File;
@@ -15,6 +16,7 @@
public class FailureWatcher extends TestWatcher {
private static final String TAG = "FailureWatcher";
+ private static boolean sHadFailedTestDeinitialization;
final private UiDevice mDevice;
public FailureWatcher(UiDevice device) {
@@ -60,4 +62,31 @@
device.takeScreenshot(new File(pathname));
}
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+
+ @Override
+ public void evaluate() throws Throwable {
+ if (sHadFailedTestDeinitialization) {
+ Log.d(TAG, "Skipping due to a recent test deinitialization failure: " +
+ description.getDisplayName());
+ return;
+ }
+
+ try {
+ base.evaluate();
+ } catch (Throwable e) {
+ if (!Log.getStackTraceString(e).contains(
+ "androidx.test.internal.runner.junit4.statement.RunBefores.evaluate")) {
+ // Test failed to deinitialize. Since the global state is probably
+ // corrupted, won't execute other tests.
+ sHadFailedTestDeinitialization = true;
+ }
+ throw e;
+ }
+ }
+ };
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Background.java b/tests/tapl/com/android/launcher3/tapl/Background.java
index 5c4689b..9f29a1a 100644
--- a/tests/tapl/com/android/launcher3/tapl/Background.java
+++ b/tests/tapl/com/android/launcher3/tapl/Background.java
@@ -25,6 +25,7 @@
import android.view.MotionEvent;
import androidx.annotation.NonNull;
+import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiObject2;
import com.android.launcher3.testing.TestProtocol;
@@ -37,6 +38,7 @@
*/
public class Background extends LauncherInstrumentation.VisibleContainer {
private static final int ZERO_BUTTON_SWIPE_UP_GESTURE_DURATION = 500;
+ private static final Pattern SQUARE_BUTTON_EVENT = Pattern.compile("onOverviewToggle");
Background(LauncherInstrumentation launcher) {
super(launcher);
@@ -129,6 +131,7 @@
}
case THREE_BUTTON:
+ mLauncher.expectEvent(SQUARE_BUTTON_EVENT);
mLauncher.runToState(
() -> mLauncher.waitForSystemUiObject("recent_apps").click(),
OVERVIEW_STATE_ORDINAL);
@@ -177,9 +180,12 @@
endX = startX;
endY = 0;
}
+ final boolean launcherIsVisible =
+ mLauncher.hasLauncherObject(By.textStartsWith(""));
+ final boolean isZeroButton = mLauncher.getNavigationModel()
+ == LauncherInstrumentation.NavigationModel.ZERO_BUTTON;
mLauncher.swipeToState(startX, startY, endX, endY, 20, expectedState,
- mLauncher.getNavigationModel()
- == LauncherInstrumentation.NavigationModel.ZERO_BUTTON
+ launcherIsVisible && isZeroButton
? LauncherInstrumentation.GestureScope.INSIDE_TO_OUTSIDE
: LauncherInstrumentation.GestureScope.OUTSIDE
);
@@ -189,8 +195,10 @@
case THREE_BUTTON:
// Double press the recents button.
UiObject2 recentsButton = mLauncher.waitForSystemUiObject("recent_apps");
+ mLauncher.expectEvent(SQUARE_BUTTON_EVENT);
mLauncher.runToState(() -> recentsButton.click(), OVERVIEW_STATE_ORDINAL);
mLauncher.getOverview();
+ mLauncher.expectEvent(SQUARE_BUTTON_EVENT);
recentsButton.click();
break;
}
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index 0a3462f..cf5b24e 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) {
@@ -793,7 +797,7 @@
return mDevice.hasObject(getLauncherObjectSelector(resId));
}
- private boolean hasLauncherObject(BySelector selector) {
+ boolean hasLauncherObject(BySelector selector) {
return mDevice.hasObject(makeLauncherSelector(selector));
}
@@ -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() {
diff --git a/tests/tapl/com/android/launcher3/tapl/Widgets.java b/tests/tapl/com/android/launcher3/tapl/Widgets.java
index ede5bd9..8659aa7 100644
--- a/tests/tapl/com/android/launcher3/tapl/Widgets.java
+++ b/tests/tapl/com/android/launcher3/tapl/Widgets.java
@@ -24,6 +24,8 @@
import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiObject2;
+import com.android.launcher3.tapl.LauncherInstrumentation.GestureScope;
+
import java.util.Collection;
/**
@@ -93,7 +95,7 @@
int i = 0;
for (; ; ) {
final Collection<UiObject2> cells = mLauncher.getObjectsInContainer(
- widgetsContainer, "widgets_cell_list_container");
+ widgetsContainer, "widgets_scroll_container");
mLauncher.assertTrue("Widgets doesn't have 2 rows", cells.size() >= 2);
for (UiObject2 cell : cells) {
final UiObject2 label = cell.findObject(labelSelector);
@@ -105,6 +107,19 @@
"com.android.launcher3.widget.WidgetCell",
widget.getClassName());
+ int maxWidth = 0;
+ for (UiObject2 sibling : widget.getParent().getChildren()) {
+ maxWidth = Math.max(sibling.getVisibleBounds().width(), maxWidth);
+ }
+
+ int visibleDelta = maxWidth - widget.getVisibleBounds().width();
+ if (visibleDelta > 0) {
+ Rect parentBounds = cell.getVisibleBounds();
+ mLauncher.linearGesture(parentBounds.centerX() + visibleDelta,
+ parentBounds.centerY(), parentBounds.centerX(),
+ parentBounds.centerY(), 10, true, GestureScope.INSIDE);
+ }
+
if (widget.getVisibleBounds().bottom
<= displaySize.y - mLauncher.getBottomGestureSize()) {
return new Widget(mLauncher, widget);