Merge "Waiting Launcher to stop after swiping to unstash taskbar" into main
diff --git a/Android.bp b/Android.bp
index 010ea45..19d2a58 100644
--- a/Android.bp
+++ b/Android.bp
@@ -23,42 +23,66 @@
// All sources are split so they can be reused in many other libraries/apps in other folders
filegroup {
name: "launcher-src",
- srcs: [ "src/**/*.java", "src/**/*.kt" ],
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ ],
}
filegroup {
name: "launcher-quickstep-src",
- srcs: [ "quickstep/src/**/*.java", "quickstep/src/**/*.kt" ],
+ srcs: [
+ "quickstep/src/**/*.java",
+ "quickstep/src/**/*.kt",
+ ],
}
filegroup {
name: "launcher-go-src",
- srcs: [ "go/src/**/*.java", "go/src/**/*.kt" ],
+ srcs: [
+ "go/src/**/*.java",
+ "go/src/**/*.kt",
+ ],
}
filegroup {
name: "launcher-go-quickstep-src",
- srcs: [ "go/quickstep/src/**/*.java", "go/quickstep/src/**/*.kt" ],
+ srcs: [
+ "go/quickstep/src/**/*.java",
+ "go/quickstep/src/**/*.kt",
+ ],
}
filegroup {
name: "launcher-src_shortcuts_overrides",
- srcs: [ "src_shortcuts_overrides/**/*.java", "src_shortcuts_overrides/**/*.kt" ],
+ srcs: [
+ "src_shortcuts_overrides/**/*.java",
+ "src_shortcuts_overrides/**/*.kt",
+ ],
}
filegroup {
name: "launcher-src_ui_overrides",
- srcs: [ "src_ui_overrides/**/*.java", "src_ui_overrides/**/*.kt" ],
+ srcs: [
+ "src_ui_overrides/**/*.java",
+ "src_ui_overrides/**/*.kt",
+ ],
}
filegroup {
name: "launcher-ext_tests",
- srcs: [ "ext_tests/**/*.java", "ext_tests/**/*.kt" ],
+ srcs: [
+ "ext_tests/**/*.java",
+ "ext_tests/**/*.kt",
+ ],
}
filegroup {
name: "launcher-quickstep-ext_tests",
- srcs: [ "quickstep/ext_tests/**/*.java", "quickstep/ext_tests/**/*.kt" ],
+ srcs: [
+ "quickstep/ext_tests/**/*.java",
+ "quickstep/ext_tests/**/*.kt",
+ ],
}
// Proguard files for Launcher3
@@ -85,9 +109,12 @@
srcs: [
"tests/tapl/**/*.java",
],
- resource_dirs: [ ],
+ resource_dirs: [],
manifest: "tests/tapl/AndroidManifest.xml",
platform_apis: true,
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library_static {
@@ -99,12 +126,15 @@
sdk_version: "current",
proto: {
type: "lite",
- local_include_dirs:[
+ local_include_dirs: [
"protos",
"protos_overrides",
],
},
static_libs: ["libprotobuf-java-lite"],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library_static {
@@ -115,14 +145,17 @@
sdk_version: "current",
proto: {
type: "lite",
- local_include_dirs:[
+ local_include_dirs: [
"quickstep/protos_overrides",
],
},
static_libs: [
- "libprotobuf-java-lite",
- "launcher_log_protos_lite"
- ],
+ "libprotobuf-java-lite",
+ "launcher_log_protos_lite",
+ ],
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
java_library {
@@ -134,12 +167,15 @@
sdk_version: "current",
min_sdk_version: min_launcher3_sdk_version,
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
// Library with all the dependencies for building Launcher3
android_library {
name: "Launcher3ResLib",
- srcs: [ ],
+ srcs: [],
resource_dirs: ["res"],
static_libs: [
"LauncherPluginLib",
@@ -154,7 +190,7 @@
"com.google.android.material_material",
"iconloader_base",
"view_capture",
- "animationlib"
+ "animationlib",
],
manifest: "AndroidManifest-common.xml",
sdk_version: "current",
@@ -236,7 +272,7 @@
// Library with all the dependencies for building quickstep
android_library {
name: "QuickstepResLib",
- srcs: [ ],
+ srcs: [],
resource_dirs: [
"quickstep/res",
],
@@ -253,9 +289,11 @@
],
manifest: "quickstep/AndroidManifest.xml",
min_sdk_version: "current",
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
-
// Library with all the dependencies for building Launcher Go
android_library {
name: "LauncherGoResLib",
@@ -360,7 +398,10 @@
manifest: "go/AndroidManifest.xml",
jacoco: {
include_filter: ["com.android.launcher3.*"],
- }
+ },
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
@@ -396,7 +437,10 @@
manifest: "quickstep/AndroidManifest.xml",
jacoco: {
include_filter: ["com.android.launcher3.*"],
- }
+ },
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
@@ -414,7 +458,7 @@
min_sdk_version: "current",
target_sdk_version: "current",
- srcs: [ ],
+ srcs: [],
resource_dirs: [
"go/quickstep/res",
@@ -446,7 +490,9 @@
manifest: "quickstep/AndroidManifest.xml",
jacoco: {
include_filter: ["com.android.launcher3.*"],
- }
+ },
+ lint: {
+ baseline_filename: "lint-baseline.xml",
+ },
}
-
diff --git a/aconfig/launcher.aconfig b/aconfig/launcher.aconfig
index 210cfd0..a62f809 100644
--- a/aconfig/launcher.aconfig
+++ b/aconfig/launcher.aconfig
@@ -67,14 +67,7 @@
name: "enable_taskbar_pinning"
namespace: "launcher"
description: "Enables taskbar pinning to allow user to switch between transient and persistent taskbar flavors."
- bug: "270396583"
-}
-
-flag {
- name: "enable_split_from_fullscreen_with_keyboard_shortcuts"
- namespace: "launcher"
- description: "Enables initiating split from a fullscreen app using keyboard shortcuts"
- bug: "270394122"
+ bug: "296231746"
}
flag {
@@ -97,3 +90,10 @@
description: "Enables full width two pane widget picker for tablets in landscape and portrait"
bug: "315055849"
}
+
+flag {
+ name: "enable_support_for_archiving"
+ namespace: "launcher"
+ description: "Enables support for archived apps in Launcher3, such as empty progress bar etc."
+ bug: "210590852"
+}
diff --git a/quickstep/res/drawable-sw600dp-land/gesture_tutorial_back_step_shape.xml b/quickstep/res/drawable-sw600dp-land/gesture_tutorial_back_step_shape.xml
index a07aeaa..8b4127a 100644
--- a/quickstep/res/drawable-sw600dp-land/gesture_tutorial_back_step_shape.xml
+++ b/quickstep/res/drawable-sw600dp-land/gesture_tutorial_back_step_shape.xml
@@ -16,7 +16,8 @@
android:width="84dp"
android:height="208dp"
android:viewportWidth="84"
- android:viewportHeight="208">
+ android:viewportHeight="208"
+ android:autoMirrored="true">
<path
android:pathData="M24.53,169.2L32.09,165.56C77.7,143.55 77.7,64.45 32.09,42.35L24.53,38.71C14.55,33.95 6.06,25.56 0,14.92V193.08C6.06,182.44 14.55,174.05 24.53,169.2Z"
android:fillColor="?attr/onSurfaceBack"/>
diff --git a/quickstep/res/drawable-sw720dp-land/gesture_tutorial_back_step_shape.xml b/quickstep/res/drawable-sw720dp-land/gesture_tutorial_back_step_shape.xml
index e20458e..3a11f21 100644
--- a/quickstep/res/drawable-sw720dp-land/gesture_tutorial_back_step_shape.xml
+++ b/quickstep/res/drawable-sw720dp-land/gesture_tutorial_back_step_shape.xml
@@ -16,7 +16,8 @@
android:width="122dp"
android:height="303dp"
android:viewportWidth="122"
- android:viewportHeight="303">
+ android:viewportHeight="303"
+ android:autoMirrored="true">
<path
android:pathData="M35.65,245.9L46.63,240.61C112.92,208.62 112.92,93.67 46.63,61.54L35.65,56.26C21.15,49.34 8.81,37.14 0,21.69V280.6C8.81,265.15 21.15,252.95 35.65,245.9Z"
android:fillColor="?attr/onSurfaceBack"/>
diff --git a/quickstep/res/drawable/gesture_tutorial_back_step_shape.xml b/quickstep/res/drawable/gesture_tutorial_back_step_shape.xml
index 9389340..c217be2 100644
--- a/quickstep/res/drawable/gesture_tutorial_back_step_shape.xml
+++ b/quickstep/res/drawable/gesture_tutorial_back_step_shape.xml
@@ -16,7 +16,8 @@
android:width="83dp"
android:height="208dp"
android:viewportWidth="83"
- android:viewportHeight="208">
+ android:viewportHeight="208"
+ android:autoMirrored="true">
<path
android:pathData="M23.53,169.2L31.09,165.56C76.7,143.55 76.7,64.45 31.09,42.35L23.53,38.71C13.55,33.95 5.06,25.56 -1,14.92V193.08C5.06,182.44 13.55,174.05 23.53,169.2Z"
android:fillColor="?attr/onSurfaceBack"/>
diff --git a/quickstep/res/values-zh-rCN/strings.xml b/quickstep/res/values-zh-rCN/strings.xml
index 2c740b7..4d21108 100644
--- a/quickstep/res/values-zh-rCN/strings.xml
+++ b/quickstep/res/values-zh-rCN/strings.xml
@@ -114,7 +114,7 @@
<string name="taskbar_edu_close" msgid="887022990168191073">"关闭"</string>
<string name="taskbar_edu_done" msgid="6880178093977704569">"完成"</string>
<string name="taskbar_button_home" msgid="2151398979630664652">"主屏幕"</string>
- <string name="taskbar_button_a11y" msgid="5241161324875094465">"无障碍"</string>
+ <string name="taskbar_button_a11y" msgid="5241161324875094465">"无障碍功能"</string>
<string name="taskbar_button_back" msgid="8558862226461164514">"返回"</string>
<string name="taskbar_button_ime_switcher" msgid="1730244360907588541">"IME 切换器"</string>
<string name="taskbar_button_recents" msgid="7273376136216613134">"最近用过"</string>
diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml
index 232c441..2a1f39f 100644
--- a/quickstep/res/values/dimens.xml
+++ b/quickstep/res/values/dimens.xml
@@ -322,7 +322,6 @@
<!-- Taskbar -->
<dimen name="taskbar_size">@*android:dimen/taskbar_frame_height</dimen>
- <dimen name="taskbar_phone_size">@*android:dimen/navigation_bar_frame_height</dimen>
<dimen name="taskbar_ime_size">48dp</dimen>
<dimen name="taskbar_icon_min_touch_size">48dp</dimen>
<!-- Note that this applies to both sides of all icons, so visible space is double this. -->
diff --git a/quickstep/src/com/android/launcher3/HomeTransitionController.java b/quickstep/src/com/android/launcher3/HomeTransitionController.java
index b264115..2b50283 100644
--- a/quickstep/src/com/android/launcher3/HomeTransitionController.java
+++ b/quickstep/src/com/android/launcher3/HomeTransitionController.java
@@ -28,19 +28,16 @@
*/
public class HomeTransitionController {
- private final QuickstepLauncher mLauncher;
+ @Nullable private QuickstepLauncher mLauncher;
@Nullable private IHomeTransitionListener mHomeTransitionListener;
- public HomeTransitionController(QuickstepLauncher launcher) {
+ public void registerHomeTransitionListener(QuickstepLauncher launcher) {
mLauncher = launcher;
- }
-
- public void registerHomeTransitionListener() {
mHomeTransitionListener = new IHomeTransitionListener.Stub() {
@Override
public void onHomeVisibilityChanged(boolean isVisible) {
MAIN_EXECUTOR.execute(() -> {
- if (mLauncher.getTaskbarUIController() != null) {
+ if (mLauncher != null && mLauncher.getTaskbarUIController() != null) {
mLauncher.getTaskbarUIController().onLauncherVisibilityChanged(isVisible);
}
});
@@ -53,5 +50,6 @@
public void unregisterHomeTransitionListener() {
SystemUiProxy.INSTANCE.get(mLauncher).setHomeTransitionListener(null);
mHomeTransitionListener = null;
+ mLauncher = null;
}
}
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index f25b652..67d3827 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -1290,7 +1290,7 @@
/**
* Returns view on launcher that corresponds to the closing app in the list of app targets
*/
- private @Nullable View findLauncherView(RemoteAnimationTarget[] appTargets) {
+ public @Nullable View findLauncherView(RemoteAnimationTarget[] appTargets) {
for (RemoteAnimationTarget appTarget : appTargets) {
if (appTarget.mode == MODE_CLOSING) {
View launcherView = findLauncherView(appTarget);
@@ -1607,7 +1607,7 @@
|| mLauncher.getWorkspace().isOverlayShown()
|| shouldPlayFallbackClosingAnimation(appTargets);
- boolean playWorkspaceReveal = true;
+ boolean playWorkspaceReveal = !fromPredictiveBack;
boolean skipAllAppsScale = false;
if (fromUnlock) {
anim.play(getUnlockWindowAnimator(appTargets, wallpaperTargets));
@@ -1649,7 +1649,7 @@
// targets list because it is already visible). In that case, we force
// invisibility on touch down, and only reset it after the animation to home
// is initialized.
- if (launcherIsForceInvisibleOrOpening) {
+ if (launcherIsForceInvisibleOrOpening || fromPredictiveBack) {
addCujInstrumentation(anim, playFallBackAnimation
? Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK
: Cuj.CUJ_LAUNCHER_APP_CLOSE_TO_HOME);
@@ -1666,7 +1666,7 @@
// Only register the content animation for cancellation when state changes
mLauncher.getStateManager().setCurrentAnimation(anim);
- if (mLauncher.isInState(LauncherState.ALL_APPS)) {
+ if (mLauncher.isInState(LauncherState.ALL_APPS) && !fromPredictiveBack) {
Pair<AnimatorSet, Runnable> contentAnimator =
getLauncherContentAnimator(false, LAUNCHER_RESUME_START_DELAY,
skipAllAppsScale);
@@ -1677,10 +1677,8 @@
contentAnimator.second.run();
}
});
- } else {
- if (playWorkspaceReveal) {
+ } else if (playWorkspaceReveal) {
anim.play(new WorkspaceRevealAnim(mLauncher, false).getAnimators());
- }
}
}
diff --git a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
index 477cd84..575ad9e 100644
--- a/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
+++ b/quickstep/src/com/android/launcher3/WidgetPickerActivity.java
@@ -16,6 +16,7 @@
package com.android.launcher3;
+import static android.content.ClipDescription.MIMETYPE_TEXT_INTENT;
import static android.view.WindowInsets.Type.navigationBars;
import static android.view.WindowInsets.Type.statusBars;
@@ -23,6 +24,8 @@
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.appwidget.AppWidgetProviderInfo;
+import android.content.ClipData;
+import android.content.ClipDescription;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
@@ -43,6 +46,14 @@
/** An Activity that can host Launcher's widget picker. */
public class WidgetPickerActivity extends BaseActivity {
+ /**
+ * Name of the extra that indicates that a widget being dragged.
+ *
+ * <p>When set to "true" in the result of startActivityForResult, the client that launched the
+ * picker knows that activity was closed due to pending drag.
+ */
+ private static final String EXTRA_IS_PENDING_WIDGET_DRAG = "is_pending_widget_drag";
+
private SimpleDragLayer<WidgetPickerActivity> mDragLayer;
private WidgetsModel mModel;
private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {});
@@ -102,6 +113,46 @@
};
}
+ @Override
+ public View.OnLongClickListener getAllAppsItemLongClickListener() {
+ return view -> {
+ if (!(view instanceof WidgetCell widgetCell)) return false;
+
+ if (widgetCell.getWidgetView().getDrawable() == null
+ && widgetCell.getAppWidgetHostViewPreview() == null) {
+ // The widget preview hasn't been loaded; so, we abort the drag.
+ return false;
+ }
+
+ final AppWidgetProviderInfo info = widgetCell.getWidgetItem().widgetInfo;
+ if (info == null || info.provider == null) {
+ return false;
+ }
+
+ ClipData clipData = new ClipData(
+ new ClipDescription(
+ /* label= */ "", // not displayed anywhere; so, set to empty.
+ new String[]{MIMETYPE_TEXT_INTENT}
+ ),
+ new ClipData.Item(new Intent()
+ .putExtra(Intent.EXTRA_USER, info.getProfile())
+ .putExtra(Intent.EXTRA_COMPONENT_NAME, info.provider))
+ );
+
+ // Set result indicating activity was closed due a widget being dragged.
+ setResult(RESULT_OK, new Intent()
+ .putExtra(EXTRA_IS_PENDING_WIDGET_DRAG, true));
+
+ // DRAG_FLAG_GLOBAL permits dragging data beyond app window.
+ return view.startDragAndDrop(
+ clipData,
+ new View.DragShadowBuilder(view),
+ /* myLocalState= */ null,
+ View.DRAG_FLAG_GLOBAL
+ );
+ };
+ }
+
private void refreshAndBindWidgets() {
MODEL_EXECUTOR.execute(() -> {
LauncherAppState app = LauncherAppState.getInstance(this);
diff --git a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
index 1440498..f012197 100644
--- a/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
+++ b/quickstep/src/com/android/launcher3/appprediction/PredictionRowView.java
@@ -23,6 +23,7 @@
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
@@ -31,6 +32,7 @@
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
+import com.android.launcher3.Flags;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.FloatingHeaderRow;
@@ -54,6 +56,11 @@
private final T mActivityContext;
private int mNumPredictedAppsPerRow;
+ // Vertical padding of the icon that contributes to the expected cell height.
+ private final int mVerticalPadding;
+ // Extra padding that is used in the top app rows (prediction and search) that is not used in
+ // the regular A-Z list. This only applies to single line label.
+ private final int mTopRowExtraHeight;
// Helper to drawing the focus indicator.
private final FocusIndicatorHelper mFocusHelper;
@@ -76,6 +83,10 @@
mFocusHelper = new SimpleFocusIndicatorHelper(this);
mActivityContext = ActivityContext.lookupContext(context);
mNumPredictedAppsPerRow = mActivityContext.getDeviceProfile().numShownAllAppsColumns;
+ mTopRowExtraHeight = getResources().getDimensionPixelSize(
+ R.dimen.all_apps_search_top_row_extra_height);
+ mVerticalPadding = getResources().getDimensionPixelSize(
+ R.dimen.all_apps_predicted_icon_vertical_padding);
updateVisibility();
}
@@ -124,13 +135,11 @@
int iconHeight = deviceProfile.allAppsIconSizePx;
int iconPadding = deviceProfile.allAppsIconDrawablePaddingPx;
int textHeight = Utilities.calculateTextHeight(deviceProfile.allAppsIconTextSizePx);
- int verticalPadding = getResources().getDimensionPixelSize(
- R.dimen.all_apps_predicted_icon_vertical_padding);
- int totalHeight = iconHeight + iconPadding + textHeight + verticalPadding * 2;
- if (FeatureFlags.enableTwolineAllapps()) {
- // Add extra textHeight to the existing total height.
- totalHeight += textHeight;
- }
+ int totalHeight = iconHeight + iconPadding + textHeight + mVerticalPadding * 2;
+ // Prediction row height will be 4dp bigger than the regular apps in A-Z list when two line
+ // is not enabled. Otherwise, the extra height will increase by just the textHeight.
+ int extraHeight = FeatureFlags.enableTwolineAllapps() ? textHeight : mTopRowExtraHeight;
+ totalHeight += extraHeight;
return getVisibility() == GONE ? 0 : totalHeight + getPaddingTop() + getPaddingBottom();
}
@@ -199,8 +208,12 @@
icon.setOnFocusChangeListener(mFocusHelper);
LayoutParams lp = (LayoutParams) icon.getLayoutParams();
- // Ensure the all apps icon height matches the workspace icons in portrait mode.
- lp.height = mActivityContext.getDeviceProfile().allAppsCellHeightPx;
+ if (Flags.enableFocusOutline()) {
+ lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
+ } else {
+ // Ensure the all apps icon height matches the workspace icons in portrait mode.
+ lp.height = mActivityContext.getDeviceProfile().allAppsCellHeightPx;
+ }
lp.width = 0;
lp.weight = 1;
addView(icon);
diff --git a/quickstep/src/com/android/launcher3/model/AppEventProducer.java b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
index 17c39af..7209bd8 100644
--- a/quickstep/src/com/android/launcher3/model/AppEventProducer.java
+++ b/quickstep/src/com/android/launcher3/model/AppEventProducer.java
@@ -72,12 +72,13 @@
import com.android.launcher3.logging.StatsLogManager.EventEnum;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.shortcuts.ShortcutRequest;
+import com.android.launcher3.util.UserIconInfo;
import com.android.quickstep.logging.StatsLogCompatManager.StatsLogConsumer;
+import com.android.systemui.shared.system.SysUiStatsLog;
import java.util.Locale;
import java.util.Optional;
import java.util.function.ObjIntConsumer;
-import java.util.function.Predicate;
/**
* Utility class to track stats log and emit corresponding app events
@@ -188,14 +189,13 @@
}
@Nullable
- private AppTarget toAppTarget(LauncherAtom.ItemInfo info) {
- UserHandle userHandle = Process.myUserHandle();
- if (info.getIsWork()) {
- userHandle = UserCache.INSTANCE.get(mContext).getUserProfiles().stream()
- .filter(((Predicate<UserHandle>) userHandle::equals).negate())
- .findAny()
- .orElse(null);
- }
+ AppTarget toAppTarget(LauncherAtom.ItemInfo info) {
+ int iconInfoType = getIconInfoTypeFromItemInfo(info);
+ UserCache userCache = UserCache.getInstance(mContext);
+ UserHandle userHandle = userCache.getUserProfiles().stream()
+ .filter(user -> userCache.getUserInfo(user).type == iconInfoType)
+ .findFirst()
+ .orElse(null);
if (userHandle == null) {
return null;
}
@@ -328,4 +328,16 @@
return TextUtils.isEmpty(componentNameString)
? null : ComponentName.unflattenFromString(componentNameString);
}
+
+ private int getIconInfoTypeFromItemInfo(LauncherAtom.ItemInfo info) {
+ int userType = info.getUserType();
+ return switch (userType) {
+ case SysUiStatsLog.LAUNCHER_UICHANGED__USER_TYPE__TYPE_WORK -> UserIconInfo.TYPE_WORK;
+ case SysUiStatsLog.LAUNCHER_UICHANGED__USER_TYPE__TYPE_CLONED ->
+ UserIconInfo.TYPE_CLONED;
+ case SysUiStatsLog.LAUNCHER_UICHANGED__USER_TYPE__TYPE_PRIVATE ->
+ UserIconInfo.TYPE_PRIVATE;
+ default -> UserIconInfo.TYPE_MAIN;
+ };
+ }
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
index f981610..535c8ec 100644
--- a/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/FallbackTaskbarUIController.java
@@ -105,6 +105,13 @@
}
@Override
+ protected boolean isInOverview() {
+ TopTaskTracker.CachedTaskInfo topTask = TopTaskTracker.INSTANCE
+ .get(mControllers.taskbarActivityContext).getCachedTopTask(true);
+ return topTask.isRecentsTask();
+ }
+
+ @Override
public RecentsView getRecentsView() {
return mRecentsActivity.getOverviewPanel();
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
index 6e88780..9dac89d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/KeyboardQuickSwitchViewController.java
@@ -15,20 +15,26 @@
*/
package com.android.launcher3.taskbar;
+import static android.window.SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED;
+
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.animation.Animator;
+import android.app.ActivityOptions;
import android.view.KeyEvent;
import android.view.View;
+import android.window.RemoteTransition;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.GroupTask;
+import com.android.quickstep.util.SlideInRemoteTransition;
import com.android.systemui.shared.recents.model.Task;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -142,19 +148,23 @@
return -1;
}
+ RemoteTransition remoteTransition = new RemoteTransition(new SlideInRemoteTransition(
+ Utilities.isRtl(mControllers.taskbarActivityContext.getResources())));
if (mOnDesktop) {
UI_HELPER_EXECUTOR.execute(() ->
SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext())
.showDesktopApp(task.task1.key.id));
} else if (task.task2 == null) {
- UI_HELPER_EXECUTOR.execute(() ->
- ActivityManagerWrapper.getInstance().startActivityFromRecents(
- task.task1.key,
- mControllers.taskbarActivityContext.getActivityLaunchOptions(
- taskView == null ? mKeyboardQuickSwitchView : taskView, null)
- .options));
+ UI_HELPER_EXECUTOR.execute(() -> {
+ ActivityOptions activityOptions = mControllers.taskbarActivityContext
+ .makeDefaultActivityOptions(SPLASH_SCREEN_STYLE_UNDEFINED).options;
+ activityOptions.setRemoteTransition(remoteTransition);
+
+ ActivityManagerWrapper.getInstance().startActivityFromRecents(
+ task.task1.key, activityOptions);
+ });
} else {
- mControllers.uiController.launchSplitTasks(task);
+ mControllers.uiController.launchSplitTasks(task, remoteTransition);
}
return -1;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
index 159a6ef..d62e117 100644
--- a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java
@@ -27,6 +27,7 @@
import android.util.Log;
import android.view.TaskTransitionSpec;
import android.view.WindowManagerGlobal;
+import android.window.RemoteTransition;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -390,8 +391,9 @@
}
@Override
- public void launchSplitTasks(@NonNull GroupTask groupTask) {
- mLauncher.launchSplitTasks(groupTask);
+ public void launchSplitTasks(
+ @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) {
+ mLauncher.launchSplitTasks(groupTask, remoteTransition);
}
@Override
diff --git a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
index 709d3ba..6d4fc18 100644
--- a/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/NavbarButtonsViewController.java
@@ -61,6 +61,7 @@
import android.graphics.Region;
import android.graphics.Region.Op;
import android.graphics.drawable.AnimatedVectorDrawable;
+import android.graphics.drawable.Drawable;
import android.graphics.drawable.PaintDrawable;
import android.graphics.drawable.RotateDrawable;
import android.inputmethodservice.InputMethodService;
@@ -96,6 +97,7 @@
import com.android.launcher3.util.TouchController;
import com.android.launcher3.util.window.WindowManagerProxy;
import com.android.launcher3.views.BaseDragLayer;
+import com.android.systemui.shared.navigationbar.KeyButtonRipple;
import com.android.systemui.shared.rotation.FloatingRotationButton;
import com.android.systemui.shared.rotation.RotationButton;
import com.android.systemui.shared.rotation.RotationButtonController;
@@ -665,6 +667,11 @@
for (ImageView button : mAllButtons) {
button.setImageTintList(ColorStateList.valueOf(iconColor));
+ Drawable background = button.getBackground();
+ if (background instanceof KeyButtonRipple) {
+ ((KeyButtonRipple) background).setDarkIntensity(
+ mTaskbarNavButtonDarkIntensity.value);
+ }
}
}
@@ -751,6 +758,7 @@
mA11yButton, res, isInKidsMode, isInSetup, isThreeButtonNav,
mContext.isPhoneMode(), mWindowManagerProxy.getRotation(mContext));
navButtonLayoutter.layoutButtons(mContext, isA11yButtonPersistent());
+ updateButtonsBackground();
updateNavButtonColor();
return;
}
@@ -870,7 +878,27 @@
}
}
}
+ }
+ private void updateButtonsBackground() {
+ boolean clipped = !mContext.isPhoneButtonNavMode();
+ mNavButtonContainer.setClipToPadding(clipped);
+ mNavButtonContainer.setClipChildren(clipped);
+ mNavButtonsView.setClipToPadding(clipped);
+ mNavButtonsView.setClipChildren(clipped);
+
+ for (ImageView button : mAllButtons) {
+ updateButtonBackground(button, mContext.isPhoneButtonNavMode());
+ }
+ }
+
+ private static void updateButtonBackground(View view, boolean isPhoneButtonNavMode) {
+ if (isPhoneButtonNavMode) {
+ view.setBackground(new KeyButtonRipple(view.getContext(), view,
+ R.dimen.key_button_ripple_max_width));
+ } else {
+ view.setBackgroundResource(R.drawable.taskbar_icon_click_feedback_roundrect);
+ }
}
public void onDestroy() {
diff --git a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
index ad2dc23..e5a6021 100644
--- a/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/StashedHandleViewController.java
@@ -149,7 +149,7 @@
});
initRegionSampler();
if (mActivity.isPhoneGestureNavMode()) {
- onIsStashedChanged(true);
+ onIsStashedChanged();
}
}
@@ -232,10 +232,10 @@
}
/** Called when taskbar is stashed or unstashed. */
- public void onIsStashedChanged(boolean isStashed) {
- mIsStashed = isStashed;
+ public void onIsStashedChanged() {
+ mIsStashed = isStashedHandleVisible();
updateRegionSamplingWindowVisibility();
- if (isStashed) {
+ if (mIsStashed) {
mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
mRegionSamplingHelper.start(mStashedHandleView.getSampledRegion());
} else {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index fb25ec1..913ca6c 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -42,7 +42,9 @@
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
+import android.app.ActivityManager.RunningTaskInfo;
import android.app.ActivityOptions;
+import android.app.ActivityTaskManager;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
@@ -130,10 +132,13 @@
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
import java.io.PrintWriter;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
+import java.util.Set;
import java.util.function.Consumer;
+import java.util.stream.Collectors;
/**
* The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements
@@ -451,6 +456,10 @@
return mTransientTaskbarBounds;
}
+ protected float getCurrentTaskbarWidth() {
+ return mControllers.taskbarViewController.getCurrentVisualTaskbarWidth();
+ }
+
@Override
public StatsLogManager getStatsLogManager() {
// Used to mock, can't mock a default interface method directly
@@ -1147,15 +1156,25 @@
@Nullable Task foundTask = foundTasks[0];
if (foundTask != null) {
TaskView foundTaskView = recents.getTaskViewByTaskId(foundTask.key.id);
- if (foundTaskView != null
- && foundTaskView.isVisibleToUser()) {
- TestLogging.recordEvent(
- TestProtocol.SEQUENCE_MAIN, "start: taskbarAppIcon");
- foundTaskView.launchTasks();
- return;
+ if (foundTaskView != null) {
+ // The foundTaskView contains the 1-2 taskIds we are looking for.
+ // If we are already in-app and running the correct tasks, no need
+ // to do anything.
+ if (FeatureFlags.enableAppPairs()
+ && isAlreadyInApp(foundTaskView.getTaskIds())) {
+ return;
+ }
+ // If we are in Overview and the TaskView tile is visible, expand that
+ // tile.
+ if (foundTaskView.isVisibleToUser()) {
+ TestLogging.recordEvent(
+ TestProtocol.SEQUENCE_MAIN, "start: taskbarAppIcon");
+ foundTaskView.launchTasks();
+ return;
+ }
}
}
-
+ // If none of the above cases apply, launch a new app or app pair.
if (findExactPairMatch) {
// We did not find the app pair we were looking for, so launch one.
recents.getSplitSelectController().getAppPairsController().launchAppPair(
@@ -1167,6 +1186,27 @@
);
}
+ /**
+ * Checks if a given list of taskIds are all already running in-app.
+ */
+ private boolean isAlreadyInApp(int[] ids) {
+ if (mControllers.uiController.isInOverview()) {
+ return false;
+ }
+
+ RunningTaskInfo[] currentlyRunningTasks = ActivityManagerWrapper.getInstance()
+ .getRunningTasks(false /* filterOnlyVisibleRecents */);
+ Set<Integer> currentlyRunningIds = Arrays.stream(currentlyRunningTasks)
+ .map(task -> task.taskId).collect(Collectors.toSet());
+
+ for (int id : ids) {
+ if (id != ActivityTaskManager.INVALID_TASK_ID && !currentlyRunningIds.contains(id)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
private void startItemInfoActivity(ItemInfo info) {
Intent intent = new Intent(info.getIntent())
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -1305,6 +1345,16 @@
}
}
+ /** Unstashes the Bubble Bar if it is stashed. */
+ @VisibleForTesting
+ public void unstashBubbleBarIfStashed() {
+ mControllers.bubbleControllers.ifPresent(bubbleControllers -> {
+ if (bubbleControllers.bubbleStashController.isStashed()) {
+ bubbleControllers.bubbleStashController.showBubbleBar(false);
+ }
+ });
+ }
+
protected boolean isUserSetupComplete() {
return mIsUserSetupComplete;
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
index 30f8d56..e290c3f 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarBackgroundRenderer.kt
@@ -203,12 +203,7 @@
val newBackgroundHeight =
mapRange(progress, backgroundHeightWhileAnimating, maxTransientTaskbarHeight)
val fullWidth = transientBackgroundBounds.width()
-
- // .9f is here to restrict min width of the background while animating, so transient
- // background keeps it pill shape until animation end.
- val animationWidth =
- if (DisplayController.isTransientTaskbar(context)) fullWidth.toFloat() * .9f
- else fullWidth.toFloat()
+ val animationWidth = context.currentTaskbarWidth
val backgroundWidthWhileAnimating =
if (isAnimatingPinning) animationWidth else stashedHandleWidth.toFloat()
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
index 3f9b66a..dab9950 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDividerPopupView.kt
@@ -16,7 +16,6 @@
package com.android.launcher3.taskbar
import android.animation.Animator
-import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
@@ -197,32 +196,19 @@
mActivityContext.deviceProfile.taskbarIconSize) / 2 + verticalOffsetForPopupView
}
- override fun animateClose() {
- if (!mIsOpen) {
- return
+ override fun onCreateCloseAnimation(anim: AnimatorSet?) {
+ // If taskbar pinning preference changed insert custom close animation for popup menu.
+ if (didPreferenceChange) {
+ mOpenCloseAnimator = getCloseAnimator()
}
- if (mOpenCloseAnimator != null) {
- mOpenCloseAnimator.cancel()
- }
- mIsOpen = false
-
- mOpenCloseAnimator = getCloseAnimator()
-
- mOpenCloseAnimator.addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- mOpenCloseAnimator = null
- if (mDeferContainerRemoval) {
- setVisibility(INVISIBLE)
- } else {
- closeComplete()
- }
- }
- }
- )
onCloseCallback(didPreferenceChange)
onCloseCallback = {}
- mOpenCloseAnimator.start()
+ }
+
+ /** Aligning the view pivot to center for animation. */
+ override fun setPivotForOpenCloseAnimation() {
+ pivotX = measuredWidth / 2f
+ pivotY = measuredHeight.toFloat()
}
private fun getCloseAnimator(): AnimatorSet {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
index 1a34b7a..7ebc18d 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarInsetsController.kt
@@ -101,8 +101,10 @@
windowLayoutParams.providedInsets =
if (enableTaskbarNoRecreate()) {
- getProvidedInsets(controllers.sharedState!!.insetsFrameProviders!!,
- insetsRoundedCornerFlag)
+ getProvidedInsets(
+ controllers.sharedState!!.insetsFrameProviders!!,
+ insetsRoundedCornerFlag
+ )
} else {
getProvidedInsets(insetsRoundedCornerFlag)
}
@@ -164,19 +166,18 @@
/**
* This is for when ENABLE_TASKBAR_NO_RECREATION is enabled. We generate one instance of
- * providedInsets and use it across the entire lifecycle of TaskbarManager. The only thing
- * we need to reset is nav bar flags based on insetsRoundedCornerFlag.
+ * providedInsets and use it across the entire lifecycle of TaskbarManager. The only thing we
+ * need to reset is nav bar flags based on insetsRoundedCornerFlag.
*/
- private fun getProvidedInsets(providedInsets: Array<InsetsFrameProvider>,
- insetsRoundedCornerFlag: Int): Array<InsetsFrameProvider> {
+ private fun getProvidedInsets(
+ providedInsets: Array<InsetsFrameProvider>,
+ insetsRoundedCornerFlag: Int
+ ): Array<InsetsFrameProvider> {
val navBarsFlag =
- (if (context.isGestureNav) FLAG_SUPPRESS_SCRIM else 0) or insetsRoundedCornerFlag
+ (if (context.isGestureNav) FLAG_SUPPRESS_SCRIM else 0) or insetsRoundedCornerFlag
for (provider in providedInsets) {
if (provider.type == navigationBars()) {
- provider.setFlags(
- navBarsFlag,
- FLAG_SUPPRESS_SCRIM or FLAG_INSETS_ROUNDED_CORNER
- )
+ provider.setFlags(navBarsFlag, FLAG_SUPPRESS_SCRIM or FLAG_INSETS_ROUNDED_CORNER)
}
}
return providedInsets
@@ -184,25 +185,22 @@
/**
* The inset types and number of insets provided have to match for both gesture nav and button
- * nav. The values and the order of the elements in array are allowed to differ.
- * Reason being WM does not allow types and number of insets changing for a given window once it
- * is added into the hierarchy for performance reasons.
+ * nav. The values and the order of the elements in array are allowed to differ. Reason being WM
+ * does not allow types and number of insets changing for a given window once it is added into
+ * the hierarchy for performance reasons.
*/
private fun getProvidedInsets(insetsRoundedCornerFlag: Int): Array<InsetsFrameProvider> {
val navBarsFlag =
- (if (context.isGestureNav) FLAG_SUPPRESS_SCRIM else 0) or insetsRoundedCornerFlag
+ (if (context.isGestureNav) FLAG_SUPPRESS_SCRIM else 0) or insetsRoundedCornerFlag
return arrayOf(
- InsetsFrameProvider(insetsOwner, 0, navigationBars())
- .setFlags(
- navBarsFlag,
- FLAG_SUPPRESS_SCRIM or FLAG_INSETS_ROUNDED_CORNER
- ),
- InsetsFrameProvider(insetsOwner, 0, tappableElement()),
- InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures()),
- InsetsFrameProvider(insetsOwner, INDEX_LEFT, systemGestures())
- .setSource(SOURCE_DISPLAY),
- InsetsFrameProvider(insetsOwner, INDEX_RIGHT, systemGestures())
- .setSource(SOURCE_DISPLAY)
+ InsetsFrameProvider(insetsOwner, 0, navigationBars())
+ .setFlags(navBarsFlag, FLAG_SUPPRESS_SCRIM or FLAG_INSETS_ROUNDED_CORNER),
+ InsetsFrameProvider(insetsOwner, 0, tappableElement()),
+ InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures()),
+ InsetsFrameProvider(insetsOwner, INDEX_LEFT, systemGestures())
+ .setSource(SOURCE_DISPLAY),
+ InsetsFrameProvider(insetsOwner, INDEX_RIGHT, systemGestures())
+ .setSource(SOURCE_DISPLAY)
)
}
@@ -216,46 +214,52 @@
provider.insetsSize = getInsetsForGravity(tappableHeight, gravity)
} else if (provider.type == systemGestures() && provider.index == INDEX_LEFT) {
val leftIndexInset =
- if (context.isThreeButtonNav) 0
- else gestureNavSettingsObserver.getLeftSensitivityForCallingUser(res)
+ if (context.isThreeButtonNav) 0
+ else gestureNavSettingsObserver.getLeftSensitivityForCallingUser(res)
provider.insetsSize = Insets.of(leftIndexInset, 0, 0, 0)
} else if (provider.type == systemGestures() && provider.index == INDEX_RIGHT) {
val rightIndexInset =
- if (context.isThreeButtonNav) 0
- else gestureNavSettingsObserver.getRightSensitivityForCallingUser(res)
+ if (context.isThreeButtonNav) 0
+ else gestureNavSettingsObserver.getRightSensitivityForCallingUser(res)
provider.insetsSize = Insets.of(0, 0, rightIndexInset, 0)
}
// When in gesture nav, report the stashed height to the IME, to allow hiding the
// IME navigation bar.
- val imeInsetsSize = if (ENABLE_HIDE_IME_CAPTION_BAR && context.isGestureNav) {
- getInsetsForGravity(controllers.taskbarStashController.stashedHeight, gravity);
- } else {
- getInsetsForGravity(taskbarHeightForIme, gravity)
- }
+ val imeInsetsSize =
+ if (ENABLE_HIDE_IME_CAPTION_BAR && context.isGestureNav) {
+ getInsetsForGravity(controllers.taskbarStashController.stashedHeight, gravity)
+ } else {
+ getInsetsForGravity(taskbarHeightForIme, gravity)
+ }
val imeInsetsSizeOverride =
- arrayOf(
- InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
- InsetsFrameProvider.InsetsSizeOverride(TYPE_VOICE_INTERACTION,
- // No-op override to keep the size and types in sync with the
- // override below (insetsSizeOverrides must have the same length and
- // types after the window is added according to
- // WindowManagerService#relayoutWindow)
- provider.insetsSize)
+ arrayOf(
+ InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
+ InsetsFrameProvider.InsetsSizeOverride(
+ TYPE_VOICE_INTERACTION,
+ // No-op override to keep the size and types in sync with the
+ // override below (insetsSizeOverrides must have the same length and
+ // types after the window is added according to
+ // WindowManagerService#relayoutWindow)
+ provider.insetsSize
)
+ )
// Use 0 tappableElement insets for the VoiceInteractionWindow when gesture nav is enabled.
val visInsetsSizeForTappableElement =
- if (context.isGestureNav) getInsetsForGravity(0, gravity)
- else getInsetsForGravity(tappableHeight, gravity)
+ if (context.isGestureNav) getInsetsForGravity(0, gravity)
+ else getInsetsForGravity(tappableHeight, gravity)
val insetsSizeOverrideForTappableElement =
- arrayOf(
- InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
- InsetsFrameProvider.InsetsSizeOverride(TYPE_VOICE_INTERACTION,
- visInsetsSizeForTappableElement
- ),
- )
- if ((context.isGestureNav || ENABLE_TASKBAR_NAVBAR_UNIFICATION)
- && provider.type == tappableElement()) {
+ arrayOf(
+ InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
+ InsetsFrameProvider.InsetsSizeOverride(
+ TYPE_VOICE_INTERACTION,
+ visInsetsSizeForTappableElement
+ ),
+ )
+ if (
+ (context.isGestureNav || ENABLE_TASKBAR_NAVBAR_UNIFICATION) &&
+ provider.type == tappableElement()
+ ) {
provider.insetsSizeOverrides = insetsSizeOverrideForTappableElement
} else if (provider.type != systemGestures()) {
// We only override insets at the bottom of the screen
@@ -264,8 +268,8 @@
}
/**
- * @return [Insets] where the [inset] is either used as a bottom inset or
- * right/left inset if using 3 button nav
+ * @return [Insets] where the [inset] is either used as a bottom inset or right/left inset if
+ * using 3 button nav
*/
private fun getInsetsForGravity(inset: Int, gravity: Int): Insets {
if ((gravity and Gravity.BOTTOM) == Gravity.BOTTOM) {
@@ -277,7 +281,7 @@
val isSeascape = (gravity and Gravity.START) == Gravity.START
val leftInset = if (isSeascape) inset else 0
val rightInset = if (isSeascape) 0 else inset
- return Insets.of(leftInset , 0, rightInset, 0)
+ return Insets.of(leftInset, 0, rightInset, 0)
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 831bc11..a2e5e81 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -25,6 +25,9 @@
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate;
+import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY;
+import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
+import static com.android.launcher3.util.DisplayController.CHANGE_TASKBAR_PINNING;
import static com.android.launcher3.util.DisplayController.TASKBAR_NOT_DESTROYED_TAG;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
@@ -55,7 +58,6 @@
import androidx.annotation.VisibleForTesting;
import com.android.launcher3.DeviceProfile;
-import com.android.launcher3.InvariantDeviceProfile.OnIDPChangeListener;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.statemanager.StatefulActivity;
@@ -71,7 +73,6 @@
import com.android.systemui.shared.system.QuickStepContract;
import com.android.systemui.unfold.UnfoldTransitionProgressProvider;
import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider;
-import com.android.wm.shell.Flags;
import java.io.PrintWriter;
import java.util.StringJoiner;
@@ -135,7 +136,17 @@
* We use WindowManager's ComponentCallbacks() for internal UI changes (similar to an Activity)
* which comes via a different channel
*/
- private final OnIDPChangeListener mIdpChangeListener = c -> recreateTaskbar();
+ private final RecreationListener mRecreationListener = new RecreationListener();
+
+ private class RecreationListener implements DisplayController.DisplayInfoChangeListener {
+ @Override
+ public void onDisplayInfoChanged(Context context, DisplayController.Info info, int flags) {
+ if ((flags & (CHANGE_DENSITY | CHANGE_NAVIGATION_MODE
+ | CHANGE_TASKBAR_PINNING)) != 0) {
+ recreateTaskbar();
+ }
+ }
+ }
private final SettingsCache.OnChangeListener mOnSettingsChangeListener = c -> recreateTaskbar();
private boolean mUserUnlocked = false;
@@ -329,7 +340,10 @@
return;
}
- if (mActivity != null && mActivity.isResumed() && !mActivity.isInState(OVERVIEW)) {
+ if (mActivity != null
+ && mActivity.isResumed()
+ && !mActivity.isInState(OVERVIEW)
+ && !(mActivity instanceof QuickstepLauncher l && l.areFreeformTasksVisible())) {
mContext.startActivity(homeAllAppsIntent);
return;
}
@@ -353,7 +367,7 @@
*/
public void onUserUnlocked() {
mUserUnlocked = true;
- LauncherAppState.getIDP(mContext).addOnChangeListener(mIdpChangeListener);
+ DisplayController.INSTANCE.get(mContext).addChangeListener(mRecreationListener);
recreateTaskbar();
addTaskbarRootViewToWindow();
}
@@ -551,7 +565,7 @@
() -> mTaskbarBroadcastReceiver.unregisterReceiverSafely(mContext));
destroyExistingTaskbar();
if (mUserUnlocked) {
- LauncherAppState.getIDP(mContext).removeOnChangeListener(mIdpChangeListener);
+ DisplayController.INSTANCE.get(mContext).removeChangeListener(mRecreationListener);
}
SettingsCache.INSTANCE.get(mContext)
.unregister(USER_SETUP_COMPLETE_URI, mOnSettingsChangeListener);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt b/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
index 6cb28ee..5b117fc 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarPinningController.kt
@@ -20,6 +20,7 @@
import android.view.View
import androidx.annotation.VisibleForTesting
import androidx.core.animation.doOnEnd
+import com.android.app.animation.Interpolators
import com.android.launcher3.LauncherPrefs
import com.android.launcher3.LauncherPrefs.Companion.TASKBAR_PINNING
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_DIVIDER_MENU_CLOSE
@@ -106,6 +107,7 @@
taskbarViewController.taskbarIconTranslationXForPinning.animateToValue(animateToValue)
)
+ animatorSet.interpolator = Interpolators.EMPHASIZED
return animatorSet
}
@@ -129,6 +131,6 @@
companion object {
const val PINNING_PERSISTENT = 1f
const val PINNING_TRANSIENT = 0f
- const val PINNING_ANIMATION_DURATION = 500L
+ const val PINNING_ANIMATION_DURATION = 600L
}
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index b3a8b93..eced202 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -590,6 +590,7 @@
mAnimator.addListener(AnimatorListeners.forEndCallback(() -> {
mAnimator = null;
mIsStashed = isStashed;
+ onIsStashedChanged();
}));
return;
}
@@ -604,7 +605,7 @@
@Override
public void onAnimationStart(Animator animation) {
mIsStashed = isStashed;
- onIsStashedChanged(mIsStashed);
+ onIsStashedChanged();
cancelTimeoutIfExists();
}
@@ -829,9 +830,9 @@
.setDuration(TASKBAR_HINT_STASH_DURATION).start();
}
- private void onIsStashedChanged(boolean isStashed) {
+ private void onIsStashedChanged() {
mControllers.runAfterInit(() -> {
- mControllers.stashedHandleViewController.onIsStashedChanged(isStashed);
+ mControllers.stashedHandleViewController.onIsStashedChanged();
mControllers.taskbarInsetsController.onTaskbarOrBubblebarWindowHeightOrInsetsChanged();
});
}
@@ -930,19 +931,26 @@
}
/**
- * We stash when IME or IME switcher is showing AND NOT
- * * in small screen AND
- * * 3 button nav AND
- * * landscape (or seascape)
- * We do not stash if taskbar is transient or hardware keyboard is active.
+ * We stash when IME or IME switcher is showing.
+ *
+ * <p>Do not stash if in small screen, with 3 button nav, and in landscape (or seascape).
+ * <p>Do not stash if taskbar is transient.
+ * <p>Do not stash if hardware keyboard is attached and taskbar is pinned.
*/
private boolean shouldStashForIme() {
- if (DisplayController.isTransientTaskbar(mActivity) || mActivity.isHardwareKeyboard()) {
+ if (DisplayController.isTransientTaskbar(mActivity)) {
return false;
}
- return (mIsImeShowing || mIsImeSwitcherShowing) &&
- !(mActivity.isPhoneMode() && mActivity.isThreeButtonNav()
- && mActivity.getDeviceProfile().isLandscape);
+ // Do not stash if in small screen, with 3 button nav, and in landscape.
+ if (mActivity.isPhoneMode() && mActivity.isThreeButtonNav()
+ && mActivity.getDeviceProfile().isLandscape) {
+ return false;
+ }
+ // Do not stash if pinned taskbar and hardware keyboard is attached.
+ if (mActivity.isHardwareKeyboard() && DisplayController.isPinnedTaskbar(mActivity)) {
+ return false;
+ }
+ return mIsImeShowing || mIsImeSwitcherShowing;
}
/**
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
index 7edf0d3..ecedf8a 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java
@@ -25,6 +25,7 @@
import android.graphics.drawable.BitmapDrawable;
import android.view.MotionEvent;
import android.view.View;
+import android.window.RemoteTransition;
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
@@ -302,7 +303,8 @@
/**
* Launches the given task in split-screen.
*/
- public void launchSplitTasks(@NonNull GroupTask groupTask) { }
+ public void launchSplitTasks(
+ @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) { }
/**
* Returns the matching view (if any) in the taskbar.
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
index 33fb395..1f03f24 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarViewController.java
@@ -159,6 +159,8 @@
private final int mTransientIconSize;
private final int mPersistentIconSize;
+ private final float mTaskbarLeftRightMargin;
+
public TaskbarViewController(TaskbarActivityContext activity, TaskbarView taskbarView) {
mActivity = activity;
mTransientTaskbarDp = mActivity.getTransientTaskbarDeviceProfile();
@@ -184,6 +186,9 @@
mTaskbarThemedIconsBackgroundColor = ColorUtils.HSLToColor(colorHSL);
}
mIsRtl = Utilities.isRtl(mTaskbarView.getResources());
+ mTaskbarLeftRightMargin = mActivity.getResources().getDimensionPixelSize(
+ R.dimen.transient_taskbar_padding);
+
}
public void init(TaskbarControllers controllers) {
@@ -391,6 +396,26 @@
}
}
+ /**
+ * Calculates visual taskbar view width.
+ */
+ public float getCurrentVisualTaskbarWidth() {
+ if (mTaskbarView.getIconViews().length == 0) {
+ return 0;
+ }
+
+ View[] iconViews = mTaskbarView.getIconViews();
+
+ int leftIndex = mActivity.getDeviceProfile().isQsbInline && !mIsRtl ? 1 : 0;
+ int rightIndex = mActivity.getDeviceProfile().isQsbInline && mIsRtl
+ ? iconViews.length - 2
+ : iconViews.length - 1;
+
+ float left = iconViews[leftIndex].getX();
+ float right = iconViews[rightIndex].getRight() + iconViews[rightIndex].getTranslationX();
+
+ return right - left + (2 * mTaskbarLeftRightMargin);
+ }
/**
* Sets the translation of the TaskbarView during the swipe up gesture.
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
index b516d6f..23e3310 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/AbstractNavButtonLayoutter.kt
@@ -65,18 +65,19 @@
fun getParamsToCenterView(): FrameLayout.LayoutParams {
val params = FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)
params.gravity = Gravity.CENTER
return params;
}
- open fun repositionContextualContainer(contextualContainer: ViewGroup, barAxisMargin: Int,
+ open fun repositionContextualContainer(contextualContainer: ViewGroup, buttonSize: Int,
+ barAxisMarginStart: Int, barAxisMarginEnd: Int,
gravity: Int) {
val contextualContainerParams = FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT)
+ buttonSize, ViewGroup.LayoutParams.MATCH_PARENT)
contextualContainerParams.apply {
- marginStart = barAxisMargin
- marginEnd = barAxisMargin
+ marginStart = barAxisMarginStart
+ marginEnd = barAxisMarginEnd
topMargin = 0
bottomMargin = 0
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
index 65b77ac..f31af09 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/KidsNavLayoutter.kt
@@ -21,6 +21,7 @@
import android.graphics.drawable.PaintDrawable
import android.view.Gravity
import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
@@ -103,8 +104,9 @@
val contextualMargin = resources.getDimensionPixelSize(
R.dimen.taskbar_contextual_button_padding)
- repositionContextualContainer(endContextualContainer, 0, Gravity.END)
- repositionContextualContainer(startContextualContainer, contextualMargin, Gravity.START)
+ repositionContextualContainer(endContextualContainer, WRAP_CONTENT, 0, 0, Gravity.END)
+ repositionContextualContainer(startContextualContainer, WRAP_CONTENT, contextualMargin,
+ contextualMargin, Gravity.START)
if (imeSwitcher != null) {
startContextualContainer.addView(imeSwitcher)
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
index 7583cc1..b1b50d6 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneLandscapeNavLayoutter.kt
@@ -22,10 +22,8 @@
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
-import androidx.core.view.children
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarActivityContext
-import com.android.launcher3.util.DimensionUtils
import com.android.systemui.shared.rotation.RotationButton
open class PhoneLandscapeNavLayoutter(
@@ -48,48 +46,67 @@
) {
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
- // TODO(b/230395757): Polish pending, this is just to make it usable
- val endStartMargins = resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
- val taskbarDimensions = DimensionUtils.getTaskbarPhoneDimensions(context.deviceProfile,
- resources, context.isPhoneMode)
- navButtonContainer.removeAllViews()
- navButtonContainer.orientation = LinearLayout.VERTICAL
+ val totalHeight = context.deviceProfile.heightPx
+ val homeButtonHeight = resources.getDimensionPixelSize(
+ R.dimen.taskbar_phone_home_button_size)
+ val roundedCornerContentMargin = resources.getDimensionPixelSize(
+ R.dimen.taskbar_phone_rounded_corner_content_margin)
+ val contentPadding = resources.getDimensionPixelSize(R.dimen.taskbar_phone_content_padding)
+ val contentWidth = totalHeight - roundedCornerContentMargin * 2 - contentPadding * 2
+
+ // left:back:space(reserved for home):overview:right = 0.25:0.5:1:0.5:0.25
+ val contextualButtonHeight = contentWidth / (0.25f + 0.5f + 1f + 0.5f + 0.25f) * 0.25f
+ val sideButtonHeight = contextualButtonHeight * 2
+ val navButtonContainerHeight = contentWidth - contextualButtonHeight * 2
val navContainerParams = FrameLayout.LayoutParams(
- taskbarDimensions.x, ViewGroup.LayoutParams.MATCH_PARENT)
+ ViewGroup.LayoutParams.MATCH_PARENT, navButtonContainerHeight.toInt())
navContainerParams.apply {
- topMargin = endStartMargins
- bottomMargin = endStartMargins
+ topMargin =
+ (contextualButtonHeight + contentPadding + roundedCornerContentMargin).toInt()
+ bottomMargin =
+ (contextualButtonHeight + contentPadding + roundedCornerContentMargin).toInt()
marginEnd = 0
marginStart = 0
}
- navButtonContainer.layoutParams = navContainerParams
- navButtonContainer.gravity = Gravity.CENTER
+ // Ensure order of buttons is correct
+ navButtonContainer.removeAllViews()
+ navButtonContainer.orientation = LinearLayout.VERTICAL
addThreeButtons()
+ navButtonContainer.layoutParams = navContainerParams
+ navButtonContainer.gravity = Gravity.CENTER
+
// Add the spaces in between the nav buttons
- val spaceInBetween: Int =
- resources.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween_phone)
- navButtonContainer.children.forEachIndexed { i, navButton ->
+ val spaceInBetween = (navButtonContainerHeight - homeButtonHeight -
+ sideButtonHeight * 2) / 2.0f
+ for (i in 0 until navButtonContainer.childCount) {
+ val navButton = navButtonContainer.getChildAt(i)
val buttonLayoutParams = navButton.layoutParams as LinearLayout.LayoutParams
- buttonLayoutParams.weight = 1f
+ val margin = (spaceInBetween / 2).toInt()
when (i) {
0 -> {
- buttonLayoutParams.bottomMargin = spaceInBetween / 2
+ // First button
+ buttonLayoutParams.bottomMargin = margin
+ buttonLayoutParams.height = sideButtonHeight.toInt()
}
navButtonContainer.childCount - 1 -> {
- buttonLayoutParams.topMargin = spaceInBetween / 2
+ // Last button
+ buttonLayoutParams.topMargin = margin
+ buttonLayoutParams.height = sideButtonHeight.toInt()
}
else -> {
- buttonLayoutParams.bottomMargin = spaceInBetween / 2
- buttonLayoutParams.topMargin = spaceInBetween / 2
+ // other buttons
+ buttonLayoutParams.topMargin = margin
+ buttonLayoutParams.bottomMargin = margin
+ buttonLayoutParams.height = homeButtonHeight
}
}
}
- repositionContextualButtons()
+ repositionContextualButtons(contextualButtonHeight.toInt())
}
open fun addThreeButtons() {
@@ -99,13 +116,15 @@
navButtonContainer.addView(backButton)
}
- open fun repositionContextualButtons() {
+ open fun repositionContextualButtons(buttonSize: Int) {
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
- val contextualMargin = resources.getDimensionPixelSize(
- R.dimen.taskbar_contextual_button_padding)
- repositionContextualContainer(startContextualContainer, contextualMargin, Gravity.TOP)
+ val roundedCornerContentMargin = resources.getDimensionPixelSize(
+ R.dimen.taskbar_phone_rounded_corner_content_margin)
+ val contentPadding = resources.getDimensionPixelSize(R.dimen.taskbar_phone_content_padding)
+ repositionContextualContainer(startContextualContainer, buttonSize,
+ roundedCornerContentMargin + contentPadding, 0, Gravity.TOP)
if (imeSwitcher != null) {
startContextualContainer.addView(imeSwitcher)
@@ -120,15 +139,16 @@
}
}
- override fun repositionContextualContainer(contextualContainer: ViewGroup, barAxisMargin: Int,
+ override fun repositionContextualContainer(contextualContainer: ViewGroup, buttonSize: Int,
+ barAxisMarginTop: Int, barAxisMarginBottom: Int,
gravity: Int) {
val contextualContainerParams = FrameLayout.LayoutParams(
- ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+ ViewGroup.LayoutParams.MATCH_PARENT, buttonSize)
contextualContainerParams.apply {
marginStart = 0
marginEnd = 0
- topMargin = barAxisMargin
- bottomMargin = barAxisMargin
+ topMargin = barAxisMarginTop
+ bottomMargin = barAxisMarginBottom
}
contextualContainerParams.gravity = gravity or Gravity.CENTER_HORIZONTAL
contextualContainer.layoutParams = contextualContainerParams
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
index 4388ce6..05183b8 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhonePortraitNavLayoutter.kt
@@ -24,7 +24,6 @@
import android.widget.LinearLayout
import com.android.launcher3.R
import com.android.launcher3.taskbar.TaskbarActivityContext
-import com.android.launcher3.util.DimensionUtils
import com.android.systemui.shared.rotation.RotationButton
class PhonePortraitNavLayoutter(
@@ -47,26 +46,33 @@
) {
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
- // TODO(b/230395757): Polish pending, this is just to make it usable
- val taskbarDimensions =
- DimensionUtils.getTaskbarPhoneDimensions(context.deviceProfile, resources,
- context.isPhoneMode)
- val endStartMargins = resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size)
+ val totalWidth = context.deviceProfile.widthPx
+ val homeButtonWidth = resources.getDimensionPixelSize(R.dimen.taskbar_phone_home_button_size)
+ val roundedCornerContentMargin = resources.getDimensionPixelSize(
+ R.dimen.taskbar_phone_rounded_corner_content_margin)
+ val contentPadding = resources.getDimensionPixelSize(R.dimen.taskbar_phone_content_padding)
+ val contentWidth = totalWidth - roundedCornerContentMargin * 2 - contentPadding * 2
+
+ // left:back:space(reserved for home):overview:right = 0.25:0.5:1:0.5:0.25
+ val contextualButtonWidth = contentWidth / (0.25f + 0.5f + 1f + 0.5f + 0.25f) * 0.25f
+ val sideButtonWidth = contextualButtonWidth * 2
+ val navButtonContainerWidth = contentWidth - contextualButtonWidth * 2
+
+ val navContainerParams = FrameLayout.LayoutParams(navButtonContainerWidth.toInt(),
+ ViewGroup.LayoutParams.MATCH_PARENT)
+ navContainerParams.apply {
+ topMargin = 0
+ bottomMargin = 0
+ marginEnd =
+ (contextualButtonWidth + contentPadding + roundedCornerContentMargin).toInt()
+ marginStart =
+ (contextualButtonWidth + contentPadding + roundedCornerContentMargin).toInt()
+ }
// Ensure order of buttons is correct
navButtonContainer.removeAllViews()
navButtonContainer.orientation = LinearLayout.HORIZONTAL
- val navContainerParams = FrameLayout.LayoutParams(
- taskbarDimensions.x, ViewGroup.LayoutParams.MATCH_PARENT)
- navContainerParams.apply {
- topMargin = 0
- bottomMargin = 0
- marginEnd = endStartMargins
- marginStart = endStartMargins
- }
-
- // Swap recents and back button in case we were landscape prior to this
navButtonContainer.addView(backButton)
navButtonContainer.addView(homeButton)
navButtonContainer.addView(recentsButton)
@@ -75,25 +81,28 @@
navButtonContainer.gravity = Gravity.CENTER
// Add the spaces in between the nav buttons
- val spaceInBetween =
- resources.getDimensionPixelSize(R.dimen.taskbar_button_space_inbetween_phone)
+ val spaceInBetween = (navButtonContainerWidth - homeButtonWidth -
+ sideButtonWidth * 2) / 2.0f
for (i in 0 until navButtonContainer.childCount) {
val navButton = navButtonContainer.getChildAt(i)
val buttonLayoutParams = navButton.layoutParams as LinearLayout.LayoutParams
- buttonLayoutParams.weight = 1f
+ val margin = (spaceInBetween / 2).toInt()
when (i) {
0 -> {
// First button
- buttonLayoutParams.marginEnd = spaceInBetween / 2
+ buttonLayoutParams.marginEnd = margin
+ buttonLayoutParams.width = sideButtonWidth.toInt()
}
navButtonContainer.childCount - 1 -> {
// Last button
- buttonLayoutParams.marginStart = spaceInBetween / 2
+ buttonLayoutParams.marginStart = margin
+ buttonLayoutParams.width = sideButtonWidth.toInt()
}
else -> {
// other buttons
- buttonLayoutParams.marginStart = spaceInBetween / 2
- buttonLayoutParams.marginEnd = spaceInBetween / 2
+ buttonLayoutParams.marginStart = margin
+ buttonLayoutParams.marginEnd = margin
+ buttonLayoutParams.width = homeButtonWidth
}
}
}
@@ -101,9 +110,8 @@
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
- val contextualMargin = resources.getDimensionPixelSize(
- R.dimen.taskbar_contextual_button_padding)
- repositionContextualContainer(endContextualContainer, contextualMargin, Gravity.END)
+ repositionContextualContainer(endContextualContainer, contextualButtonWidth.toInt(), 0,
+ roundedCornerContentMargin + contentPadding, Gravity.END)
if (imeSwitcher != null) {
endContextualContainer.addView(imeSwitcher)
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
index 0368b1d..0f52552 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/PhoneSeascapeNavLayoutter.kt
@@ -50,13 +50,15 @@
navButtonContainer.addView(recentsButton)
}
- override fun repositionContextualButtons() {
+ override fun repositionContextualButtons(buttonSize: Int) {
endContextualContainer.removeAllViews()
startContextualContainer.removeAllViews()
- val contextualMargin = resources.getDimensionPixelSize(
- R.dimen.taskbar_contextual_button_padding)
- repositionContextualContainer(endContextualContainer, contextualMargin, Gravity.BOTTOM)
+ val roundedCornerContentMargin = resources.getDimensionPixelSize(
+ R.dimen.taskbar_phone_rounded_corner_content_margin)
+ val contentPadding = resources.getDimensionPixelSize(R.dimen.taskbar_phone_content_padding)
+ repositionContextualContainer(endContextualContainer, buttonSize, 0,
+ roundedCornerContentMargin + contentPadding, Gravity.BOTTOM)
if (imeSwitcher != null) {
endContextualContainer.addView(imeSwitcher)
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
index 1ac0060..5111bba 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/SetupNavLayoutter.kt
@@ -19,6 +19,7 @@
import android.content.res.Resources
import android.view.Gravity
import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
@@ -61,8 +62,9 @@
val contextualMargin = resources.getDimensionPixelSize(
R.dimen.taskbar_contextual_button_padding)
- repositionContextualContainer(endContextualContainer, 0, Gravity.END)
- repositionContextualContainer(startContextualContainer, contextualMargin, Gravity.START)
+ repositionContextualContainer(endContextualContainer, WRAP_CONTENT, 0, 0, Gravity.END)
+ repositionContextualContainer(startContextualContainer, WRAP_CONTENT, contextualMargin,
+ contextualMargin, Gravity.START)
if (imeSwitcher != null) {
startContextualContainer.addView(imeSwitcher)
diff --git a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
index 5465b6b..45dbebb 100644
--- a/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
+++ b/quickstep/src/com/android/launcher3/taskbar/navbutton/TaskbarNavLayoutter.kt
@@ -19,6 +19,7 @@
import android.content.res.Resources
import android.view.Gravity
import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import android.widget.FrameLayout
import android.widget.ImageView
import android.widget.LinearLayout
@@ -96,8 +97,9 @@
if (!context.deviceProfile.isGestureMode) {
val contextualMargin = resources.getDimensionPixelSize(
R.dimen.taskbar_contextual_button_padding)
- repositionContextualContainer(endContextualContainer, 0, Gravity.END)
- repositionContextualContainer(startContextualContainer, contextualMargin, Gravity.START)
+ repositionContextualContainer(endContextualContainer, WRAP_CONTENT, 0, 0, Gravity.END)
+ repositionContextualContainer(startContextualContainer, WRAP_CONTENT, contextualMargin,
+ contextualMargin, Gravity.START)
if (imeSwitcher != null) {
startContextualContainer.addView(imeSwitcher)
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index a065387..71f4faf 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -84,6 +84,7 @@
import android.window.BackEvent;
import android.window.OnBackAnimationCallback;
import android.window.OnBackInvokedDispatcher;
+import android.window.RemoteTransition;
import android.window.SplashScreen;
import androidx.annotation.BinderThread;
@@ -266,8 +267,8 @@
mAppTransitionManager.registerRemoteTransitions();
if (FeatureFlags.enableHomeTransitionListener()) {
- mHomeTransitionController = new HomeTransitionController(this);
- mHomeTransitionController.registerHomeTransitionListener();
+ mHomeTransitionController = new HomeTransitionController();
+ mHomeTransitionController.registerHomeTransitionListener(this);
}
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
@@ -1271,7 +1272,8 @@
/**
* Launches the given {@link GroupTask} in splitscreen.
*/
- public void launchSplitTasks(@NonNull GroupTask groupTask) {
+ public void launchSplitTasks(
+ @NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) {
// Top/left and bottom/right tasks respectively.
Task task1 = groupTask.task1;
// task2 should never be null when calling this method. Allow a crash to catch invalid calls
@@ -1285,7 +1287,8 @@
/* freezeTaskList= */ false,
groupTask.mSplitBounds == null
? SNAP_TO_50_50
- : groupTask.mSplitBounds.snapPosition);
+ : groupTask.mSplitBounds.snapPosition,
+ remoteTransition);
}
/**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
index f66bc60..1a75535 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepWidgetHolder.java
@@ -67,8 +67,6 @@
private static AppWidgetHost sWidgetHost = null;
- private final SparseArray<AppWidgetHostView> mViews = new SparseArray<>();
-
private final @Nullable RemoteViews.InteractionHandler mInteractionHandler;
private final @NonNull IntConsumer mAppWidgetRemovedCallback;
@@ -112,16 +110,12 @@
@Override
protected void updateDeferredView() {
- super.updateDeferredView();
int count = mPendingUpdateMap.size();
for (int i = 0; i < count; i++) {
int widgetId = mPendingUpdateMap.keyAt(i);
AppWidgetHostView view = mViews.get(widgetId);
- if (view == null) {
- continue;
- }
PendingUpdate pendingUpdate = mPendingUpdateMap.valueAt(i);
- if (pendingUpdate == null) {
+ if (view == null || pendingUpdate == null) {
continue;
}
if (pendingUpdate.providerInfo != null) {
@@ -172,7 +166,6 @@
@Override
public void deleteAppWidgetId(int appWidgetId) {
super.deleteAppWidgetId(appWidgetId);
- mViews.remove(appWidgetId);
sListeners.remove(appWidgetId);
}
@@ -238,7 +231,6 @@
@Override
public LauncherAppWidgetHostView createView(@NonNull Context context, int appWidgetId,
@NonNull LauncherAppWidgetProviderInfo appWidget) {
-
if (appWidget.isCustomWidget()) {
LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(context);
lahv.setAppWidget(appWidgetId, appWidget);
@@ -252,7 +244,6 @@
} else {
widgetView = new LauncherAppWidgetHostView(context);
}
- widgetView.setIsWidgetCachingDisabled(true);
widgetView.setInteractionHandler(mInteractionHandler);
widgetView.setAppWidget(appWidgetId, appWidget);
mViews.put(appWidgetId, widgetView);
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
index 1d55da3..856b519 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/OverviewModalTaskState.java
@@ -62,12 +62,6 @@
@Override
public void onBackPressed(Launcher launcher) {
launcher.getStateManager().goToState(LauncherState.OVERVIEW);
- RecentsView recentsView = launcher.<RecentsView>getOverviewPanel();
- if (recentsView != null) {
- recentsView.resetModalVisuals();
- } else {
- super.onBackPressed(launcher);
- }
}
@Override
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 42b18bd..cbf6ad6 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -1572,7 +1572,8 @@
mSwipePipToHomeAnimator.getTaskId(),
mSwipePipToHomeAnimator.getComponentName(),
mSwipePipToHomeAnimator.getDestinationBounds(),
- mSwipePipToHomeAnimator.getContentOverlay());
+ mSwipePipToHomeAnimator.getContentOverlay(),
+ mSwipePipToHomeAnimator.getAppBounds());
windowAnim = mSwipePipToHomeAnimators;
} else {
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index 7c24ba8..b9029ea 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -54,12 +54,14 @@
import com.android.internal.view.AppearanceRegion;
import com.android.launcher3.AbstractFloatingView;
+import com.android.launcher3.BubbleTextView;
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.taskbar.LauncherTaskbarUIController;
import com.android.launcher3.uioverrides.QuickstepLauncher;
+import com.android.launcher3.widget.LauncherAppWidgetHostView;
import com.android.quickstep.util.RectFSpringAnim;
import com.android.systemui.shared.system.QuickStepContract;
@@ -108,6 +110,7 @@
private final PointF mInitialTouchPos = new PointF();
private RemoteAnimationTarget mBackTarget;
+ private View mLauncherTargetView;
private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
private boolean mSpringAnimationInProgress = false;
private boolean mAnimatorSetInProgress = false;
@@ -287,7 +290,7 @@
mBackInProgress = true;
RemoteAnimationTarget appTarget = backEvent.getDepartingAnimationTarget();
- if (appTarget == null) {
+ if (appTarget == null || appTarget.leash == null || !appTarget.leash.isValid()) {
return;
}
@@ -303,11 +306,22 @@
int insetBottom = mStartRect.bottom - appTarget.contentInsets.bottom;
mStartRect.set(mStartRect.left, mStartRect.top, mStartRect.right, insetBottom);
}
+ mLauncherTargetView = mQuickstepTransitionManager.findLauncherView(
+ new RemoteAnimationTarget[]{ mBackTarget });
+ setLauncherTargetViewVisible(false);
mCurrentRect.set(mStartRect);
addScrimLayer();
mTransaction.apply();
}
+ private void setLauncherTargetViewVisible(boolean isVisible) {
+ if (mLauncherTargetView instanceof BubbleTextView) {
+ ((BubbleTextView) mLauncherTargetView).setIconVisible(isVisible);
+ } else if (mLauncherTargetView instanceof LauncherAppWidgetHostView) {
+ mLauncherTargetView.setAlpha(isVisible ? 1f : 0f);
+ }
+ }
+
void addScrimLayer() {
ViewRootImpl viewRootImpl = mLauncher.getDragLayer().getViewRootImpl();
SurfaceControl parent = viewRootImpl != null
@@ -436,6 +450,8 @@
mLauncher.getStateManager().moveToRestState();
}
+ setLauncherTargetViewVisible(true);
+
// Explicitly close opened floating views (which is typically called from
// Launcher#onResumed, but in the predictive back flow launcher is not resumed until
// the transition is fully finished.)
@@ -456,6 +472,7 @@
mBackInProgress /* fromPredictiveBack */);
startTransitionAnimations(pair.first, pair.second);
mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
+ customizeStatusBarAppearance(true);
}
private void finishAnimation() {
@@ -469,6 +486,8 @@
mInitialTouchPos.set(0, 0);
mAnimatorSetInProgress = false;
mSpringAnimationInProgress = false;
+ setLauncherTargetViewVisible(true);
+ mLauncherTargetView = null;
// We don't call customizeStatusBarAppearance here to prevent the status bar update with
// the legacy appearance. It should be refreshed after the transition done.
mOverridingStatusBarFlags = false;
diff --git a/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt b/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt
index fc450f0..0913fca 100644
--- a/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt
+++ b/quickstep/src/com/android/quickstep/LauncherRestoreEventLoggerImpl.kt
@@ -5,9 +5,10 @@
import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType
import android.app.backup.BackupRestoreEventLogger.BackupRestoreError
import android.content.Context
-import com.android.launcher3.Flags
+import com.android.launcher3.Flags.enableLauncherBrMetrics
import com.android.launcher3.LauncherSettings.Favorites
import com.android.launcher3.backuprestore.LauncherRestoreEventLogger
+import com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCHER_BR_METRICS
/**
* Concrete implementation for wrapper to log Restore event metrics for both success and failure to
@@ -44,7 +45,7 @@
count: Int,
@BackupRestoreError error: String?
) {
- if (Flags.enableLauncherBrMetrics()) {
+ if (enableLauncherBrMetrics() || ENABLE_LAUNCHER_BR_METRICS.get()) {
restoreEventLogger.logItemsRestoreFailed(dataType, count, error)
}
}
@@ -56,7 +57,7 @@
* @param count the number of data items restored.
*/
override fun logLauncherItemsRestored(@BackupRestoreDataType dataType: String, count: Int) {
- if (Flags.enableLauncherBrMetrics()) {
+ if (enableLauncherBrMetrics() || ENABLE_LAUNCHER_BR_METRICS.get()) {
restoreEventLogger.logItemsRestored(dataType, count)
}
}
@@ -67,7 +68,7 @@
* @param favoritesId The id of the item type from [Favorites] that was restored.
*/
override fun logSingleFavoritesItemRestored(favoritesId: Int) {
- if (Flags.enableLauncherBrMetrics()) {
+ if (enableLauncherBrMetrics() || ENABLE_LAUNCHER_BR_METRICS.get()) {
restoreEventLogger.logItemsRestored(favoritesIdToDataType(favoritesId), 1)
}
}
@@ -79,7 +80,7 @@
* @param count number of items that restored.
*/
override fun logFavoritesItemsRestored(favoritesId: Int, count: Int) {
- if (Flags.enableLauncherBrMetrics()) {
+ if (enableLauncherBrMetrics() || ENABLE_LAUNCHER_BR_METRICS.get()) {
restoreEventLogger.logItemsRestored(favoritesIdToDataType(favoritesId), count)
}
}
@@ -94,7 +95,7 @@
favoritesId: Int,
@BackupRestoreError error: String?
) {
- if (Flags.enableLauncherBrMetrics()) {
+ if (enableLauncherBrMetrics() || ENABLE_LAUNCHER_BR_METRICS.get()) {
restoreEventLogger.logItemsRestoreFailed(favoritesIdToDataType(favoritesId), 1, error)
}
}
@@ -111,7 +112,7 @@
count: Int,
@BackupRestoreError error: String?
) {
- if (Flags.enableLauncherBrMetrics()) {
+ if (enableLauncherBrMetrics() || ENABLE_LAUNCHER_BR_METRICS.get()) {
restoreEventLogger.logItemsRestoreFailed(
favoritesIdToDataType(favoritesId),
count,
@@ -125,7 +126,7 @@
* done restoring items for Launcher.
*/
override fun reportLauncherRestoreResults() {
- if (Flags.enableLauncherBrMetrics()) {
+ if (enableLauncherBrMetrics() || ENABLE_LAUNCHER_BR_METRICS.get()) {
BackupManager(context).reportDelayedRestoreResult(restoreEventLogger)
}
}
diff --git a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
index 0303791..65c8662 100644
--- a/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
+++ b/quickstep/src/com/android/quickstep/QuickstepTestInformationHandler.java
@@ -1,5 +1,6 @@
package com.android.quickstep;
+import static com.android.launcher3.taskbar.TaskbarThresholdUtils.getFromNavThreshold;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import android.app.Activity;
@@ -10,7 +11,6 @@
import androidx.annotation.Nullable;
-import com.android.launcher3.R;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.testing.TestInformationHandler;
import com.android.launcher3.testing.shared.TestProtocol;
@@ -18,6 +18,7 @@
import com.android.launcher3.util.DisplayController;
import com.android.quickstep.util.LayoutUtils;
import com.android.quickstep.util.TISBindHelper;
+import com.android.quickstep.views.RecentsView;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
@@ -77,6 +78,11 @@
return response;
}
+ case TestProtocol.REQUEST_GET_OVERVIEW_CURRENT_PAGE_INDEX: {
+ return getLauncherUIProperty(Bundle::putInt,
+ launcher -> launcher.<RecentsView>getOverviewPanel().getCurrentPage());
+ }
+
case TestProtocol.REQUEST_HAS_TIS: {
response.putBoolean(TestProtocol.TEST_INFO_RESPONSE_FIELD, true);
return response;
@@ -93,7 +99,7 @@
case TestProtocol.REQUEST_TASKBAR_FROM_NAV_THRESHOLD: {
final Resources resources = mContext.getResources();
response.putInt(TestProtocol.TEST_INFO_RESPONSE_FIELD,
- resources.getDimensionPixelSize(R.dimen.taskbar_from_nav_threshold));
+ getFromNavThreshold(resources, mDeviceProfile));
return response;
}
@@ -149,6 +155,19 @@
case TestProtocol.REQUEST_REFRESH_OVERVIEW_TARGET:
runOnTISBinder(TouchInteractionService.TISBinder::refreshOverviewTarget);
return response;
+
+ case TestProtocol.REQUEST_RECREATE_TASKBAR:
+ // Allow null-pointer to catch illegal states.
+ runOnTISBinder(tisBinder -> tisBinder.getTaskbarManager().recreateTaskbar());
+ return response;
+
+ case TestProtocol.REQUEST_UNSTASH_BUBBLE_BAR_IF_STASHED:
+ runOnTISBinder(tisBinder -> {
+ // Allow null-pointer to catch illegal states.
+ tisBinder.getTaskbarManager().getCurrentActivityContext()
+ .unstashBubbleBarIfStashed();
+ });
+ return response;
}
return super.call(method, arg, extras);
diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java
index 94ed5b9..a8c6809 100644
--- a/quickstep/src/com/android/quickstep/SystemUiProxy.java
+++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java
@@ -630,10 +630,11 @@
* should be responsible for cleaning up the overlay.
*/
public void stopSwipePipToHome(int taskId, ComponentName componentName, Rect destinationBounds,
- SurfaceControl overlay) {
+ SurfaceControl overlay, Rect appBounds) {
if (mPip != null) {
try {
- mPip.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay);
+ mPip.stopSwipePipToHome(taskId, componentName, destinationBounds, overlay,
+ appBounds);
} catch (RemoteException e) {
Log.w(TAG, "Failed call stopSwipePipToHome");
}
diff --git a/quickstep/src/com/android/quickstep/TaskAnimationManager.java b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
index 8e03a91..4f885af 100644
--- a/quickstep/src/com/android/quickstep/TaskAnimationManager.java
+++ b/quickstep/src/com/android/quickstep/TaskAnimationManager.java
@@ -38,6 +38,7 @@
import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
+import com.android.internal.util.ArrayUtils;
import com.android.launcher3.Utilities;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.DisplayController;
@@ -185,6 +186,16 @@
cleanUpRecentsAnimation(newCallbacks);
}
+ private boolean isNonRecentsStartedTasksAppeared(
+ RemoteAnimationTarget[] appearedTaskTargets) {
+ // For example, right after swiping from task X to task Y (e.g. from
+ // AbsSwipeUpHandler#startNewTask), and then task Y starts X immediately
+ // (e.g. in Y's onResume). The case will be: lastStartedTask=Y and appearedTask=X.
+ return mLastGestureState.getEndTarget() == GestureState.GestureEndTarget.NEW_TASK
+ && ArrayUtils.find(appearedTaskTargets,
+ mLastGestureState.mLastStartedTaskIdPredicate) == null;
+ }
+
@Override
public void onTasksAppeared(RemoteAnimationTarget[] appearedTaskTargets) {
RemoteAnimationTarget appearedTaskTarget = appearedTaskTargets[0];
@@ -206,13 +217,15 @@
}
}
- RemoteAnimationTarget[] nonAppTargets = SystemUiProxy.INSTANCE.get(mCtx)
- .onStartingSplitLegacy(appearedTaskTargets);
+ RemoteAnimationTarget[] nonAppTargets = ENABLE_SHELL_TRANSITIONS
+ ? null : SystemUiProxy.INSTANCE.get(mCtx).onStartingSplitLegacy(
+ appearedTaskTargets);
if (nonAppTargets == null) {
nonAppTargets = new RemoteAnimationTarget[0];
}
if ((activityInterface.isInLiveTileMode()
- || mLastGestureState.getEndTarget() == RECENTS)
+ || mLastGestureState.getEndTarget() == RECENTS
+ || isNonRecentsStartedTasksAppeared(appearedTaskTargets))
&& activityInterface.getCreatedActivity() != null) {
RecentsView recentsView =
activityInterface.getCreatedActivity().getOverviewPanel();
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 86ba7ef..6f45caf 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -305,6 +305,10 @@
@Override
public void onNavigationBarSurface(SurfaceControl surface) {
// TODO: implement
+ if (surface != null) {
+ surface.release();
+ surface = null;
+ }
}
@BinderThread
diff --git a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
index 35fa539..1008da3 100644
--- a/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
+++ b/quickstep/src/com/android/quickstep/fallback/FallbackRecentsView.java
@@ -217,7 +217,6 @@
} else {
if (mActivity.isInState(RecentsState.MODAL_TASK)) {
mActivity.getStateManager().goToState(DEFAULT, animate);
- resetModalVisuals();
}
}
}
@@ -237,6 +236,8 @@
setOverviewFullscreenEnabled(toState.isFullScreen());
if (toState == MODAL_TASK) {
setOverviewSelectEnabled(true);
+ } else {
+ resetModalVisuals();
}
// Set border after select mode changes to avoid showing border during state transition
diff --git a/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt b/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt
new file mode 100644
index 0000000..c1fa2f3
--- /dev/null
+++ b/quickstep/src/com/android/quickstep/util/SlideInRemoteTransition.kt
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.quickstep.util
+
+import android.animation.ValueAnimator
+import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
+import android.graphics.Rect
+import android.os.IBinder
+import android.os.RemoteException
+import android.view.SurfaceControl
+import android.view.SurfaceControl.Transaction
+import android.window.IRemoteTransition
+import android.window.IRemoteTransitionFinishedCallback
+import android.window.TransitionInfo
+import com.android.launcher3.anim.AnimatorListeners.forEndCallback
+import com.android.launcher3.util.Executors
+import com.android.wm.shell.util.TransitionUtil
+
+/** Remote animation which slides the opening targets in and the closing targets out */
+class SlideInRemoteTransition(val isRtl: Boolean) : IRemoteTransition.Stub() {
+
+ override fun mergeAnimation(
+ iBinder: IBinder,
+ transitionInfo: TransitionInfo,
+ transaction: Transaction,
+ mergeTarget: IBinder,
+ finishCB: IRemoteTransitionFinishedCallback
+ ) {
+
+ try {
+ finishCB.onTransitionFinished(null, Transaction())
+ } catch (e: RemoteException) {
+ // Ignore
+ }
+ }
+
+ override fun startAnimation(
+ transition: IBinder,
+ info: TransitionInfo,
+ startT: Transaction,
+ finishCB: IRemoteTransitionFinishedCallback
+ ) {
+ val anim = ValueAnimator.ofFloat(0f, 1f)
+
+ val closingStartBounds: HashMap<SurfaceControl, Rect> = HashMap()
+ val openingEndBounds: HashMap<SurfaceControl, Rect> = HashMap()
+ for (chg in info.changes) {
+ val leash = chg.leash
+ startT.show(leash)
+
+ val taskInfo = chg.taskInfo
+ if (taskInfo?.activityType == ACTIVITY_TYPE_HOME || taskInfo?.parentTaskId != -1) {
+ continue
+ }
+ if (TransitionUtil.isClosingType(chg.mode)) {
+ closingStartBounds[leash] = chg.startAbsBounds
+ }
+ if (TransitionUtil.isOpeningType(chg.mode)) {
+ openingEndBounds[leash] = chg.endAbsBounds
+ }
+ }
+ startT.apply()
+
+ anim.addUpdateListener {
+ val t = Transaction()
+ closingStartBounds.keys.forEach {
+ // Translate the surface from its original position on-screen to off-screen on the
+ // right (or left in RTL)
+ val startBounds = closingStartBounds[it]
+ val targetX = (if (isRtl) -1 else 1) * startBounds!!.right
+ t.setPosition(it, anim.animatedValue as Float * targetX, 0f)
+ }
+ openingEndBounds.keys.forEach {
+ // Set the alpha in the update listener to prevent one visible frame at the
+ // beginning
+ t.setAlpha(it, 1f)
+ // Translate the surface from off-screen on the left (or left in RTL) to its final
+ // position on-screen
+ val endBounds = openingEndBounds[it]
+ val targetX = (if (isRtl) -1 else 1) * endBounds!!.right
+ t.setPosition(it, (1f - anim.animatedValue as Float) * -targetX, 0f)
+ }
+ t.apply()
+ }
+ anim.addListener(
+ forEndCallback(
+ Runnable {
+ val t = Transaction()
+ try {
+ finishCB.onTransitionFinished(null, t)
+ } catch (e: RemoteException) {
+ // Ignore
+ }
+ }
+ )
+ )
+
+ Executors.MAIN_EXECUTOR.execute { anim.start() }
+ }
+
+ override fun onTransitionConsumed(transition: IBinder?, aborted: Boolean) {}
+}
diff --git a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
index a9fa337..3c90e0c 100644
--- a/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
+++ b/quickstep/src/com/android/quickstep/util/SplitSelectStateController.java
@@ -498,6 +498,29 @@
}
/**
+ * Used to launch split screen from a split pair that already exists, optionally with a custom
+ * remote transition.
+ * <p>
+ * See {@link SplitSelectStateController#launchExistingSplitPair(
+ * GroupedTaskView, int, int, int, Consumer, boolean, int, RemoteTransition)}
+ */
+ public void launchExistingSplitPair(@Nullable GroupedTaskView groupedTaskView,
+ int firstTaskId, int secondTaskId, @StagePosition int stagePosition,
+ Consumer<Boolean> callback, boolean freezeTaskList,
+ @PersistentSnapPosition int snapPosition) {
+ launchExistingSplitPair(
+ groupedTaskView,
+ firstTaskId,
+ secondTaskId,
+ stagePosition,
+ callback,
+ freezeTaskList,
+ snapPosition,
+ /* remoteTransition= */ null);
+ }
+
+
+ /**
* Used to launch split screen from a split pair that already exists (usually accessible through
* Overview). This is different than {@link #launchTasks(Consumer, boolean, int, InstanceId)}
* in that this only launches split screen that are existing tasks. This doesn't determine which
@@ -509,7 +532,7 @@
public void launchExistingSplitPair(@Nullable GroupedTaskView groupedTaskView,
int firstTaskId, int secondTaskId, @StagePosition int stagePosition,
Consumer<Boolean> callback, boolean freezeTaskList,
- @PersistentSnapPosition int snapPosition) {
+ @PersistentSnapPosition int snapPosition, @Nullable RemoteTransition remoteTransition) {
mLaunchingTaskView = groupedTaskView;
final ActivityOptions options1 = ActivityOptions.makeBasic();
if (freezeTaskList) {
@@ -518,10 +541,12 @@
Bundle optionsBundle = options1.toBundle();
if (TaskAnimationManager.ENABLE_SHELL_TRANSITIONS) {
- final RemoteTransition remoteTransition = getShellRemoteTransition(firstTaskId,
- secondTaskId, callback, "LaunchExistingPair");
+ final RemoteTransition transition = remoteTransition == null
+ ? getShellRemoteTransition(
+ firstTaskId, secondTaskId, callback, "LaunchExistingPair")
+ : remoteTransition;
mSystemUiProxy.startTasks(firstTaskId, optionsBundle, secondTaskId, null /* options2 */,
- stagePosition, snapPosition, remoteTransition, null /*shellInstanceId*/);
+ stagePosition, snapPosition, transition, null /*shellInstanceId*/);
} else {
final RemoteAnimationAdapter adapter = getLegacyRemoteAdapter(firstTaskId,
secondTaskId, callback);
@@ -667,8 +692,10 @@
MAIN_EXECUTOR.execute(() -> {
// Only animate from taskView if it's already visible
- boolean shouldLaunchFromTaskView = mLaunchingTaskView != null &&
- mLaunchingTaskView.getRecentsView().isTaskViewVisible(mLaunchingTaskView);
+ boolean shouldLaunchFromTaskView = mLaunchingTaskView != null
+ && mLaunchingTaskView.getRecentsView() != null
+ && mLaunchingTaskView.getRecentsView().isTaskViewVisible(
+ mLaunchingTaskView);
mSplitAnimationController.playSplitLaunchAnimation(
shouldLaunchFromTaskView ? mLaunchingTaskView : null,
mLaunchingIconView,
diff --git a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
index a36b32c..6c89be1 100644
--- a/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
+++ b/quickstep/src/com/android/quickstep/util/SwipePipToHomeAnimator.java
@@ -264,6 +264,10 @@
return mDestinationBounds;
}
+ public Rect getAppBounds() {
+ return mAppBounds;
+ }
+
@Nullable
public SurfaceControl getContentOverlay() {
return mPipContentOverlay == null ? null : mPipContentOverlay.getLeash();
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index a10d2ed..6b00473 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -17,6 +17,7 @@
package com.android.quickstep.views;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
import static com.android.launcher3.util.SplitConfigurationOptions.STAGE_POSITION_UNDEFINED;
import android.content.Context;
@@ -53,6 +54,8 @@
import com.android.systemui.shared.system.QuickStepContract;
import com.android.wm.shell.Flags;
+import kotlin.Unit;
+
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -60,7 +63,6 @@
import java.util.List;
import java.util.function.Consumer;
-import kotlin.Unit;
/**
* TaskView that contains all tasks that are part of the desktop.
@@ -91,6 +93,8 @@
private View mBackgroundView;
+ private int mChildCountAtInflation;
+
/** Check whether desktop windowing is enabled */
public static boolean isDesktopModeSupported() {
// Check for aconfig flag first
@@ -151,6 +155,8 @@
Drawable iconBackground = getResources().getDrawable(R.drawable.bg_circle,
getContext().getTheme());
mIconView.setDrawable(new LayerDrawable(new Drawable[]{iconBackground, icon}));
+
+ mChildCountAtInflation = getChildCount();
}
@Override
@@ -196,7 +202,9 @@
for (int i = 0; i < diff; i++) {
TaskThumbnailView snapshotView = new TaskThumbnailView(getContext());
mSnapshotViews.add(snapshotView);
- addView(snapshotView, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
+ // Add snapshots from to position after the initial child views.
+ addView(snapshotView, mChildCountAtInflation,
+ new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
}
}
diff --git a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
index 5b5d0de..9bb9775a 100644
--- a/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/LauncherRecentsView.java
@@ -16,6 +16,7 @@
package com.android.quickstep.views;
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.CLEAR_ALL_BUTTON;
import static com.android.launcher3.LauncherState.EDIT_MODE;
@@ -144,6 +145,8 @@
setOverviewFullscreenEnabled(toState.getOverviewFullscreenProgress() == 1);
if (toState == OVERVIEW_MODAL_TASK) {
setOverviewSelectEnabled(true);
+ } else {
+ resetModalVisuals();
}
// Set border after select mode changes to avoid showing border during state transition
@@ -214,7 +217,6 @@
} else {
if (mActivity.isInState(LauncherState.OVERVIEW_MODAL_TASK)) {
mActivity.getStateManager().goToState(LauncherState.OVERVIEW, animate);
- resetModalVisuals();
}
}
}
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index ca9d13e..997624f 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -3246,8 +3246,9 @@
anim.add(ObjectAnimator.ofFloat(taskView, secondaryViewTranslate,
verticalFactor * secondaryTaskDimension * 2).setDuration(duration), LINEAR, sp);
- if (mEnableDrawingLiveTile && taskView.isRunningTask()) {
+ if (taskView.isRunningTask()) {
anim.addOnFrameCallback(() -> {
+ if (!mEnableDrawingLiveTile) return;
runActionOnRemoteHandles(
remoteTargetHandle -> remoteTargetHandle.getTaskViewSimulator()
.taskSecondaryTranslation.value = mOrientationHandler
@@ -4381,11 +4382,14 @@
private void updatePivots() {
if (mOverviewSelectEnabled) {
- getModalTaskSize(mTempRect);
- Rect selectedTaskPosition = getSelectedTaskBounds();
-
- Utilities.getPivotsForScalingRectToRect(mTempRect, selectedTaskPosition,
- mTempPointF);
+ if (enableGridOnlyOverview()) {
+ getModalTaskSize(mTempRect);
+ Rect selectedTaskPosition = getSelectedTaskBounds();
+ Utilities.getPivotsForScalingRectToRect(mTempRect, selectedTaskPosition,
+ mTempPointF);
+ } else {
+ mTempPointF.set(mLastComputedTaskSize.centerX(), mLastComputedTaskSize.bottom);
+ }
} else {
mTempRect.set(mLastComputedTaskSize);
// Only update pivot when it is tablet and not in grid yet, so the pivot is correct
@@ -5708,11 +5712,20 @@
}
private void updateEnabledOverlays() {
+ TaskView focusedTaskView = getFocusedTaskView();
int taskCount = getTaskViewCount();
for (int i = 0; i < taskCount; i++) {
TaskView taskView = requireTaskViewAt(i);
+ if (taskView == focusedTaskView) {
+ continue;
+ }
taskView.setOverlayEnabled(mOverlayEnabled && isTaskViewFullyVisible(taskView));
}
+ // Focus task overlay should be enabled and refreshed at last
+ if (focusedTaskView != null) {
+ focusedTaskView.setOverlayEnabled(
+ mOverlayEnabled && isTaskViewFullyVisible(focusedTaskView));
+ }
}
public void setOverlayEnabled(boolean overlayEnabled) {
diff --git a/quickstep/src/com/android/quickstep/views/TaskMenuView.java b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
index cf89d2e..77033b2 100644
--- a/quickstep/src/com/android/quickstep/views/TaskMenuView.java
+++ b/quickstep/src/com/android/quickstep/views/TaskMenuView.java
@@ -115,9 +115,6 @@
animateClose();
} else {
closeComplete();
- if (enableOverviewIconMenu()) {
- ((IconAppChipView) mTaskContainer.getIconView()).reset();
- }
}
}
@@ -378,9 +375,18 @@
private void closeComplete() {
mIsOpen = false;
+ resetOverviewIconMenu();
mActivity.getDragLayer().removeView(this);
}
+ private void resetOverviewIconMenu() {
+ if (enableOverviewIconMenu()) {
+ ((IconAppChipView) mTaskContainer.getIconView()).reset();
+ setTranslationY(mMenuTranslationYBeforeOpen);
+ mTaskContainer.getIconView().asView().setTranslationY(mIconViewTranslationYBeforeOpen);
+ }
+ }
+
private RoundedRectRevealOutlineProvider createOpenCloseOutlineProvider() {
float radius = TaskCornerRadius.get(mContext);
Rect fromRect = new Rect(
diff --git a/quickstep/tests/src/com/android/launcher3/model/AppEventProducerTest.java b/quickstep/tests/src/com/android/launcher3/model/AppEventProducerTest.java
new file mode 100644
index 0000000..d6c1447
--- /dev/null
+++ b/quickstep/tests/src/com/android/launcher3/model/AppEventProducerTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2023 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.model;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+
+import static junit.framework.Assert.assertEquals;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import android.app.prediction.AppTarget;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Process;
+import android.os.UserHandle;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.launcher3.logger.LauncherAtom;
+import com.android.launcher3.model.data.AppInfo;
+import com.android.launcher3.pm.UserCache;
+import com.android.launcher3.util.ActivityContextWrapper;
+import com.android.launcher3.util.UserIconInfo;
+import com.android.launcher3.util.rule.StaticMockitoRule;
+import com.android.systemui.shared.system.SysUiStatsLog;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.Arrays;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class AppEventProducerTest {
+
+ private static final UserHandle MAIN_HANDLE = Process.myUserHandle();
+ private static final UserHandle PRIVATE_HANDLE = new UserHandle(11);
+
+ private static final UserIconInfo MAIN_ICON_INFO =
+ new UserIconInfo(MAIN_HANDLE, UserIconInfo.TYPE_MAIN);
+ private static final UserIconInfo PRIVATE_ICON_INFO =
+ new UserIconInfo(PRIVATE_HANDLE, UserIconInfo.TYPE_PRIVATE);
+
+ private Context mContext;
+ private AppEventProducer mAppEventProducer;
+ @Mock
+ private UserCache mUserCache;
+
+ @Rule
+ public final StaticMockitoRule mStaticMockitoRule = new StaticMockitoRule(UserCache.class);
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = new ActivityContextWrapper(getApplicationContext());
+ when(UserCache.getInstance(any(Context.class))).thenReturn(mUserCache);
+ mAppEventProducer = new AppEventProducer(mContext, null);
+ }
+
+ @Test
+ public void buildAppTarget_containsCorrectUser() {
+ when(mUserCache.getUserProfiles())
+ .thenReturn(Arrays.asList(MAIN_HANDLE, PRIVATE_HANDLE));
+ when(mUserCache.getUserInfo(any(UserHandle.class)))
+ .thenReturn(MAIN_ICON_INFO, PRIVATE_ICON_INFO);
+ ComponentName gmailComponentName = new ComponentName(mContext,
+ "com.android.launcher3.tests.Activity" + "Gmail");
+ AppInfo gmailAppInfo = new
+ AppInfo(gmailComponentName, "Gmail", MAIN_HANDLE, new Intent());
+ gmailAppInfo.container = CONTAINER_ALL_APPS;
+ gmailAppInfo.itemType = ITEM_TYPE_APPLICATION;
+
+ AppTarget gmailTarget = mAppEventProducer
+ .toAppTarget(buildItemInfoProtoForAppInfo(gmailAppInfo));
+
+ assert gmailTarget != null;
+ assertEquals(gmailTarget.getUser(), MAIN_HANDLE);
+
+ when(mUserCache.getUserInfo(any(UserHandle.class)))
+ .thenReturn(MAIN_ICON_INFO, PRIVATE_ICON_INFO);
+ AppInfo gmailAppInfoPrivate = new
+ AppInfo(gmailComponentName, "Gmail", PRIVATE_HANDLE, new Intent());
+ gmailAppInfoPrivate.container = CONTAINER_ALL_APPS;
+ gmailAppInfoPrivate.itemType = ITEM_TYPE_APPLICATION;
+
+ AppTarget gmailPrivateTarget = mAppEventProducer
+ .toAppTarget(buildItemInfoProtoForAppInfo(gmailAppInfoPrivate));
+
+ assert gmailPrivateTarget != null;
+ assertEquals(gmailPrivateTarget.getUser(), PRIVATE_HANDLE);
+ }
+
+ private LauncherAtom.ItemInfo buildItemInfoProtoForAppInfo(AppInfo appInfo) {
+ LauncherAtom.ItemInfo.Builder itemBuilder = LauncherAtom.ItemInfo.newBuilder();
+ if (appInfo.user.equals(PRIVATE_HANDLE)) {
+ itemBuilder.setUserType(SysUiStatsLog.LAUNCHER_UICHANGED__USER_TYPE__TYPE_PRIVATE);
+ } else {
+ itemBuilder.setUserType(SysUiStatsLog.LAUNCHER_UICHANGED__USER_TYPE__TYPE_MAIN);
+ }
+ itemBuilder.setApplication(LauncherAtom.Application.newBuilder()
+ .setComponentName(appInfo.componentName.flattenToShortString())
+ .setPackageName(appInfo.componentName.getPackageName()));
+ itemBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder()
+ .setAllAppsContainer(LauncherAtom.AllAppsContainer.getDefaultInstance())
+ .build());
+ return itemBuilder.build();
+ }
+}
diff --git a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
index edf95ea..39d6f03 100644
--- a/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
+++ b/quickstep/tests/src/com/android/quickstep/FallbackRecentsTest.java
@@ -32,6 +32,8 @@
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.rule.ShellCommandRule.disableHeadsUpNotification;
import static com.android.launcher3.util.rule.ShellCommandRule.getLauncherCommand;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@@ -67,7 +69,6 @@
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.RuleChain;
@@ -76,6 +77,7 @@
import org.junit.runners.model.Statement;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
@@ -185,12 +187,14 @@
mLauncher.getLaunchedAppState().switchToOverview();
}
- // b/143488140
+ // Staging; will be promoted to presubmit if stable
+ @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
+
//@NavigationModeSwitch
- @Ignore
@Test
public void goToOverviewFromApp() {
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
+ waitForRecentsActivityStop();
mLauncher.getLaunchedAppState().switchToOverview();
}
@@ -227,6 +231,19 @@
}
private void waitForRecentsActivityStop() {
+ try {
+ final boolean recentsActivityIsNull = MAIN_EXECUTOR.submit(
+ () -> RecentsActivity.ACTIVITY_TRACKER.getCreatedActivity() == null).get();
+ if (recentsActivityIsNull) {
+ // Null activity counts as a "stopped" one.
+ return;
+ }
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+
Wait.atMost("Recents activity didn't stop",
() -> getFromRecents(recents -> !recents.isStarted()),
DEFAULT_UI_TIMEOUT, mLauncher);
@@ -239,6 +256,7 @@
startAppFast(getAppPackageName());
startAppFast(resolveSystemApp(Intent.CATEGORY_APP_CALCULATOR));
startTestActivity(2);
+ waitForRecentsActivityStop();
Wait.atMost("Expected three apps in the task list",
() -> mLauncher.getRecentTasks().size() >= 3, DEFAULT_ACTIVITY_TIMEOUT, mLauncher);
@@ -280,7 +298,6 @@
// Test dismissing all tasks.
pressHomeAndGoToOverview().dismissAllTasks();
- waitForRecentsActivityStop(); // dismissAllTasks() will close Recents
assertTrue("Fallback Launcher not visible", TestHelpers.wait(Until.hasObject(By.pkg(
mOtherLauncherActivity.packageName).text(FALLBACK_LAUNCHER_TITLE)), WAIT_TIME_MS));
}
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
index 25adb62..1b8866a 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsQuickstep.java
@@ -28,7 +28,6 @@
import android.content.Intent;
import android.content.res.Configuration;
-import android.platform.test.annotations.PlatinumTest;
import androidx.test.filters.LargeTest;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -44,6 +43,7 @@
import com.android.launcher3.tapl.Overview;
import com.android.launcher3.tapl.OverviewActions;
import com.android.launcher3.tapl.OverviewTask;
+import com.android.launcher3.tapl.SelectModeButtons;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.util.Wait;
@@ -112,7 +112,6 @@
@Test
@PortraitLandscape
- @PlatinumTest(focusArea = "launcher")
public void testOverview() throws Exception {
startTestAppsWithCheck();
// mLauncher.pressHome() also tests an important case of pressing home while in background.
@@ -194,6 +193,43 @@
actionsView.clickAndDismissScreenshot();
}
+ @Test
+ public void testDismissOverviewWithEscKey() throws Exception {
+ startTestAppsWithCheck();
+ final Overview overview = mLauncher.goHome().switchToOverview();
+ assertTrue("Launcher internal state is not Overview",
+ isInState(() -> LauncherState.OVERVIEW));
+
+ overview.dismissByEscKey();
+ assertTrue("Launcher internal state is not Home",
+ isInState(() -> LauncherState.NORMAL));
+ }
+
+ @Test
+ public void testDismissModalTaskAndOverviewWithEscKey() throws Exception {
+ startTestAppsWithCheck();
+ final Overview overview = mLauncher.goHome().switchToOverview();
+
+ final SelectModeButtons selectModeButtons;
+
+ if (mLauncher.isTablet() && mLauncher.isGridOnlyOverviewEnabled()) {
+ selectModeButtons = overview.getCurrentTask().tapMenu().tapSelectMenuItem();
+ } else {
+ selectModeButtons = overview.getOverviewActions().clickSelect();
+ }
+
+ assertTrue("Launcher internal state is not Overview Modal Task",
+ isInState(() -> LauncherState.OVERVIEW_MODAL_TASK));
+
+ selectModeButtons.dismissByEscKey();
+
+ assertTrue("Launcher internal state is not Overview",
+ isInState(() -> LauncherState.OVERVIEW));
+ overview.dismissByEscKey();
+ assertTrue("Launcher internal state is not Home",
+ isInState(() -> LauncherState.NORMAL));
+ }
+
private int getCurrentOverviewPage(Launcher launcher) {
return launcher.<RecentsView>getOverviewPanel().getCurrentPage();
}
@@ -210,11 +246,12 @@
return launcher.<RecentsView>getOverviewPanel().getBottomRowTaskCountForTablet();
}
- @Ignore
+ // Staging; will be promoted to presubmit if stable
+ @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
+
@Test
@NavigationModeSwitch
@PortraitLandscape
- @ScreenRecord // b/238461765
public void testSwitchToOverview() throws Exception {
startTestAppsWithCheck();
assertNotNull("Workspace.switchToOverview() returned null",
@@ -236,7 +273,9 @@
}
}
- @Ignore
+ // Staging; will be promoted to presubmit if stable
+ @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
+
@Test
@NavigationModeSwitch
@PortraitLandscape
@@ -295,7 +334,9 @@
@Test
@TaskbarModeSwitch
- @Ignore // b/314873201
+ @ScreenRecord // b/314873201
+ // Staging; will be promoted to presubmit if stable
+ @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
public void testQuickSwitchToPreviousAppForTablet() throws Exception {
assumeTrue(mLauncher.isTablet());
startTestActivity(2);
@@ -369,7 +410,6 @@
@Test
@PortraitLandscape
@TaskbarModeSwitch()
- @PlatinumTest(focusArea = "launcher")
@TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT) // b/309820115
@ScreenRecord // b/309820115
public void testOverviewForTablet() throws Exception {
diff --git a/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java b/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java
index 7109bbf..38d6046 100644
--- a/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java
+++ b/quickstep/tests/src/com/android/quickstep/TaplTestsTransientTaskbar.java
@@ -25,6 +25,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
+import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
import org.junit.Test;
@@ -69,6 +70,7 @@
@Test
@TaskbarModeSwitch(mode = TRANSIENT)
@PortraitLandscape
+ @ScreenRecord // b/317798731
public void testSwipeToStashAndUnstash() {
getTaskbar().swipeDownToStash();
mLauncher.getLaunchedAppState().swipeUpToUnstashTaskbar();
diff --git a/res/drawable-v31/bg_deferred_app_widget.xml b/res/drawable-v31/bg_deferred_app_widget.xml
deleted file mode 100644
index a08998d..0000000
--- a/res/drawable-v31/bg_deferred_app_widget.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2021 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.
--->
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:inset="8dp">
- <shape android:shape="rectangle">
- <corners android:radius="@android:dimen/system_app_widget_background_radius" />
- <solid android:color="#77000000" />
- </shape>
-</inset>
diff --git a/res/drawable/bg_deferred_app_widget.xml b/res/drawable/bg_deferred_app_widget.xml
deleted file mode 100644
index 07bae48..0000000
--- a/res/drawable/bg_deferred_app_widget.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-**
-** Copyright 2015, 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.
-*/
--->
-
-<inset
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:inset="8dp">
- <color android:color="#77000000" />
-</inset>
diff --git a/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml b/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml
index c502178..379e0e5 100644
--- a/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml
+++ b/res/drawable/bg_rounded_corner_bottom_sheet_handle.xml
@@ -17,6 +17,6 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:shape="rectangle" >
- <solid android:color="?androidprv:attr/colorSurfaceVariant"/>
+ <solid android:color="?androidprv:attr/materialColorOutlineVariant"/>
<corners android:radius="@dimen/bottom_sheet_handle_corner_radius" />
</shape>
diff --git a/res/layout/work_apps_edu.xml b/res/layout/work_apps_edu.xml
index eeb7f4f..f557fb6 100644
--- a/res/layout/work_apps_edu.xml
+++ b/res/layout/work_apps_edu.xml
@@ -19,7 +19,7 @@
android:paddingTop="@dimen/work_edu_card_margin"
android:paddingBottom="@dimen/work_edu_card_bottom_margin"
android:gravity="center">
- <RelativeLayout
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
@@ -28,40 +28,33 @@
android:paddingEnd="@dimen/work_card_margin"
android:paddingStart="@dimen/work_card_margin"
android:paddingTop="@dimen/work_card_margin"
+ android:paddingBottom="@dimen/work_card_margin"
android:id="@+id/wrapper">
<TextView
style="@style/PrimaryHeadline"
android:textColor="?android:attr/textColorPrimary"
android:id="@+id/work_apps_paused_title"
- android:layout_width="wrap_content"
+ android:layout_width="0dp"
android:layout_height="wrap_content"
- android:layout_marginBottom="@dimen/work_card_margin"
- android:layout_marginEnd="@dimen/work_card_margin"
+ android:layout_weight="1"
+ android:paddingEnd="@dimen/work_edu_card_text_end_margin"
android:text="@string/work_profile_edu_work_apps"
android:textDirection="locale"
android:textSize="18sp" />
- <RelativeLayout
- android:layout_width="match_parent"
- android:layout_height="@dimen/padded_rounded_button_height"
- android:orientation="horizontal">
- <FrameLayout
- android:layout_width="@dimen/rounded_button_width"
- android:layout_height="@dimen/rounded_button_width"
- android:layout_alignParentEnd="true"
- android:background="@drawable/rounded_action_button"
- android:padding="@dimen/rounded_button_padding">
- <ImageButton
- android:id="@+id/action_btn"
- android:layout_width="@dimen/x_icon_size"
- android:layout_height="@dimen/x_icon_size"
- android:layout_gravity="center"
- android:padding="@dimen/x_icon_padding"
- android:contentDescription="@string/accessibility_close"
- android:src="@drawable/ic_remove_no_shadow" />
- </FrameLayout>
- </RelativeLayout>
- </RelativeLayout>
-
-
-
-</com.android.launcher3.allapps.WorkEduCard>
\ No newline at end of file
+ <FrameLayout
+ android:layout_width="@dimen/rounded_button_width"
+ android:layout_height="@dimen/rounded_button_width"
+ android:background="@drawable/rounded_action_button"
+ android:padding="@dimen/rounded_button_padding">
+ <ImageButton
+ android:id="@+id/action_btn"
+ android:layout_width="@dimen/x_icon_size"
+ android:layout_height="@dimen/x_icon_size"
+ android:layout_gravity="center"
+ android:contentDescription="@string/accessibility_close"
+ android:padding="@dimen/x_icon_padding"
+ android:background="@android:color/transparent"
+ android:src="@drawable/ic_remove_no_shadow" />
+ </FrameLayout>
+ </LinearLayout>
+</com.android.launcher3.allapps.WorkEduCard>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index 8d84c90..77af671 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -189,6 +189,10 @@
defaults to 2 * numAllAppsColumns -->
<attr name="numExtendedAllAppsColumns" format="integer" />
+ <!-- Number of rows to calculate the cell height for all apps when it's necessary.
+ Defaults to numRows. Requires FeatureFlags.ENABLE_RESPONSIVE_WORKSPACE to be enabled. -->
+ <attr name="numAllAppsRowsForCellHeightCalculation" format="integer" />
+
<!-- numHotseatIcons defaults to numColumns, if not specified -->
<attr name="numHotseatIcons" format="integer" />
<!-- Number of icons to use when extending the hotseat size,
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 62ce256..603e697 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -103,6 +103,7 @@
<dimen name="all_apps_search_bar_content_overlap">24dp</dimen>
<dimen name="all_apps_search_bar_bottom_padding">30dp</dimen>
<dimen name="all_apps_empty_search_message_top_offset">40dp</dimen>
+ <dimen name="all_apps_search_top_row_extra_height">4dp</dimen>
<dimen name="all_apps_header_pill_height">48dp</dimen>
<dimen name="all_apps_header_pill_corner_radius">12dp</dimen>
<dimen name="all_apps_header_tab_height">48dp</dimen>
@@ -155,6 +156,7 @@
<dimen name="work_edu_card_margin">16dp</dimen>
<dimen name="work_edu_card_radius">16dp</dimen>
<dimen name="work_edu_card_bottom_margin">26dp</dimen>
+ <dimen name="work_edu_card_text_end_margin">32dp</dimen>
<dimen name="work_apps_paused_button_stroke">1dp</dimen>
<dimen name="work_card_margin">24dp</dimen>
@@ -368,6 +370,11 @@
<!-- Taskbar related (placeholders to compile in Launcher3 without Quickstep) -->
<dimen name="taskbar_size">0dp</dimen>
<dimen name="taskbar_phone_size">@*android:dimen/navigation_bar_frame_height</dimen>
+ <dimen name="taskbar_phone_home_button_size">80dp</dimen>
+ <dimen name="taskbar_phone_content_padding">8dp</dimen>
+ <dimen name="taskbar_phone_rounded_corner_content_margin">
+ @*android:dimen/rounded_corner_content_padding
+ </dimen>
<dimen name="taskbar_stashed_size">0dp</dimen>
<dimen name="qsb_widget_height">0dp</dimen>
<dimen name="qsb_shadow_height">0dp</dimen>
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 91da7e6..a57eaa3 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -923,6 +923,8 @@
if (mIcon instanceof PreloadIconDrawable) {
preloadIconDrawable = (PreloadIconDrawable) mIcon;
preloadIconDrawable.setLevel(progressLevel);
+ // TODO(b/302115555): For archived apps, show icon as disabled if active session
+ // exists.
preloadIconDrawable.setIsDisabled(info.getProgressLevel() == 0);
} else {
preloadIconDrawable = makePreloadIcon();
@@ -948,6 +950,7 @@
final PreloadIconDrawable preloadDrawable = newPendingIcon(getContext(), info);
preloadDrawable.setLevel(progressLevel);
+ // TODO(b/302115555): For archived apps, show icon as disabled if active session exists.
preloadDrawable.setIsDisabled(info.getProgressLevel() == 0);
return preloadDrawable;
}
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 53297f2..834ba04 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -78,7 +78,6 @@
public class DeviceProfile {
private static final int DEFAULT_DOT_SIZE = 100;
- private static final float ALL_APPS_TABLET_MAX_ROWS = 5.5f;
private static final float MIN_FOLDER_TEXT_SIZE_SP = 16f;
private static final float MIN_WIDGET_PADDING_DP = 6f;
@@ -641,8 +640,8 @@
DimensionType.WIDTH, numShownAllAppsColumns, availableWidthPx,
mResponsiveWorkspaceWidthSpec);
mResponsiveAllAppsHeightSpec = allAppsSpecs.getCalculatedSpec(responsiveAspectRatio,
- DimensionType.HEIGHT, inv.numRows, heightPx - mInsets.top,
- mResponsiveWorkspaceHeightSpec);
+ DimensionType.HEIGHT, inv.numAllAppsRowsForCellHeightCalculation,
+ heightPx - mInsets.top, mResponsiveWorkspaceHeightSpec);
ResponsiveSpecsProvider folderSpecs = ResponsiveSpecsProvider.create(
new ResourceHelper(context,
@@ -734,14 +733,9 @@
hotseatBorderSpace = cellLayoutBorderSpacePx.y;
}
- // AllApps height calculation depends on updated cellSize
if (isTablet) {
- int collapseHandleHeight =
- res.getDimensionPixelOffset(R.dimen.bottom_sheet_handle_area_height);
- int contentHeight = heightPx - collapseHandleHeight - hotseatQsbHeight;
- int targetContentHeight = (int) (allAppsCellHeightPx * ALL_APPS_TABLET_MAX_ROWS);
- allAppsPadding.top = Math.max(mInsets.top, contentHeight - targetContentHeight);
- allAppsShiftRange = heightPx - allAppsPadding.top;
+ allAppsPadding.top = mInsets.top;
+ allAppsShiftRange = heightPx;
} else {
allAppsPadding.top = 0;
allAppsShiftRange =
@@ -994,16 +988,6 @@
float workspaceCellPaddingY = getCellSize().y - iconSizePx - iconDrawablePaddingPx
- iconTextHeight;
- if (mIsResponsiveGrid) {
- iconTextSizePx = 0;
- iconDrawablePaddingPx = 0;
- int iconSizeWithOverlap = getIconSizeWithOverlap(iconSizePx);
- cellYPaddingPx = Math.max(0, getCellSize().y - iconSizeWithOverlap) / 2;
- autoResizeAllAppsCells();
-
- return;
- }
-
// We want enough space so that the text is closer to its corresponding icon.
if (workspaceCellPaddingY < iconTextHeight) {
iconTextSizePx = 0;
@@ -1121,25 +1105,28 @@
iconSizePx = mIconSizeSteps.getIconSmallerThan(cellWidthPx);
}
- iconDrawablePaddingPx = getNormalizedIconDrawablePadding();
+ if (isVerticalLayout) {
+ iconDrawablePaddingPx = 0;
+ iconTextSizePx = 0;
+ } else {
+ iconDrawablePaddingPx = getNormalizedIconDrawablePadding();
+ }
CellContentDimensions cellContentDimensions = new CellContentDimensions(iconSizePx,
iconDrawablePaddingPx,
iconTextSizePx);
- if (isVerticalLayout) {
- if (cellHeightPx < iconSizePx) {
- cellContentDimensions.setIconSizePx(
- mIconSizeSteps.getIconSmallerThan(cellHeightPx));
- }
- } else {
- cellContentDimensions.resizeToFitCellHeight(cellHeightPx, mIconSizeSteps);
- }
+ int cellContentHeight = cellContentDimensions.resizeToFitCellHeight(cellHeightPx,
+ mIconSizeSteps);
iconSizePx = cellContentDimensions.getIconSizePx();
iconDrawablePaddingPx = cellContentDimensions.getIconDrawablePaddingPx();
iconTextSizePx = cellContentDimensions.getIconTextSizePx();
- int cellContentHeight = cellContentDimensions.getCellContentHeight();
- cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2;
+ if (isVerticalLayout) {
+ cellYPaddingPx = Math.max(0, getCellSize().y - getIconSizeWithOverlap(iconSizePx))
+ / 2;
+ } else {
+ cellYPaddingPx = Math.max(0, cellHeightPx - cellContentHeight) / 2;
+ }
} else if (mIsScalableGrid) {
iconDrawablePaddingPx = (int) (getNormalizedIconDrawablePadding() * iconScale);
cellWidthPx = pxFromDp(inv.minCellSize[mTypeIndex].x, mMetrics, scale);
@@ -1223,7 +1210,7 @@
updateAllAppsIconSize(scale, res);
}
updateAllAppsContainerWidth();
- if (isVerticalBarLayout()) {
+ if (isVerticalLayout && !mIsResponsiveGrid) {
hideWorkspaceLabelsIfNotEnoughSpace();
}
if (FeatureFlags.enableTwolineAllapps()) {
@@ -1347,7 +1334,7 @@
if (allAppsCellHeightPx < cellContentDimensions.getCellContentHeight()) {
if (isVerticalBarLayout()) {
- if (allAppsCellHeightPx < iconSizePx) {
+ if (allAppsCellHeightPx < allAppsIconSizePx) {
cellContentDimensions.setIconSizePx(
mIconSizeSteps.getIconSmallerThan(allAppsCellHeightPx));
}
@@ -1361,6 +1348,10 @@
}
allAppsCellHeightPx += mResponsiveAllAppsHeightSpec.getGutterPx();
+
+ if (isVerticalBarLayout()) {
+ autoResizeAllAppsCells();
+ }
}
/**
diff --git a/src/com/android/launcher3/InvariantDeviceProfile.java b/src/com/android/launcher3/InvariantDeviceProfile.java
index 5721ed3..78c12e5 100644
--- a/src/com/android/launcher3/InvariantDeviceProfile.java
+++ b/src/com/android/launcher3/InvariantDeviceProfile.java
@@ -170,6 +170,7 @@
* Number of columns in the all apps list.
*/
public int numAllAppsColumns;
+ public int numAllAppsRowsForCellHeightCalculation;
public int numDatabaseAllAppsColumns;
public @StyleRes int allAppsStyle;
@@ -247,7 +248,7 @@
public InvariantDeviceProfile(Context context, String gridName) {
String newName = initGrid(context, gridName);
if (newName == null || !newName.equals(gridName)) {
- throw new IllegalArgumentException("Unknown grid name");
+ throw new IllegalArgumentException("Unknown grid name: " + gridName);
}
}
@@ -393,6 +394,8 @@
workspaceCellSpecsTwoPanelId = closestProfile.mWorkspaceCellSpecsTwoPanelId;
allAppsCellSpecsId = closestProfile.mAllAppsCellSpecsId;
allAppsCellSpecsTwoPanelId = closestProfile.mAllAppsCellSpecsTwoPanelId;
+ numAllAppsRowsForCellHeightCalculation =
+ closestProfile.mNumAllAppsRowsForCellHeightCalculation;
this.deviceType = deviceType;
inlineNavButtonsEndSpacing = closestProfile.inlineNavButtonsEndSpacing;
@@ -423,6 +426,7 @@
allAppsStyle = closestProfile.allAppsStyle;
numAllAppsColumns = closestProfile.numAllAppsColumns;
+
numDatabaseAllAppsColumns = deviceType == TYPE_MULTI_DISPLAY
? closestProfile.numDatabaseAllAppsColumns : closestProfile.numAllAppsColumns;
@@ -821,6 +825,7 @@
private final @StyleRes int allAppsStyle;
private final int numAllAppsColumns;
+ private final int mNumAllAppsRowsForCellHeightCalculation;
private final int numDatabaseAllAppsColumns;
private final int numHotseatIcons;
private final int numDatabaseHotseatIcons;
@@ -971,6 +976,9 @@
mAllAppsCellSpecsTwoPanelId = a.getResourceId(
R.styleable.GridDisplayOption_allAppsCellSpecsTwoPanelId,
INVALID_RESOURCE_HANDLE);
+ mNumAllAppsRowsForCellHeightCalculation = a.getInt(
+ R.styleable.GridDisplayOption_numAllAppsRowsForCellHeightCalculation,
+ numRows);
} else {
mWorkspaceSpecsId = INVALID_RESOURCE_HANDLE;
mWorkspaceSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
@@ -984,6 +992,7 @@
mWorkspaceCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
mAllAppsCellSpecsId = INVALID_RESOURCE_HANDLE;
mAllAppsCellSpecsTwoPanelId = INVALID_RESOURCE_HANDLE;
+ mNumAllAppsRowsForCellHeightCalculation = numRows;
}
int inlineForRotation = a.getInt(R.styleable.GridDisplayOption_inlineQsb,
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 11dc6e2..e0e35a4 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -1715,11 +1715,7 @@
mModel.removeCallbacks(this);
mRotationHelper.destroy();
- try {
- mAppWidgetHolder.stopListening();
- } catch (NullPointerException ex) {
- Log.w(TAG, "problem while stopping AppWidgetHost during Launcher destruction", ex);
- }
+ mAppWidgetHolder.stopListening();
mAppWidgetHolder.destroy();
TextKeyListener.getInstance().release();
@@ -2478,7 +2474,7 @@
*/
private LauncherAppWidgetInfo completeRestoreAppWidget(int appWidgetId, int finalRestoreFlag) {
LauncherAppWidgetHostView view = mWorkspace.getWidgetForAppWidgetId(appWidgetId);
- if ((view == null) || !(view instanceof PendingAppWidgetHostView)) {
+ if (!(view instanceof PendingAppWidgetHostView)) {
Log.e(TAG, "Widget update called, when the widget no longer exists.");
return null;
}
@@ -2489,8 +2485,9 @@
info.pendingItemInfo = null;
}
- if (((PendingAppWidgetHostView) view).isReinflateIfNeeded()) {
- view.reInflate();
+ PendingAppWidgetHostView pv = (PendingAppWidgetHostView) view;
+ if (pv.isReinflateIfNeeded()) {
+ pv.reInflate();
}
getModelWriter().updateItemInDatabase(info);
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 9a19526..5ae2d71 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -36,11 +36,7 @@
import android.content.pm.LauncherApps;
import android.os.UserHandle;
import android.util.Log;
-import android.util.SparseArray;
-import android.widget.RemoteViews;
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.graphics.IconShape;
@@ -77,12 +73,6 @@
private final InvariantDeviceProfile mInvariantDeviceProfile;
private final RunnableList mOnTerminateCallback = new RunnableList();
- // WORKAROUND: b/269335387 remove this after widget background listener is enabled
- /* Array of RemoteViews cached by Launcher process */
- @GuardedBy("itself")
- @NonNull
- public final SparseArray<RemoteViews> mCachedRemoteViews = new SparseArray<>();
-
public static LauncherAppState getInstance(final Context context) {
return INSTANCE.get(context);
}
diff --git a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
index 7f1d216..4ad4c71 100644
--- a/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/ActivityAllAppsContainerView.java
@@ -1150,14 +1150,15 @@
applyAdapterSideAndBottomPaddings(grid);
- MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
- mlp.leftMargin = insets.left;
- mlp.rightMargin = insets.right;
- setLayoutParams(mlp);
+ // Ignore left/right insets on tablet because we are already centered in-screen.
+ if (grid.isPhone) {
+ MarginLayoutParams mlp = (MarginLayoutParams) getLayoutParams();
+ mlp.leftMargin = insets.left;
+ mlp.rightMargin = insets.right;
+ setLayoutParams(mlp);
+ }
- if (grid.isVerticalBarLayout() && !FeatureFlags.enableResponsiveWorkspace()) {
- setPadding(grid.workspacePadding.left, 0, grid.workspacePadding.right, 0);
- } else {
+ if (!grid.isVerticalBarLayout() || FeatureFlags.enableResponsiveWorkspace()) {
int topPadding = grid.allAppsPadding.top;
if (isSearchBarFloating() && !grid.isTablet) {
topPadding += getResources().getDimensionPixelSize(
diff --git a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
index 1782791..3e55f61 100644
--- a/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
+++ b/src/com/android/launcher3/allapps/AlphabeticalAppsList.java
@@ -22,6 +22,7 @@
import android.content.Context;
import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
import androidx.recyclerview.widget.DiffUtil;
import com.android.launcher3.Flags;
@@ -338,26 +339,14 @@
hasPrivateApps = appList.stream().
allMatch(mPrivateProviderManager.getItemInfoMatcher());
}
- int privateAppCount = 0;
- int numberOfColumns = mActivityContext.getDeviceProfile().numShownAllAppsColumns;
- int numberOfAppRows = (int) Math.ceil((double) appList.size() / numberOfColumns);
- for (AppInfo info : appList) {
+ for (int i = 0; i < appList.size(); i++) {
+ AppInfo info = appList.get(i);
// Apply decorator to private apps.
if (hasPrivateApps) {
- int roundRegion = ROUND_NOTHING;
- if ((privateAppCount / numberOfColumns) == numberOfAppRows - 1) {
- if ((privateAppCount % numberOfColumns) == 0) {
- // App is the first column
- roundRegion = ROUND_BOTTOM_LEFT;
- } else if ((privateAppCount % numberOfColumns) == numberOfColumns-1) {
- roundRegion = ROUND_BOTTOM_RIGHT;
- }
- }
mAdapterItems.add(AdapterItem.asAppWithDecorationInfo(info,
new SectionDecorationInfo(mActivityContext.getApplicationContext(),
- roundRegion,
+ getRoundRegions(i, appList.size()),
true /* decorateTogether */)));
- privateAppCount += 1;
} else {
mAdapterItems.add(AdapterItem.asApp(info));
}
@@ -372,6 +361,43 @@
}
}
+ /**
+ * Determines the corner regions that should be rounded for a specific app icon based on its
+ * position in a grid. Apps that should only be cared about rounding are the apps in the last
+ * row. In the last row on the first column, the app should only be rounded on the bottom left.
+ * Apps in the middle would not be rounded and the last app on the last row will ALWAYS have a
+ * {@link SectionDecorationInfo#ROUND_BOTTOM_RIGHT}.
+ *
+ * @param appIndex The index of the app icon within the app list.
+ * @param appListSize The total number of apps within the app list.
+ * @return An integer representing the corner regions to be rounded, using bitwise flags:
+ * - {@link SectionDecorationInfo#ROUND_NOTHING}: No corners should be rounded.
+ * - {@link SectionDecorationInfo#ROUND_TOP_LEFT}: Round the top-left corner.
+ * - {@link SectionDecorationInfo#ROUND_TOP_RIGHT}: Round the top-right corner.
+ * - {@link SectionDecorationInfo#ROUND_BOTTOM_LEFT}: Round the bottom-left corner.
+ * - {@link SectionDecorationInfo#ROUND_BOTTOM_RIGHT}: Round the bottom-right corner.
+ */
+ @VisibleForTesting
+ int getRoundRegions(int appIndex, int appListSize) {
+ int numberOfAppRows = (int) Math.ceil((double) appListSize / mNumAppsPerRowAllApps);
+ int roundRegion = ROUND_NOTHING;
+ // App is in the last row.
+ if ((appIndex / mNumAppsPerRowAllApps) == numberOfAppRows - 1) {
+ if ((appIndex % mNumAppsPerRowAllApps) == 0) {
+ // App is the first column.
+ roundRegion = ROUND_BOTTOM_LEFT;
+ } else if ((appIndex % mNumAppsPerRowAllApps) == mNumAppsPerRowAllApps-1) {
+ // App is in the last column.
+ roundRegion = ROUND_BOTTOM_RIGHT;
+ }
+ // Ensure the last private app is rounded on the bottom right.
+ if (appIndex == appListSize - 1) {
+ roundRegion |= ROUND_BOTTOM_RIGHT;
+ }
+ }
+ return roundRegion;
+ }
+
private static class MyDiffCallback extends DiffUtil.Callback {
private final List<AdapterItem> mOldList;
diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java
index 1ba5f8e..a1f6ebe 100644
--- a/src/com/android/launcher3/allapps/FloatingHeaderView.java
+++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java
@@ -225,10 +225,10 @@
for (FloatingHeaderRow row : mAllRows) {
row.setup(this, mAllRows, tabsHidden);
}
- updateExpectedHeight();
mTabsHidden = tabsHidden;
maybeSetTabVisibility(VISIBLE);
+ updateExpectedHeight();
mMainRV = mainRV;
mWorkRV = workRV;
mSearchRV = searchRV;
diff --git a/src/com/android/launcher3/allapps/SectionDecorationInfo.java b/src/com/android/launcher3/allapps/SectionDecorationInfo.java
index 1fed2b6..c438d19 100644
--- a/src/com/android/launcher3/allapps/SectionDecorationInfo.java
+++ b/src/com/android/launcher3/allapps/SectionDecorationInfo.java
@@ -22,11 +22,11 @@
public class SectionDecorationInfo {
- public static final int ROUND_NOTHING = 1 << 1;
- public static final int ROUND_TOP_LEFT = 1 << 2;
- public static final int ROUND_TOP_RIGHT = 1 << 3;
- public static final int ROUND_BOTTOM_LEFT = 1 << 4;
- public static final int ROUND_BOTTOM_RIGHT = 1 << 5;
+ public static final int ROUND_NOTHING = 0;
+ public static final int ROUND_TOP_LEFT = 1 << 1;
+ public static final int ROUND_TOP_RIGHT = 1 << 2;
+ public static final int ROUND_BOTTOM_LEFT = 1 << 3;
+ public static final int ROUND_BOTTOM_RIGHT = 1 << 4;
public static final int DECORATOR_ALPHA = 255;
protected boolean mShouldDecorateItemsTogether;
diff --git a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
index 2945979..b2497a3 100644
--- a/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
+++ b/src/com/android/launcher3/apppairs/AppPairIconGraphic.kt
@@ -21,9 +21,14 @@
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.util.AttributeSet
+import android.util.Log
import android.view.Gravity
import android.widget.FrameLayout
import com.android.launcher3.DeviceProfile
+import com.android.launcher3.icons.BitmapInfo
+import com.android.launcher3.icons.PlaceHolderIconDrawable
+import com.android.launcher3.model.data.WorkspaceItemInfo
+import com.android.launcher3.util.Themes
/**
* A FrameLayout marking the area on an [AppPairIcon] where the visual icon will be drawn. One of
@@ -31,6 +36,8 @@
*/
class AppPairIconGraphic @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
FrameLayout(context, attrs) {
+ private val TAG = "AppPairIconGraphic"
+
companion object {
// Design specs -- the below ratios are in relation to the size of a standard app icon.
private const val OUTER_PADDING_SCALE = 1 / 30f
@@ -61,8 +68,8 @@
private lateinit var parentIcon: AppPairIcon
private lateinit var appPairBackground: Drawable
- private lateinit var appIcon1: Drawable
- private lateinit var appIcon2: Drawable
+ private var appIcon1: Drawable? = null
+ private var appIcon2: Drawable? = null
fun init(grid: DeviceProfile, icon: AppPairIcon) {
// Calculate device-specific measurements
@@ -79,12 +86,33 @@
appPairBackground = AppPairIconBackground(context, this)
appPairBackground.setBounds(0, 0, backgroundSize.toInt(), backgroundSize.toInt())
- appIcon1 = parentIcon.info.contents[0].newIcon(context)
- appIcon2 = parentIcon.info.contents[1].newIcon(context)
- appIcon1.setBounds(0, 0, memberIconSize.toInt(), memberIconSize.toInt())
- appIcon2.setBounds(0, 0, memberIconSize.toInt(), memberIconSize.toInt())
+ applyIcons(parentIcon.info.contents)
}
+ /** Sets up app pair member icons for drawing. */
+ private fun applyIcons(contents: ArrayList<WorkspaceItemInfo>) {
+ // App pair should always contain 2 members; if not 2, return to avoid a crash loop
+ if (contents.size != 2) {
+ Log.w(TAG, "AppPair contents not 2, size: " + contents.size, Throwable())
+ return
+ }
+
+ // Generate new icons, using themed flag if needed
+ val flags = if (Themes.isThemedIconEnabled(context)) BitmapInfo.FLAG_THEMED else 0
+ val newIcon1 = parentIcon.info.contents[0].newIcon(context, flags)
+ val newIcon2 = parentIcon.info.contents[1].newIcon(context, flags)
+
+ // If app icons did not draw fully last time, animate to full icon
+ (appIcon1 as? PlaceHolderIconDrawable)?.animateIconUpdate(newIcon1)
+ (appIcon2 as? PlaceHolderIconDrawable)?.animateIconUpdate(newIcon2)
+
+ appIcon1 = newIcon1
+ appIcon2 = newIcon2
+ appIcon1?.setBounds(0, 0, memberIconSize.toInt(), memberIconSize.toInt())
+ appIcon2?.setBounds(0, 0, memberIconSize.toInt(), memberIconSize.toInt())
+ }
+
+
/** Gets this icon graphic's bounds, with respect to the parent icon's coordinate system. */
fun getIconBounds(outBounds: Rect) {
outBounds.set(0, 0, backgroundSize.toInt(), backgroundSize.toInt())
@@ -110,6 +138,16 @@
// Draw background
appPairBackground.draw(canvas)
+ // Make sure icons are loaded
+ if (
+ appIcon1 == null ||
+ appIcon2 == null ||
+ appIcon1 is PlaceHolderIconDrawable ||
+ appIcon2 is PlaceHolderIconDrawable
+ ) {
+ applyIcons(parentIcon.info.contents)
+ }
+
// Draw first icon
canvas.save()
// The app icons are placed differently depending on device orientation.
@@ -118,7 +156,7 @@
} else {
canvas.translate(width / 2f - memberIconSize / 2f, innerPadding)
}
- appIcon1.draw(canvas)
+ appIcon1?.draw(canvas)
canvas.restore()
// Draw second icon
@@ -135,7 +173,7 @@
height - (innerPadding + memberIconSize)
)
}
- appIcon2.draw(canvas)
+ appIcon2?.draw(canvas)
canvas.restore()
}
}
diff --git a/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt b/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt
index bdac05d..063dad1 100644
--- a/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt
+++ b/src/com/android/launcher3/backuprestore/LauncherRestoreEventLogger.kt
@@ -22,6 +22,8 @@
const val RESTORE_ERROR_SHORTCUT_NOT_FOUND = "shortcut_not_found"
const val RESTORE_ERROR_APP_NOT_INSTALLED = "app_not_installed"
const val RESTORE_ERROR_WIDGETS_DISABLED = "widgets_disabled"
+ const val RESTORE_ERROR_PROFILE_NOT_RESTORED = "profile_not_restored"
+ const val RESTORE_ERROR_WIDGET_REMOVED = "widget_not_found"
fun newInstance(context: Context?): LauncherRestoreEventLogger {
return ResourceBasedOverride.Overrides.getObject(
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index f9d282c..61e853e 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -167,6 +167,8 @@
"Enable the ability to generate monochromatic icons, if it is not provided by the app");
// TODO(Block 8): Clean up flags
+ public static final BooleanFlag ENABLE_LAUNCHER_BR_METRICS = getDebugFlag(305984208,
+ "ENABLE_LAUNCHER_BR_METRICS", TEAMFOOD, "Enable metrics for Launcher restore");
// TODO(Block 9): Clean up flags
public static final BooleanFlag MULTI_SELECT_EDIT_MODE = getDebugFlag(270709220,
@@ -305,7 +307,7 @@
// TODO(Block 17): Clean up flags
// Aconfig migration complete for ENABLE_TASKBAR_PINNING.
- private static final BooleanFlag ENABLE_TASKBAR_PINNING = getDebugFlag(270396583,
+ private static final BooleanFlag ENABLE_TASKBAR_PINNING = getDebugFlag(296231746,
"ENABLE_TASKBAR_PINNING", TEAMFOOD,
"Enables taskbar pinning to allow user to switch between transient and persistent "
+ "taskbar flavors");
@@ -387,10 +389,6 @@
"ENABLE_NEW_MIGRATION_LOGIC", ENABLED,
"Enable the new grid migration logic, keeping pages when src < dest");
- public static final BooleanFlag ENABLE_CACHED_WIDGET = getDebugFlag(270395008,
- "ENABLE_CACHED_WIDGET", ENABLED,
- "Show previously cached widgets as opposed to deferred widget where available");
-
// TODO(Block 25): Clean up flags
public static final BooleanFlag ENABLE_NEW_GESTURE_NAV_TUTORIAL = getDebugFlag(270396257,
"ENABLE_NEW_GESTURE_NAV_TUTORIAL", ENABLED,
@@ -476,10 +474,10 @@
// TODO(Block 33): Clean up flags
public static final BooleanFlag ENABLE_ALL_APPS_RV_PREINFLATION = getDebugFlag(288161355,
- "ENABLE_ALL_APPS_RV_PREINFLATION", TEAMFOOD,
+ "ENABLE_ALL_APPS_RV_PREINFLATION", ENABLED,
"Enables preinflating all apps icons to avoid scrolling jank.");
public static final BooleanFlag ALL_APPS_GONE_VISIBILITY = getDebugFlag(291651514,
- "ALL_APPS_GONE_VISIBILITY", TEAMFOOD,
+ "ALL_APPS_GONE_VISIBILITY", ENABLED,
"Set all apps container view's hidden visibility to GONE instead of INVISIBLE.");
// TODO(Block 34): Empty block
diff --git a/src/com/android/launcher3/model/AllAppsList.java b/src/com/android/launcher3/model/AllAppsList.java
index 190eb78..7cbfc37 100644
--- a/src/com/android/launcher3/model/AllAppsList.java
+++ b/src/com/android/launcher3/model/AllAppsList.java
@@ -205,6 +205,8 @@
&& installInfo.state == PackageInstallInfo.STATUS_INSTALLING) {
continue;
}
+ // TODO(b/302115555): Handle the case when archived apps are to be updated
+ // during unarchival start.
appInfo.setProgressLevel(installInfo);
updatedAppInfos.add(appInfo);
diff --git a/src/com/android/launcher3/model/GridSizeMigrationUtil.java b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
index efd5574..af66431 100644
--- a/src/com/android/launcher3/model/GridSizeMigrationUtil.java
+++ b/src/com/android/launcher3/model/GridSizeMigrationUtil.java
@@ -262,7 +262,8 @@
String srcTableName, String destTableName) {
int id = copyEntryAndUpdate(helper, entry, srcTableName, destTableName);
- if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER) {
+ if (entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_FOLDER
+ || entry.itemType == LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR) {
for (Set<Integer> itemIds : entry.mFolderItems.values()) {
for (int itemId : itemIds) {
copyEntryAndUpdate(helper, itemId, id, srcTableName, destTableName);
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 8dc2ab3..7b9f6fa 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -17,6 +17,8 @@
package com.android.launcher3.model;
import static com.android.launcher3.BuildConfig.WIDGET_ON_FIRST_SCREEN;
+import static com.android.launcher3.Flags.enableSupportForArchiving;
+import static com.android.launcher3.Flags.enableLauncherBrMetrics;
import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE;
import static com.android.launcher3.LauncherPrefs.SHOULD_SHOW_SMARTSPACE;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
@@ -131,6 +133,7 @@
* - all apps icons
* - deep shortcuts within apps
*/
+@SuppressWarnings("NewApi")
public class LoaderTask implements Runnable {
private static final String TAG = "LoaderTask";
public static final String SMARTSPACE_ON_HOME_SCREEN = "pref_smartspace_home_screen";
@@ -230,8 +233,11 @@
LoaderMemoryLogger memoryLogger = new LoaderMemoryLogger();
mIsRestoreFromBackup =
(Boolean) LauncherPrefs.get(mApp.getContext()).get(IS_FIRST_LOAD_AFTER_RESTORE);
- LauncherRestoreEventLogger restoreEventLogger = LauncherRestoreEventLogger
- .Companion.newInstance(mApp.getContext());
+ LauncherRestoreEventLogger restoreEventLogger = null;
+ if (enableLauncherBrMetrics()) {
+ restoreEventLogger = LauncherRestoreEventLogger.Companion
+ .newInstance(mApp.getContext());
+ }
try (LauncherModel.LoaderTransaction transaction = mApp.getModel().beginLoader(this)) {
List<ShortcutInfo> allShortcuts = new ArrayList<>();
@@ -362,9 +368,11 @@
transaction.commit();
memoryLogger.clearLogs();
if (mIsRestoreFromBackup) {
- restoreEventLogger.reportLauncherRestoreResults();
mIsRestoreFromBackup = false;
LauncherPrefs.get(mApp.getContext()).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(false));
+ if (restoreEventLogger != null) {
+ restoreEventLogger.reportLauncherRestoreResults();
+ }
}
} catch (CancellationException e) {
// Loader stopped, ignore
@@ -419,7 +427,7 @@
final WidgetManagerHelper widgetHelper = new WidgetManagerHelper(context);
ModelDbController dbController = mApp.getModel().getModelDbController();
- dbController.tryMigrateDB();
+ dbController.tryMigrateDB(restoreEventLogger);
Log.d(TAG, "loadWorkspace: loading default favorites");
dbController.loadDefaultFavoritesIfNecessary();
@@ -772,13 +780,21 @@
PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
}
- if (c.restoreFlag != 0 && !TextUtils.isEmpty(targetPkg)) {
+ if ((c.restoreFlag != 0
+ || (enableSupportForArchiving()
+ && activityInfo != null
+ && activityInfo.getApplicationInfo().isArchived))
+ && !TextUtils.isEmpty(targetPkg)) {
tempPackageKey.update(targetPkg, c.user);
SessionInfo si = installingPkgs.get(tempPackageKey);
if (si == null) {
info.runtimeStatusFlags
&= ~ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
- } else if (activityInfo == null) {
+ } else if (activityInfo == null
+ // For archived apps, include progress info in case there is
+ // a pending install session post restart of device.
+ || (enableSupportForArchiving()
+ && activityInfo.getApplicationInfo().isArchived)) {
int installProgress = (int) (si.getProgress() * 100);
info.setProgressLevel(installProgress,
@@ -1095,6 +1111,8 @@
for (int i = 0; i < apps.size(); i++) {
LauncherActivityInfo app = apps.get(i);
AppInfo appInfo = new AppInfo(app, user, quietMode);
+ // TODO(b/302115555): Handle the case when archived apps with active sessions are
+ // loaded.
iconRequestInfos.add(new IconRequestInfo<>(
appInfo, app, /* useLowResIcon= */ false));
diff --git a/src/com/android/launcher3/model/ModelDbController.java b/src/com/android/launcher3/model/ModelDbController.java
index d2b7161..c68274a 100644
--- a/src/com/android/launcher3/model/ModelDbController.java
+++ b/src/com/android/launcher3/model/ModelDbController.java
@@ -19,6 +19,7 @@
import static android.util.Base64.NO_WRAP;
import static com.android.launcher3.DefaultLayoutParser.RES_PARTNER_DEFAULT_LAYOUT;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE;
import static com.android.launcher3.LauncherSettings.Favorites.addTableToDb;
import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_KEY;
import static com.android.launcher3.LauncherSettings.Settings.LAYOUT_DIGEST_LABEL;
@@ -48,6 +49,7 @@
import android.util.Log;
import android.util.Xml;
+import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import com.android.launcher3.AutoInstallsLayout;
@@ -62,6 +64,7 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.Utilities;
+import com.android.launcher3.backuprestore.LauncherRestoreEventLogger;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.provider.LauncherDbUtils;
@@ -86,6 +89,7 @@
private static final String TAG = "LauncherProvider";
private static final String EMPTY_DATABASE_CREATED = "EMPTY_DATABASE_CREATED";
+ private static final String RESTORE_ERROR_GRID_MIGRATION_FAILURE = "grid_migration_failed";
public static final String EXTRA_DB_NAME = "db_name";
protected DatabaseHelper mOpenHelper;
@@ -261,8 +265,12 @@
/**
* Migrates the DB if needed. If the migration failed, it clears the DB.
*/
- public void tryMigrateDB() {
+ public void tryMigrateDB(@Nullable LauncherRestoreEventLogger restoreEventLogger) {
+
if (!migrateGridIfNeeded()) {
+ if (restoreEventLogger != null) {
+ sendMetricsForFailedMigration(restoreEventLogger, getDb());
+ }
FileLog.d(TAG, "Migration failed: resetting launcher database");
createEmptyDB();
LauncherPrefs.get(mContext).putSync(
@@ -313,6 +321,30 @@
}
/**
+ * In case of migration failure, report metrics for the count of each itemType in the DB.
+ * @param restoreEventLogger logger used to report Launcher restore metrics
+ */
+ private void sendMetricsForFailedMigration(LauncherRestoreEventLogger restoreEventLogger,
+ SQLiteDatabase db) {
+ try (Cursor cursor = db.rawQuery(
+ "SELECT itemType, COUNT(*) AS count FROM favorites GROUP BY itemType",
+ null
+ )) {
+ if (cursor.moveToFirst()) {
+ do {
+ restoreEventLogger.logFavoritesItemsRestoreFailed(
+ cursor.getInt(cursor.getColumnIndexOrThrow(ITEM_TYPE)),
+ cursor.getInt(cursor.getColumnIndexOrThrow("count")),
+ RESTORE_ERROR_GRID_MIGRATION_FAILURE
+ );
+ } while (cursor.moveToNext());
+ }
+ } catch (Exception e) {
+ FileLog.e(TAG, "sendMetricsForFailedDb: Error reading from database", e);
+ }
+ }
+
+ /**
* Returns the underlying model database
*/
public SQLiteDatabase getDb() {
diff --git a/src/com/android/launcher3/model/PackageUpdatedTask.java b/src/com/android/launcher3/model/PackageUpdatedTask.java
index 4f2d398..069e96b 100644
--- a/src/com/android/launcher3/model/PackageUpdatedTask.java
+++ b/src/com/android/launcher3/model/PackageUpdatedTask.java
@@ -15,9 +15,11 @@
*/
package com.android.launcher3.model;
+import static com.android.launcher3.Flags.enableSupportForArchiving;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_PRIVATE_PROFILE_QUIET_MODE_ENABLED;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_WORK_PROFILE_QUIET_MODE_ENABLED;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_ARCHIVED;
import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_AUTOINSTALL_ICON;
import static com.android.launcher3.model.data.WorkspaceItemInfo.FLAG_RESTORED_ICON;
@@ -67,6 +69,7 @@
* Handles updates due to changes in package manager (app installed/updated/removed)
* or when a user availability changes.
*/
+@SuppressWarnings("NewApi")
public class PackageUpdatedTask extends BaseModelUpdateTask {
// TODO(b/290090023): Set to false after root causing is done.
@@ -269,6 +272,16 @@
: PackageManagerHelper.getLoadingProgress(
activities.get(0)),
PackageInstallInfo.STATUS_INSTALLED_DOWNLOADING);
+ // In case an app is archived, we need to make sure that archived state
+ // in WorkspaceItemInfo is refreshed.
+ if (enableSupportForArchiving() && !activities.isEmpty()) {
+ boolean newArchivalState = activities.get(
+ 0).getActivityInfo().isArchived;
+ if (newArchivalState != si.isArchived()) {
+ si.runtimeStatusFlags ^= FLAG_ARCHIVED;
+ infoUpdated = true;
+ }
+ }
if (si.itemType == Favorites.ITEM_TYPE_APPLICATION) {
iconCache.getTitleAndIcon(si, si.usingLowResIcon());
infoUpdated = true;
diff --git a/src/com/android/launcher3/model/data/AppInfo.java b/src/com/android/launcher3/model/data/AppInfo.java
index 6c2f589..872ce4b 100644
--- a/src/com/android/launcher3/model/data/AppInfo.java
+++ b/src/com/android/launcher3/model/data/AppInfo.java
@@ -16,6 +16,7 @@
package com.android.launcher3.model.data;
+import static com.android.launcher3.Flags.enableSupportForArchiving;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
import android.content.ComponentName;
@@ -40,6 +41,7 @@
/**
* Represents an app in AllAppsView.
*/
+@SuppressWarnings("NewApi")
public class AppInfo extends ItemInfoWithIcon implements WorkspaceItemFactory {
public static final AppInfo[] EMPTY_ARRAY = new AppInfo[0];
@@ -172,6 +174,9 @@
if (PackageManagerHelper.isAppSuspended(appInfo)) {
info.runtimeStatusFlags |= FLAG_DISABLED_SUSPENDED;
}
+ if (enableSupportForArchiving() && lai.getActivityInfo().isArchived) {
+ info.runtimeStatusFlags |= FLAG_ARCHIVED;
+ }
info.runtimeStatusFlags |= (appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0
? FLAG_SYSTEM_NO : FLAG_SYSTEM_YES;
diff --git a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
index 5141db9..58b12b1 100644
--- a/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
+++ b/src/com/android/launcher3/model/data/ItemInfoWithIcon.java
@@ -16,6 +16,8 @@
package com.android.launcher3.model.data;
+import static com.android.launcher3.Flags.enableSupportForArchiving;
+
import android.content.Context;
import android.content.Intent;
import android.os.Process;
@@ -114,6 +116,12 @@
public static final int FLAG_NOT_PINNABLE = 1 << 13;
/**
+ * Flag indicating whether the package related to the item & user corresponds to that of
+ * archived app.
+ */
+ public static final int FLAG_ARCHIVED = 1 << 14;
+
+ /**
* Status associated with the system state of the underlying item. This is calculated every
* time a new info is created and not persisted on the disk.
*/
@@ -143,6 +151,15 @@
}
/**
+ * Returns true if the app corresponding to the item is archived. */
+ public boolean isArchived() {
+ if (!enableSupportForArchiving()) {
+ return false;
+ }
+ return (runtimeStatusFlags & FLAG_ARCHIVED) != 0;
+ }
+
+ /**
* Indicates whether we're using a low res icon
*/
public boolean usingLowResIcon() {
@@ -158,7 +175,7 @@
public boolean isAppStartable() {
return ((runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) == 0)
&& (((runtimeStatusFlags & FLAG_INCREMENTAL_DOWNLOAD_ACTIVE) != 0)
- || mProgressLevel == 100);
+ || mProgressLevel == 100 || isArchived());
}
/**
@@ -167,7 +184,10 @@
* progress.
*/
public int getProgressLevel() {
- if ((runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) {
+ if (((runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0)
+ // This condition for archived apps is so that in case unarchival/update of
+ // archived app is cancelled, the state transitions back to 0% installed state.
+ || isArchived()) {
return mProgressLevel;
}
return 100;
diff --git a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
index 3ce194d..c67ec5a 100644
--- a/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
+++ b/src/com/android/launcher3/model/data/WorkspaceItemInfo.java
@@ -148,9 +148,19 @@
public final boolean isPromise() {
- return hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON);
+ return hasStatusFlag(FLAG_RESTORED_ICON | FLAG_AUTOINSTALL_ICON)
+ // For archived apps, promise icons are always ready to be displayed.
+ || isArchived();
}
+ /**
+ * Returns true if the workspace item supports promise icon UI. There are a few cases where they
+ * are supported:
+ * 1. Icons to be restored via backup/restore.
+ * 2. Icons added as an auto-install app.
+ * 3. Icons added due to it being an active install session created by the user.
+ * 4. Icons for archived apps.
+ */
public boolean hasPromiseIconUi() {
return isPromise() && !hasStatusFlag(FLAG_SUPPORTS_WEB_UI);
}
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index cb3c16c..ca27eb2 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -16,6 +16,8 @@
package com.android.launcher3.pm;
+import static com.android.launcher3.Flags.enableSupportForArchiving;
+
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
@@ -51,6 +53,7 @@
/**
* Utility class to tracking install sessions
*/
+@SuppressWarnings("NewApi")
public class InstallSessionHelper {
@NonNull
@@ -227,6 +230,11 @@
}
public boolean verifySessionInfo(@Nullable final PackageInstaller.SessionInfo sessionInfo) {
+ // For archived apps we always want to show promise icons and the checks below don't apply.
+ if (enableSupportForArchiving() && sessionInfo != null && sessionInfo.isUnarchival()) {
+ return true;
+ }
+
return verify(sessionInfo) != null
&& sessionInfo.getInstallReason() == PackageManager.INSTALL_REASON_USER
&& sessionInfo.getAppIcon() != null
diff --git a/src/com/android/launcher3/pm/InstallSessionTracker.java b/src/com/android/launcher3/pm/InstallSessionTracker.java
index 41908d3..e4a2045 100644
--- a/src/com/android/launcher3/pm/InstallSessionTracker.java
+++ b/src/com/android/launcher3/pm/InstallSessionTracker.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.pm;
+import static com.android.launcher3.Flags.enableSupportForArchiving;
import static com.android.launcher3.pm.InstallSessionHelper.getUserHandle;
import static com.android.launcher3.pm.PackageInstallInfo.STATUS_FAILED;
import static com.android.launcher3.pm.PackageInstallInfo.STATUS_INSTALLED;
@@ -36,6 +37,7 @@
import java.lang.ref.WeakReference;
import java.util.Objects;
+@SuppressWarnings("NewApi")
@WorkerThread
public class InstallSessionTracker extends PackageInstaller.SessionCallback {
@@ -77,6 +79,12 @@
}
helper.tryQueuePromiseAppIcon(sessionInfo);
+
+ if (enableSupportForArchiving() && sessionInfo != null && sessionInfo.isUnarchival()) {
+ // For archived apps, icon could already be present on the workspace. To make sure
+ // the icon state is updated, we send a change event.
+ callback.onPackageStateChanged(PackageInstallInfo.fromInstallingState(sessionInfo));
+ }
}
@Override
diff --git a/src/com/android/launcher3/popup/ArrowPopup.java b/src/com/android/launcher3/popup/ArrowPopup.java
index e3314d4..4d4a8f7 100644
--- a/src/com/android/launcher3/popup/ArrowPopup.java
+++ b/src/com/android/launcher3/popup/ArrowPopup.java
@@ -636,10 +636,10 @@
return getResources().getDimensionPixelSize(R.dimen.popup_vertical_padding);
}
- protected AnimatorSet getOpenCloseAnimator(boolean isOpening, int scaleDuration,
- int fadeStartDelay, int fadeDuration, int childFadeStartDelay, int childFadeDuration,
- Interpolator interpolator) {
-
+ /**
+ * Sets X and Y pivots for the view animation considering arrow position.
+ */
+ protected void setPivotForOpenCloseAnimation() {
int arrowCenter = mArrowOffsetHorizontal + mArrowWidth / 2;
if (mIsArrowRotated) {
setPivotX(mIsLeftAligned ? 0f : getMeasuredWidth());
@@ -648,6 +648,14 @@
setPivotX(mIsLeftAligned ? arrowCenter : getMeasuredWidth() - arrowCenter);
setPivotY(mIsAboveIcon ? getMeasuredHeight() : 0f);
}
+ }
+
+
+ protected AnimatorSet getOpenCloseAnimator(boolean isOpening, int scaleDuration,
+ int fadeStartDelay, int fadeDuration, int childFadeStartDelay, int childFadeDuration,
+ Interpolator interpolator) {
+
+ setPivotForOpenCloseAnimation();
float[] alphaValues = isOpening ? new float[] {0, 1} : new float[] {1, 0};
float[] scaleValues = isOpening ? new float[] {0.5f, 1.02f} : new float[] {1f, 0.5f};
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 20b2971..a969c8f 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -18,12 +18,19 @@
import static android.os.Process.myUserHandle;
+import static com.android.launcher3.Flags.enableLauncherBrMetrics;
import static com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY;
import static com.android.launcher3.LauncherPrefs.APP_WIDGET_IDS;
import static com.android.launcher3.LauncherPrefs.IS_FIRST_LOAD_AFTER_RESTORE;
import static com.android.launcher3.LauncherPrefs.OLD_APP_WIDGET_IDS;
import static com.android.launcher3.LauncherPrefs.RESTORE_DEVICE;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
+import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPWIDGET;
+import static com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RESTORE_ERROR_PROFILE_NOT_RESTORED;
+import static com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RESTORE_ERROR_WIDGETS_DISABLED;
+import static com.android.launcher3.backuprestore.LauncherRestoreEventLogger.RESTORE_ERROR_WIDGET_REMOVED;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_LAUNCHER_BR_METRICS;
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID;
@@ -53,6 +60,7 @@
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.Utilities;
+import com.android.launcher3.backuprestore.LauncherRestoreEventLogger;
import com.android.launcher3.logging.FileLog;
import com.android.launcher3.model.DeviceGridState;
import com.android.launcher3.model.LoaderTask;
@@ -91,7 +99,8 @@
public static final String APPWIDGET_OLD_IDS = "appwidget_old_ids";
public static final String APPWIDGET_IDS = "appwidget_ids";
- private static final String[] DB_COLUMNS_TO_LOG = {"profileId", "title", "itemType", "screen",
+ @VisibleForTesting
+ public static final String[] DB_COLUMNS_TO_LOG = {"profileId", "title", "itemType", "screen",
"container", "cellX", "cellY", "spanX", "spanY", "intent", "appWidgetProvider",
"appWidgetId", "restored"};
@@ -123,8 +132,11 @@
FileLog.d(TAG, "performRestore: starting restore from db");
try (SQLiteTransaction t = new SQLiteTransaction(db)) {
RestoreDbTask task = new RestoreDbTask();
- task.sanitizeDB(context, controller, db, new BackupManager(context));
- task.restoreAppWidgetIdsIfExists(context, controller);
+ BackupManager backupManager = new BackupManager(context);
+ LauncherRestoreEventLogger restoreEventLogger =
+ LauncherRestoreEventLogger.Companion.newInstance(context);
+ task.sanitizeDB(context, controller, db, backupManager, restoreEventLogger);
+ task.restoreAppWidgetIdsIfExists(context, controller, restoreEventLogger);
t.commit();
return true;
} catch (Exception e) {
@@ -147,7 +159,8 @@
*/
@VisibleForTesting
protected int sanitizeDB(Context context, ModelDbController controller, SQLiteDatabase db,
- BackupManager backupManager) throws Exception {
+ BackupManager backupManager, LauncherRestoreEventLogger restoreEventLogger)
+ throws Exception {
logFavoritesTable(db, "Old Launcher Database before sanitizing:", null, null);
// Primary user ids
long myProfileId = controller.getSerialNumberForUser(myUserHandle());
@@ -186,6 +199,9 @@
Arrays.fill(args, "?");
final String where = "profileId NOT IN (" + TextUtils.join(", ", Arrays.asList(args)) + ")";
logFavoritesTable(db, "items to delete from unrestored profiles:", where, profileIds);
+ if (enableLauncherBrMetrics() || ENABLE_LAUNCHER_BR_METRICS.get()) {
+ reportUnrestoredProfiles(db, where, profileIds, restoreEventLogger);
+ }
int itemsDeletedCount = db.delete(Favorites.TABLE_NAME, where, profileIds);
FileLog.d(TAG, itemsDeletedCount + " total items from unrestored user(s) were deleted");
@@ -345,21 +361,24 @@
DeviceGridState deviceGridState = new DeviceGridState(context);
FileLog.d(TAG, "restore initiated from backup: DeviceGridState=" + deviceGridState);
LauncherPrefs.get(context).putSync(RESTORE_DEVICE.to(deviceGridState.getDeviceType()));
- LauncherPrefs.get(context).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(true));
+ if (enableLauncherBrMetrics() || ENABLE_LAUNCHER_BR_METRICS.get()) {
+ LauncherPrefs.get(context).putSync(IS_FIRST_LOAD_AFTER_RESTORE.to(true));
+ }
}
@WorkerThread
@VisibleForTesting
- void restoreAppWidgetIdsIfExists(Context context, ModelDbController controller) {
+ void restoreAppWidgetIdsIfExists(Context context, ModelDbController controller,
+ LauncherRestoreEventLogger restoreEventLogger) {
LauncherPrefs lp = LauncherPrefs.get(context);
if (lp.has(APP_WIDGET_IDS, OLD_APP_WIDGET_IDS)) {
AppWidgetHost host = new AppWidgetHost(context, APPWIDGET_HOST_ID);
- restoreAppWidgetIds(context, controller,
+ restoreAppWidgetIds(context, controller, restoreEventLogger,
IntArray.fromConcatString(lp.get(OLD_APP_WIDGET_IDS)).toArray(),
IntArray.fromConcatString(lp.get(APP_WIDGET_IDS)).toArray(),
host);
} else {
- FileLog.d(TAG, "No app widget ids were received from backup to restore.");
+ FileLog.d(TAG, "Did not receive new app widget id map during Launcher restore");
}
lp.remove(APP_WIDGET_IDS, OLD_APP_WIDGET_IDS);
@@ -370,10 +389,13 @@
*/
@WorkerThread
private void restoreAppWidgetIds(Context context, ModelDbController controller,
- int[] oldWidgetIds, int[] newWidgetIds, @NonNull AppWidgetHost host) {
+ LauncherRestoreEventLogger launcherRestoreEventLogger, int[] oldWidgetIds,
+ int[] newWidgetIds, @NonNull AppWidgetHost host) {
if (WidgetsModel.GO_DISABLE_WIDGETS) {
FileLog.e(TAG, "Skipping widget ID remap as widgets not supported");
host.deleteHost();
+ launcherRestoreEventLogger.logFavoritesItemsRestoreFailed(Favorites.ITEM_TYPE_APPWIDGET,
+ oldWidgetIds.length, RESTORE_ERROR_WIDGETS_DISABLED);
return;
}
if (!RestoreDbTask.isPending(context)) {
@@ -437,11 +459,16 @@
FileLog.d(TAG, "Deleting widgetId: " + newWidgetIds[i] + " with old id: "
+ oldWidgetId);
host.deleteAppWidgetId(newWidgetIds[i]);
+ launcherRestoreEventLogger.logSingleFavoritesItemRestoreFailed(
+ ITEM_TYPE_APPWIDGET,
+ RESTORE_ERROR_WIDGET_REMOVED
+ );
}
}
}
}
+ logFavoritesTable(controller.getDb(), "launcher db after remap widget ids", null, null);
LauncherAppState app = LauncherAppState.getInstanceNoCreate();
if (app != null) {
app.getModel().forceReload();
@@ -476,17 +503,16 @@
StringBuilder builder = new StringBuilder();
builder.append("[");
for (int i = 0; i < widgetIdList.size(); i++) {
- builder.append("[")
+ builder.append("[appWidgetId=")
.append(widgetIdList.get(i))
- .append(", ")
+ .append(", restoreFlag=")
.append(widgetRestoreList.get(i))
- .append(", ")
+ .append(", profileId=")
.append(widgetProfileIdList.get(i))
.append("]");
}
builder.append("]");
- Log.d(TAG, "restoreAppWidgetIds: all widget ids in database: "
- + builder);
+ Log.d(TAG, "restoreAppWidgetIds: all widget ids in database: " + builder);
} catch (Exception ex) {
Log.e(TAG, "Getting widget ids from the database failed", ex);
}
@@ -545,7 +571,7 @@
*/
public static void logFavoritesTable(SQLiteDatabase database, @NonNull String logHeader,
String where, String[] profileIds) {
- try (Cursor itemsToDelete = database.query(
+ try (Cursor cursor = database.query(
/* table */ Favorites.TABLE_NAME,
/* columns */ DB_COLUMNS_TO_LOG,
/* selection */ where,
@@ -554,26 +580,53 @@
/* having */ null,
/* orderBy */ null
)) {
- if (itemsToDelete.moveToFirst()) {
- String[] columnNames = itemsToDelete.getColumnNames();
+ if (cursor.moveToFirst()) {
+ String[] columnNames = cursor.getColumnNames();
StringBuilder stringBuilder = new StringBuilder(logHeader + "\n");
do {
for (String columnName : columnNames) {
stringBuilder.append(columnName)
.append("=")
- .append(itemsToDelete.getString(
- itemsToDelete.getColumnIndex(columnName)))
+ .append(cursor.getString(
+ cursor.getColumnIndex(columnName)))
.append(" ");
}
stringBuilder.append("\n");
- } while (itemsToDelete.moveToNext());
+ } while (cursor.moveToNext());
FileLog.d(TAG, stringBuilder.toString());
} else {
- FileLog.d(TAG, "logFavoritesTable: No items found from query for"
+ FileLog.d(TAG, "logFavoritesTable: No items found from query for "
+ "\"" + logHeader + "\"");
}
} catch (Exception e) {
FileLog.e(TAG, "logFavoritesTable: Error reading from database", e);
}
}
+
+
+ /**
+ * Queries and reports the count of each itemType to be removed due to unrestored profiles.
+ * @param database The Launcher db to query from.
+ * @param where Query being used for to find unrestored profiles
+ * @param profileIds profile ids that were not restored
+ * @param restoreEventLogger Backup/Restore Logger to report metrics
+ */
+ private void reportUnrestoredProfiles(SQLiteDatabase database, String where,
+ String[] profileIds, LauncherRestoreEventLogger restoreEventLogger) {
+ final String query = "SELECT itemType, COUNT(*) AS count FROM favorites WHERE "
+ + where + " GROUP BY itemType";
+ try (Cursor cursor = database.rawQuery(query, profileIds)) {
+ if (cursor.moveToFirst()) {
+ do {
+ restoreEventLogger.logFavoritesItemsRestoreFailed(
+ cursor.getInt(cursor.getColumnIndexOrThrow(ITEM_TYPE)),
+ cursor.getInt(cursor.getColumnIndexOrThrow("count")),
+ RESTORE_ERROR_PROFILE_NOT_RESTORED
+ );
+ } while (cursor.moveToNext());
+ }
+ } catch (Exception e) {
+ FileLog.e(TAG, "reportUnrestoredProfiles: Error reading from database", e);
+ }
+ }
}
diff --git a/src/com/android/launcher3/responsive/ResponsiveSpec.kt b/src/com/android/launcher3/responsive/ResponsiveSpec.kt
index b0e1b27..65e0b32 100644
--- a/src/com/android/launcher3/responsive/ResponsiveSpec.kt
+++ b/src/com/android/launcher3/responsive/ResponsiveSpec.kt
@@ -59,7 +59,7 @@
val cellSize: SizeSpec,
) : IResponsiveSpec {
init {
- check(isValid()) { "Invalid ResponsiveSpec found." }
+ check(isValid()) { "Invalid ResponsiveSpec found. $this" }
}
constructor(
@@ -106,7 +106,7 @@
}
if (!isValidRemainderSpace()) {
- logError("The total Remainder Space used must be lower or equal to 100%.")
+ logError("The total Remainder Space used must be equal to 0 or 1.")
return false
}
@@ -131,11 +131,12 @@
}
private fun isValidRemainderSpace(): Boolean {
- // TODO(b/313621277): This validation must be update do accept only 0 or 1 instead of <= 1f.
- return startPadding.ofRemainderSpace +
- endPadding.ofRemainderSpace +
- gutter.ofRemainderSpace +
- cellSize.ofRemainderSpace <= 1f
+ val remainderSpaceUsed =
+ startPadding.ofRemainderSpace +
+ endPadding.ofRemainderSpace +
+ gutter.ofRemainderSpace +
+ cellSize.ofRemainderSpace
+ return remainderSpaceUsed == 0f || remainderSpaceUsed == 1f
}
private fun isValidAvailableSpace(): Boolean {
@@ -254,8 +255,8 @@
startPaddingPx = spec.startPadding.getRemainderSpaceValue(remainderSpace, startPaddingPx)
endPaddingPx = spec.endPadding.getRemainderSpaceValue(remainderSpace, endPaddingPx)
- gutterPx = spec.gutter.getRemainderSpaceValue(remainderSpace, gutterPx)
- cellSizePx = spec.cellSize.getRemainderSpaceValue(remainderSpace, cellSizePx)
+ gutterPx = spec.gutter.getRemainderSpaceValue(remainderSpace, gutterPx, gutters)
+ cellSizePx = spec.cellSize.getRemainderSpaceValue(remainderSpace, cellSizePx, cells)
}
override fun hashCode(): Int {
diff --git a/src/com/android/launcher3/responsive/SizeSpec.kt b/src/com/android/launcher3/responsive/SizeSpec.kt
index d146898..41dcd5e 100644
--- a/src/com/android/launcher3/responsive/SizeSpec.kt
+++ b/src/com/android/launcher3/responsive/SizeSpec.kt
@@ -57,11 +57,16 @@
/**
* Calculates the [SizeSpec] value when remainder space value is defined. If no remainderSpace
* is 0, returns a default value.
+ *
+ * @param remainderSpace The remainder space to be used for the calculation
+ * @param defaultValue The default value to be returned when no ofRemainderSpace is defined
+ * @param factor A number to divide the remainder space. The default value is 1. This property
+ * is used to split equally the remainder space by the number of cells and gutters.
*/
- fun getRemainderSpaceValue(remainderSpace: Int, defaultValue: Int): Int {
+ fun getRemainderSpaceValue(remainderSpace: Int, defaultValue: Int, factor: Int = 1): Int {
val remainderSpaceValue =
if (ofRemainderSpace > 0) {
- (ofRemainderSpace * remainderSpace).roundToInt()
+ (ofRemainderSpace * remainderSpace / factor).roundToInt()
} else {
defaultValue
}
diff --git a/src/com/android/launcher3/testing/TestInformationHandler.java b/src/com/android/launcher3/testing/TestInformationHandler.java
index 2c834bd..7b192dc 100644
--- a/src/com/android/launcher3/testing/TestInformationHandler.java
+++ b/src/com/android/launcher3/testing/TestInformationHandler.java
@@ -330,6 +330,7 @@
return null;
}
T value = provider.apply(target);
+
Bundle response = new Bundle();
bundleSetter.set(response, TestProtocol.TEST_INFO_RESPONSE_FIELD, value);
return response;
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index 839f98c..ff8b381 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.touch;
+import static com.android.launcher3.Flags.enableSupportForArchiving;
import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_BIND_PENDING_APPWIDGET;
import static com.android.launcher3.LauncherConstants.ActivityCodes.REQUEST_RECONFIGURE_APPWIDGET;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN;
@@ -319,7 +320,8 @@
}
// Check for abandoned promise
- if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()) {
+ if ((v instanceof BubbleTextView) && shortcut.hasPromiseIconUi()
+ && (!enableSupportForArchiving() || !shortcut.isArchived())) {
String packageName = shortcut.getIntent().getComponent() != null
? shortcut.getIntent().getComponent().getPackageName()
: shortcut.getIntent().getPackage();
diff --git a/src/com/android/launcher3/util/CellContentDimensions.kt b/src/com/android/launcher3/util/CellContentDimensions.kt
index 3c8e0c4..5059c2f 100644
--- a/src/com/android/launcher3/util/CellContentDimensions.kt
+++ b/src/com/android/launcher3/util/CellContentDimensions.kt
@@ -48,7 +48,10 @@
cellContentHeight = getCellContentHeight()
// Step 3. Decrease label size
- if (cellContentHeight > cellHeightPx) {
+ if (
+ cellContentHeight > cellHeightPx &&
+ iconTextSizePx > iconSizeSteps.minimumIconLabelSize
+ ) {
iconTextSizePx =
max(
iconSizeSteps.minimumIconLabelSize,
@@ -58,6 +61,17 @@
}
}
+ // For some cases, depending on the display size, the content might not fit inside the
+ // cell height after considering the minimum icon and label size allowed.
+ // For these extreme cases, we will allow the icon size to be smaller than
+ // [IconSizeSteps.minimumIconSize] to fit inside the cell height without cropping.
+ while (
+ cellContentHeight > cellHeightPx && iconSizePx > IconSizeSteps.ICON_SIZE_STEP_EXTRA
+ ) {
+ iconSizePx -= IconSizeSteps.ICON_SIZE_STEP_EXTRA
+ cellContentHeight = getCellContentHeight()
+ }
+
return cellContentHeight
}
diff --git a/src/com/android/launcher3/util/DisplayController.java b/src/com/android/launcher3/util/DisplayController.java
index 18f583d..1419dc4 100644
--- a/src/com/android/launcher3/util/DisplayController.java
+++ b/src/com/android/launcher3/util/DisplayController.java
@@ -175,6 +175,13 @@
sTransientTaskbarStatusForTests = enable;
}
+ /**
+ * Returns whether the taskbar is pinned in gesture navigation mode.
+ */
+ public static boolean isPinnedTaskbar(Context context) {
+ return INSTANCE.get(context).getInfo().isPinnedTaskbar();
+ }
+
@Override
public void close() {
mDestroyed = true;
@@ -423,6 +430,12 @@
}
return true;
}
+ /**
+ * Returns whether the taskbar is pinned in gesture navigation mode.
+ */
+ public boolean isPinnedTaskbar() {
+ return navigationMode == NavigationMode.NO_BUTTON && !isTransientTaskbar();
+ }
/**
* Returns {@code true} if the bounds represent a tablet.
diff --git a/src/com/android/launcher3/util/IconSizeSteps.kt b/src/com/android/launcher3/util/IconSizeSteps.kt
index 6128eb4..a207d5c 100644
--- a/src/com/android/launcher3/util/IconSizeSteps.kt
+++ b/src/com/android/launcher3/util/IconSizeSteps.kt
@@ -49,5 +49,9 @@
companion object {
internal const val TEXT_STEP = 1
+
+ // This icon extra step is used for stepping down logic in extreme cases when it's
+ // necessary to reduce the icon size below minimum size available in [icon_size_steps].
+ internal const val ICON_SIZE_STEP_EXTRA = 2
}
}
diff --git a/src/com/android/launcher3/util/KeyboardShortcutsDelegate.java b/src/com/android/launcher3/util/KeyboardShortcutsDelegate.java
index c9db83d..e4e0bae 100644
--- a/src/com/android/launcher3/util/KeyboardShortcutsDelegate.java
+++ b/src/com/android/launcher3/util/KeyboardShortcutsDelegate.java
@@ -17,6 +17,7 @@
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
+import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions;
import android.util.Log;
@@ -27,6 +28,7 @@
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Launcher;
+import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.accessibility.BaseAccessibilityDelegate;
@@ -118,17 +120,22 @@
return true;
} else if (mLauncher.getAppsView().isInAllApps()) {
// Close all apps if there are no open floating views.
- closeAllApps();
+ mLauncher.getStateManager().goToState(NORMAL, true);
+ return true;
+ } else if (mLauncher.isInState(LauncherState.OVERVIEW)
+ || mLauncher.isInState(LauncherState.OVERVIEW_SPLIT_SELECT)) {
+ // Close Overview and return to home.
+ mLauncher.getStateManager().goToState(NORMAL, true);
+ return true;
+ } else if (mLauncher.isInState(LauncherState.OVERVIEW_MODAL_TASK)) {
+ // Return to the previous state (Overview) when the modal task is open.
+ mLauncher.getStateManager().goToState(OVERVIEW, true);
return true;
}
}
return null;
}
- private void closeAllApps() {
- mLauncher.getStateManager().goToState(NORMAL, true);
- }
-
/**
* Handle key up event.
* @param keyCode code of the key being pressed.
diff --git a/src/com/android/launcher3/views/ClipIconView.java b/src/com/android/launcher3/views/ClipIconView.java
index 87e496e..7737adb 100644
--- a/src/com/android/launcher3/views/ClipIconView.java
+++ b/src/com/android/launcher3/views/ClipIconView.java
@@ -112,7 +112,7 @@
float scaleY = rect.height() / minSize;
float scale = Math.max(1f, Math.min(scaleX, scaleY));
- if (Float.isNaN(scale)) {
+ if (Float.isNaN(scale) || Float.isInfinite(scale)) {
// Views are no longer laid out, do not update.
return;
}
diff --git a/src/com/android/launcher3/widget/DeferredAppWidgetHostView.java b/src/com/android/launcher3/widget/DeferredAppWidgetHostView.java
deleted file mode 100644
index f42142e..0000000
--- a/src/com/android/launcher3/widget/DeferredAppWidgetHostView.java
+++ /dev/null
@@ -1,93 +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.widget;
-
-import android.appwidget.AppWidgetProviderInfo;
-import android.content.Context;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.text.Layout;
-import android.text.StaticLayout;
-import android.text.TextPaint;
-import android.text.TextUtils;
-import android.util.TypedValue;
-import android.view.View;
-import android.widget.RemoteViews;
-
-import com.android.launcher3.R;
-
-/**
- * A widget host views created while the host has not bind to the system service.
- */
-public class DeferredAppWidgetHostView extends LauncherAppWidgetHostView {
-
- private final TextPaint mPaint;
- private Layout mSetupTextLayout;
-
- public DeferredAppWidgetHostView(Context context) {
- super(context);
- setWillNotDraw(false);
-
- mPaint = new TextPaint();
- mPaint.setColor(Color.WHITE);
- mPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,
- mLauncher.getDeviceProfile().iconTextSizePx,
- getResources().getDisplayMetrics()));
- setBackgroundResource(R.drawable.bg_deferred_app_widget);
- }
-
- @Override
- public void updateAppWidget(RemoteViews remoteViews) {
- // Not allowed
- }
-
- @Override
- public void addView(View child) {
- // Not allowed
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
-
- AppWidgetProviderInfo info = getAppWidgetInfo();
- if (info == null || TextUtils.isEmpty(info.label)) {
- return;
- }
-
- // Use double padding so that there is extra space between background and text if possible.
- int availableWidth = getMeasuredWidth() - 2 * (getPaddingLeft() + getPaddingRight());
- if (availableWidth <= 0) {
- availableWidth = getMeasuredWidth() - (getPaddingLeft() + getPaddingRight());
- }
- if (mSetupTextLayout != null && mSetupTextLayout.getText().equals(info.label)
- && mSetupTextLayout.getWidth() == availableWidth) {
- return;
- }
- mSetupTextLayout = new StaticLayout(info.label, mPaint, availableWidth,
- Layout.Alignment.ALIGN_CENTER, 1, 0, true);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- if (mSetupTextLayout != null) {
- canvas.translate((getWidth() - mSetupTextLayout.getWidth()) / 2,
- (getHeight() - mSetupTextLayout.getHeight()) / 2);
- mSetupTextLayout.draw(canvas);
- }
- }
-}
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
index 9c21ea2..739e204 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHost.java
@@ -89,7 +89,7 @@
@NonNull
public LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId,
AppWidgetProviderInfo appWidget) {
- return mHolder.onCreateView(context, appWidgetId, appWidget);
+ return mHolder.onCreateView(context, appWidgetId);
}
/**
diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
index 12b47e6..e0de269 100644
--- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java
@@ -45,7 +45,6 @@
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.dragndrop.DragLayer;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
@@ -96,8 +95,6 @@
private boolean mTrackingWidgetUpdate = false;
- private boolean mIsWidgetCachingDisabled = false;
-
public LauncherAppWidgetHostView(Context context) {
super(context);
mLauncher = Launcher.getLauncher(context);
@@ -144,10 +141,6 @@
}
}
- public void setIsWidgetCachingDisabled(boolean isWidgetCachingDisabled) {
- mIsWidgetCachingDisabled = isWidgetCachingDisabled;
- }
-
@Override
@TargetApi(Build.VERSION_CODES.Q)
public void updateAppWidget(RemoteViews remoteViews) {
@@ -157,19 +150,11 @@
TRACE_METHOD_NAME + getAppWidgetInfo().provider, getAppWidgetId());
mTrackingWidgetUpdate = false;
}
- if (FeatureFlags.ENABLE_CACHED_WIDGET.get()
- && !mIsWidgetCachingDisabled) {
+ if (isDeferringUpdates()) {
mLastRemoteViews = remoteViews;
- if (isDeferringUpdates()) {
- return;
- }
- } else {
- if (isDeferringUpdates()) {
- mLastRemoteViews = remoteViews;
- return;
- }
- mLastRemoteViews = null;
+ return;
}
+ mLastRemoteViews = null;
super.updateAppWidget(remoteViews);
@@ -438,22 +423,6 @@
scheduleNextAdvance();
}
- public void reInflate() {
- if (!isAttachedToWindow()) {
- return;
- }
- LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) getTag();
- if (info == null) {
- // This occurs when LauncherAppWidgetHostView is used to render a preview layout.
- return;
- }
- // Remove and rebind the current widget (which was inflated in the wrong
- // orientation), but don't delete it from the database
- mLauncher.removeItem(this, info, false /* deleteFromDb */,
- "widget removed because of configuration change");
- mLauncher.bindAppWidget(info);
- }
-
@Override
protected boolean shouldAllowDirectClick() {
if (getTag() instanceof ItemInfo) {
diff --git a/src/com/android/launcher3/widget/LauncherWidgetHolder.java b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
index 6acc83d..fbd48cf 100644
--- a/src/com/android/launcher3/widget/LauncherWidgetHolder.java
+++ b/src/com/android/launcher3/widget/LauncherWidgetHolder.java
@@ -28,7 +28,6 @@
import android.content.Intent;
import android.os.Bundle;
import android.util.SparseArray;
-import android.widget.RemoteViews;
import android.widget.Toast;
import androidx.annotation.NonNull;
@@ -36,10 +35,8 @@
import com.android.launcher3.BaseActivity;
import com.android.launcher3.BaseDraggingActivity;
-import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
-import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.testing.TestLogging;
@@ -70,11 +67,9 @@
private final AppWidgetHost mWidgetHost;
@NonNull
- private final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
+ protected final SparseArray<LauncherAppWidgetHostView> mViews = new SparseArray<>();
@NonNull
private final SparseArray<PendingAppWidgetHostView> mPendingViews = new SparseArray<>();
- @NonNull
- private final SparseArray<LauncherAppWidgetHostView> mDeferredViews = new SparseArray<>();
protected int mFlags = FLAG_STATE_IS_NORMAL;
@@ -121,25 +116,12 @@
* Update any views which have been deferred because the host was not listening.
*/
protected void updateDeferredView() {
+ // Update any views which have been deferred because the host was not listening.
// We go in reverse order and inflate any deferred or cached widget
for (int i = mViews.size() - 1; i >= 0; i--) {
LauncherAppWidgetHostView view = mViews.valueAt(i);
- if (view instanceof DeferredAppWidgetHostView) {
- view.reInflate();
- }
- if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
- final int appWidgetId = mViews.keyAt(i);
- if (view == mDeferredViews.get(appWidgetId)) {
- // If the widget view was deferred, we'll need to call super.createView here
- // to make the binder call to system process to fetch cumulative updates to this
- // widget, as well as setting up this view for future updates.
- mWidgetHost.createView(view.mLauncher, appWidgetId,
- view.getAppWidgetInfo());
- // At this point #onCreateView should have been called, which in turn returned
- // the deferred view. There's no reason to keep the reference anymore, so we
- // removed it here.
- mDeferredViews.remove(appWidgetId);
- }
+ if (view instanceof PendingAppWidgetHostView pv) {
+ pv.reInflate();
}
}
}
@@ -173,12 +155,6 @@
public void deleteAppWidgetId(int appWidgetId) {
mWidgetHost.deleteAppWidgetId(appWidgetId);
mViews.remove(appWidgetId);
- if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
- final LauncherAppState state = LauncherAppState.getInstance(mContext);
- synchronized (state.mCachedRemoteViews) {
- state.mCachedRemoteViews.delete(appWidgetId);
- }
- }
}
/**
@@ -319,17 +295,6 @@
if (WidgetsModel.GO_DISABLE_WIDGETS) {
return;
}
- if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
- // Cache the content from the widgets when Launcher stops listening to widget updates
- final LauncherAppState state = LauncherAppState.getInstance(mContext);
- synchronized (state.mCachedRemoteViews) {
- for (int i = 0; i < mViews.size(); i++) {
- final int appWidgetId = mViews.keyAt(i);
- final LauncherAppWidgetHostView view = mViews.get(appWidgetId);
- state.mCachedRemoteViews.put(appWidgetId, view.mLastRemoteViews);
- }
- }
- }
mWidgetHost.stopListening();
setListeningFlag(false);
}
@@ -360,6 +325,7 @@
@NonNull
public AppWidgetHostView createView(@NonNull Context context, int appWidgetId,
@NonNull LauncherAppWidgetProviderInfo appWidget) {
+
if (appWidget.isCustomWidget()) {
LauncherAppWidgetHostView lahv = new LauncherAppWidgetHostView(context);
lahv.setAppWidget(0, appWidget);
@@ -369,24 +335,8 @@
// Since the launcher hasn't started listening to widget updates, we can't simply call
// super.createView here because the later will make a binder call to retrieve
// RemoteViews from system process.
- // TODO: have launcher always listens to widget updates in background so that this
- // check can be removed altogether.
- if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
- final RemoteViews cachedRemoteViews = getCachedRemoteViews(appWidgetId);
- if (cachedRemoteViews != null) {
- // We've found RemoteViews from cache for this widget, so we will instantiate a
- // widget host view and populate it with the cached RemoteViews.
- final LauncherAppWidgetHostView view = new LauncherAppWidgetHostView(context);
- view.setAppWidget(appWidgetId, appWidget);
- view.updateAppWidget(cachedRemoteViews);
- mDeferredViews.put(appWidgetId, view);
- mViews.put(appWidgetId, view);
- return view;
- }
- }
- // If cache misses or not enabled, a placeholder for the widget will be returned.
- DeferredAppWidgetHostView view = new DeferredAppWidgetHostView(context);
- view.setAppWidget(appWidgetId, appWidget);
+ LauncherAppWidgetHostView view =
+ new PendingAppWidgetHostView(context, appWidgetId, appWidget);
mViews.put(appWidgetId, view);
return view;
} else {
@@ -402,7 +352,7 @@
// will update.
LauncherAppWidgetHostView view = mViews.get(appWidgetId);
if (view == null) {
- view = onCreateView(mContext, appWidgetId, appWidget);
+ view = onCreateView(mContext, appWidgetId);
}
view.setAppWidget(appWidgetId, appWidget);
view.switchToErrorView();
@@ -423,23 +373,17 @@
/**
* Called to return a proper view when creating a view
- * @param context The context for which the widget view is created
+ *
+ * @param context The context for which the widget view is created
* @param appWidgetId The ID of the added widget
- * @param appWidget The provider info of the added widget
* @return A view for the specified app widget
*/
@NonNull
- public LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId,
- AppWidgetProviderInfo appWidget) {
+ public LauncherAppWidgetHostView onCreateView(Context context, int appWidgetId) {
final LauncherAppWidgetHostView view;
if (getPendingView(appWidgetId) != null) {
view = getPendingView(appWidgetId);
removePendingView(appWidgetId);
- } else if (mDeferredViews.get(appWidgetId) != null) {
- // In case the widget view is deferred, we will simply return the deferred view as
- // opposed to instantiate a new instance of LauncherAppWidgetHostView since launcher
- // already added the former to the workspace.
- view = mDeferredViews.get(appWidgetId);
} else {
view = new LauncherAppWidgetHostView(context);
}
@@ -453,10 +397,6 @@
public void clearViews() {
LauncherAppWidgetHost tempHost = (LauncherAppWidgetHost) mWidgetHost;
tempHost.clearViews();
- if (FeatureFlags.ENABLE_CACHED_WIDGET.get()) {
- // Clear previously cached content from existing widgets
- mDeferredViews.clear();
- }
mViews.clear();
}
@@ -496,14 +436,6 @@
return (flags & FLAGS_SHOULD_LISTEN) == FLAGS_SHOULD_LISTEN;
}
- @Nullable
- private RemoteViews getCachedRemoteViews(int appWidgetId) {
- final LauncherAppState state = LauncherAppState.getInstance(mContext);
- synchronized (state.mCachedRemoteViews) {
- return state.mCachedRemoteViews.get(appWidgetId);
- }
- }
-
/**
* Returns the new LauncherWidgetHolder instance
*/
diff --git a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
index 1c88c4a..2bd4c7e 100644
--- a/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
+++ b/src/com/android/launcher3/widget/PendingAppWidgetHostView.java
@@ -24,11 +24,13 @@
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
+import android.text.TextUtils;
import android.util.SizeF;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
@@ -46,7 +48,6 @@
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.PackageItemInfo;
-import com.android.launcher3.touch.ItemClickHandler;
import com.android.launcher3.util.Themes;
import java.util.List;
@@ -56,11 +57,20 @@
private static final float SETUP_ICON_SIZE_FACTOR = 2f / 5;
private static final float MIN_SATUNATION = 0.7f;
+ private static final int FLAG_DRAW_SETTINGS = 1;
+ private static final int FLAG_DRAW_ICON = 2;
+ private static final int FLAG_DRAW_LABEL = 4;
+
+ private static final int DEFERRED_ALPHA = 0x77;
+
private final Rect mRect = new Rect();
private OnClickListener mClickListener;
private final LauncherAppWidgetInfo mInfo;
private final int mStartState;
private final boolean mDisabledForSafeMode;
+ private final CharSequence mLabel;
+
+ private int mDragFlags;
private Drawable mCenterDrawable;
private Drawable mSettingIconDrawable;
@@ -72,18 +82,8 @@
public PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info,
IconCache cache, boolean disabledForSafeMode) {
- super(new ContextThemeWrapper(context, R.style.WidgetContainerTheme));
-
- mInfo = info;
- mStartState = info.restoreStatus;
- mDisabledForSafeMode = disabledForSafeMode;
-
- mPaint = new TextPaint();
- mPaint.setColor(Themes.getAttrColor(getContext(), android.R.attr.textColorPrimary));
- mPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,
- mLauncher.getDeviceProfile().iconTextSizePx, getResources().getDisplayMetrics()));
- setBackgroundResource(R.drawable.pending_widget_bg);
- setWillNotDraw(false);
+ this(context, info, disabledForSafeMode,
+ context.getResources().getText(R.string.gadget_complete_setup_text));
super.updateAppWidget(null);
setOnClickListener(mLauncher.getItemOnClickListener());
@@ -97,15 +97,62 @@
}
}
+ public PendingAppWidgetHostView(
+ Context context, int appWidgetId, LauncherAppWidgetProviderInfo appWidget) {
+ this(context, new LauncherAppWidgetInfo(appWidgetId, appWidget.provider), false,
+ appWidget.label);
+ getBackground().mutate().setAlpha(DEFERRED_ALPHA);
+
+ mCenterDrawable = new ColorDrawable(Color.TRANSPARENT);
+ mDragFlags = FLAG_DRAW_LABEL;
+ mDrawableSizeChanged = true;
+ }
+
+ private PendingAppWidgetHostView(Context context, LauncherAppWidgetInfo info,
+ boolean disabledForSafeMode, CharSequence label) {
+ super(new ContextThemeWrapper(context, R.style.WidgetContainerTheme));
+
+ mInfo = info;
+ mStartState = info.restoreStatus;
+ mDisabledForSafeMode = disabledForSafeMode;
+ mLabel = label;
+
+ mPaint = new TextPaint();
+ mPaint.setColor(Themes.getAttrColor(getContext(), android.R.attr.textColorPrimary));
+ mPaint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,
+ mLauncher.getDeviceProfile().iconTextSizePx, getResources().getDisplayMetrics()));
+
+ setWillNotDraw(false);
+ setBackgroundResource(R.drawable.pending_widget_bg);
+ }
+
@Override
public void updateAppWidget(RemoteViews remoteViews) {
WidgetManagerHelper widgetManagerHelper = new WidgetManagerHelper(getContext());
if (widgetManagerHelper.isAppWidgetRestored(mInfo.appWidgetId)) {
- super.updateAppWidget(remoteViews);
reInflate();
}
}
+ /**
+ * Forces the Launcher to reinflate the widget view
+ */
+ public void reInflate() {
+ if (!isAttachedToWindow()) {
+ return;
+ }
+ LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) getTag();
+ if (info == null) {
+ // This occurs when LauncherAppWidgetHostView is used to render a preview layout.
+ return;
+ }
+ // Remove and rebind the current widget (which was inflated in the wrong
+ // orientation), but don't delete it from the database
+ mLauncher.removeItem(this, info, false /* deleteFromDb */,
+ "widget removed because of configuration change");
+ mLauncher.bindAppWidget(info);
+ }
+
@Override
public void updateAppWidgetSize(Bundle newOptions, int minWidth, int minHeight, int maxWidth,
int maxHeight) {
@@ -147,7 +194,10 @@
mCenterDrawable.setCallback(null);
mCenterDrawable = null;
}
+ mDragFlags = 0;
if (info.bitmap.icon != null) {
+ mDragFlags = FLAG_DRAW_ICON;
+
Drawable widgetCategoryIcon = getWidgetCategoryIcon();
// The view displays three modes,
// 1) App icon in the center
@@ -169,6 +219,8 @@
: widgetCategoryIcon;
mSettingIconDrawable = getResources().getDrawable(R.drawable.ic_setting).mutate();
updateSettingColor(info.bitmap.color);
+
+ mDragFlags |= FLAG_DRAW_SETTINGS | FLAG_DRAW_LABEL;
} else {
mCenterDrawable = widgetCategoryIcon == null
? newPendingIcon(getContext(), info)
@@ -239,68 +291,63 @@
int availableWidth = getWidth() - paddingLeft - paddingRight - 2 * minPadding;
int availableHeight = getHeight() - paddingTop - paddingBottom - 2 * minPadding;
- if (mSettingIconDrawable == null) {
- int maxSize = grid.iconSizePx;
- int size = Math.min(maxSize, Math.min(availableWidth, availableHeight));
+ float iconSize = ((mDragFlags & FLAG_DRAW_ICON) == 0) ? 0
+ : Math.max(0, Math.min(availableWidth, availableHeight));
+ // Use twice the setting size factor, as the setting is drawn at a corner and the
+ // icon is drawn in the center.
+ float settingIconScaleFactor = ((mDragFlags & FLAG_DRAW_SETTINGS) == 0) ? 0
+ : 1 + SETUP_ICON_SIZE_FACTOR * 2;
- mRect.set(0, 0, size, size);
- mRect.offsetTo((getWidth() - mRect.width()) / 2, (getHeight() - mRect.height()) / 2);
- mCenterDrawable.setBounds(mRect);
- } else {
- float iconSize = Math.max(0, Math.min(availableWidth, availableHeight));
+ int maxSize = Math.max(availableWidth, availableHeight);
+ if (iconSize * settingIconScaleFactor > maxSize) {
+ // There is an overlap
+ iconSize = maxSize / settingIconScaleFactor;
+ }
- // Use twice the setting size factor, as the setting is drawn at a corner and the
- // icon is drawn in the center.
- float settingIconScaleFactor = 1 + SETUP_ICON_SIZE_FACTOR * 2;
- int maxSize = Math.max(availableWidth, availableHeight);
- if (iconSize * settingIconScaleFactor > maxSize) {
- // There is an overlap
- iconSize = maxSize / settingIconScaleFactor;
+ int actualIconSize = (int) Math.min(iconSize, grid.iconSizePx);
+
+ // Icon top when we do not draw the text
+ int iconTop = (getHeight() - actualIconSize) / 2;
+ mSetupTextLayout = null;
+
+ if (availableWidth > 0 && !TextUtils.isEmpty(mLabel)
+ && ((mDragFlags & FLAG_DRAW_LABEL) != 0)) {
+ // Recreate the setup text.
+ mSetupTextLayout = new StaticLayout(
+ mLabel, mPaint, availableWidth, Layout.Alignment.ALIGN_CENTER, 1, 0, true);
+ int textHeight = mSetupTextLayout.getHeight();
+
+ // Extra icon size due to the setting icon
+ float minHeightWithText = textHeight + actualIconSize * settingIconScaleFactor
+ + grid.iconDrawablePaddingPx;
+
+ if (minHeightWithText < availableHeight) {
+ // We can draw the text as well
+ iconTop = (getHeight() - textHeight
+ - grid.iconDrawablePaddingPx - actualIconSize) / 2;
+
+ } else {
+ // We can't draw the text. Let the iconTop be same as before.
+ mSetupTextLayout = null;
}
+ }
- int actualIconSize = (int) Math.min(iconSize, grid.iconSizePx);
+ mRect.set(0, 0, actualIconSize, actualIconSize);
+ mRect.offset((getWidth() - actualIconSize) / 2, iconTop);
+ mCenterDrawable.setBounds(mRect);
- // Icon top when we do not draw the text
- int iconTop = (getHeight() - actualIconSize) / 2;
- mSetupTextLayout = null;
-
- if (availableWidth > 0) {
- // Recreate the setup text.
- mSetupTextLayout = new StaticLayout(
- getResources().getText(R.string.gadget_complete_setup_text), mPaint,
- availableWidth, Layout.Alignment.ALIGN_CENTER, 1, 0, true);
- int textHeight = mSetupTextLayout.getHeight();
-
- // Extra icon size due to the setting icon
- float minHeightWithText = textHeight + actualIconSize * settingIconScaleFactor
- + grid.iconDrawablePaddingPx;
-
- if (minHeightWithText < availableHeight) {
- // We can draw the text as well
- iconTop = (getHeight() - textHeight -
- grid.iconDrawablePaddingPx - actualIconSize) / 2;
-
- } else {
- // We can't draw the text. Let the iconTop be same as before.
- mSetupTextLayout = null;
- }
- }
-
- mRect.set(0, 0, actualIconSize, actualIconSize);
- mRect.offset((getWidth() - actualIconSize) / 2, iconTop);
- mCenterDrawable.setBounds(mRect);
-
+ if (mSettingIconDrawable != null) {
mRect.left = paddingLeft + minPadding;
mRect.right = mRect.left + (int) (SETUP_ICON_SIZE_FACTOR * actualIconSize);
mRect.top = paddingTop + minPadding;
mRect.bottom = mRect.top + (int) (SETUP_ICON_SIZE_FACTOR * actualIconSize);
mSettingIconDrawable.setBounds(mRect);
+ }
- if (mSetupTextLayout != null) {
- // Set up position for dragging the text
- mRect.left = paddingLeft + minPadding;
- mRect.top = mCenterDrawable.getBounds().bottom + grid.iconDrawablePaddingPx;
- }
+ if (mSetupTextLayout != null) {
+ // Set up position for dragging the text
+ mRect.left = paddingLeft + minPadding;
+ mRect.top = mCenterDrawable.getBounds().bottom + grid.iconDrawablePaddingPx;
}
}
diff --git a/tests/Android.bp b/tests/Android.bp
index bdb53ba..dd0ba9e 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -174,3 +174,77 @@
sdk_version: "current",
min_sdk_version: min_launcher3_sdk_version,
}
+
+filegroup {
+ name: "launcher-testing-helpers",
+ srcs: [
+ "src/**/*.java",
+ "src/**/*.kt",
+ "src/com/android/launcher3/ui/AbstractLauncherUiTest.java",
+ "tapl/com/android/launcher3/tapl/*.java",
+ "tapl/com/android/launcher3/tapl/*.kt",
+ ],
+ exclude_srcs: [
+ // Test classes
+ "src/**/*Test.java",
+ "src/**/*Test.kt",
+ ],
+}
+
+android_library {
+ name: "Launcher3Lib",
+ srcs: [
+ ":launcher-src",
+ ":launcher-src_shortcuts_overrides",
+ ":launcher-src_ui_overrides",
+ ],
+ static_libs: [
+ "Launcher3CommonDepsLib",
+ ],
+}
+
+android_robolectric_test {
+ enabled: true,
+ name: "Launcher3RoboTests",
+ srcs: [
+ "src/com/android/launcher3/util/*.java",
+ "src/com/android/launcher3/util/*.kt",
+
+ // Test util classes
+ ":launcher-testing-helpers",
+ ":launcher-testing-shared",
+ ],
+ exclude_srcs: [
+ "src/com/android/launcher3/util/CellContentDimensionsTest.kt", // Failing - b/316553889
+
+ // requires modification to work with inline mock maker
+ "src/com/android/launcher3/util/rule/StaticMockitoRule.java",
+
+ // requires kotlin mockito
+ "src/com/android/launcher3/util/LockedUserStateTest.kt",
+ "src/com/android/launcher3/util/DisplayControllerTest.kt",
+ ],
+ java_resource_dirs: ["config"],
+ static_libs: [
+ "flag-junit-base",
+ "com_android_launcher3_flags_lib",
+ "com_android_wm_shell_flags_lib",
+ "androidx.test.uiautomator_uiautomator",
+ "androidx.core_core-animation-testing",
+ "androidx.test.ext.junit",
+ "inline-mockito-robolectric-prebuilt",
+ "platform-parametric-runner-lib",
+ "testables",
+ "Launcher3TestResources",
+ "SystemUISharedLib",
+ "launcher-testing-shared",
+ ],
+ libs: [
+ "android.test.runner",
+ "android.test.base",
+ "android.test.mock",
+ "truth",
+ ],
+ instrumentation_for: "Launcher3",
+ upstream: true,
+}
diff --git a/tests/AndroidManifest-common.xml b/tests/AndroidManifest-common.xml
index bd9da0a..e5e3354 100644
--- a/tests/AndroidManifest-common.xml
+++ b/tests/AndroidManifest-common.xml
@@ -149,7 +149,8 @@
android:name="com.android.launcher3.testcomponent.BaseTestingActivity"
android:label="LauncherTestApp"
android:exported="true"
- android:taskAffinity="com.android.launcher3.testcomponent.Affinity1">
+ android:taskAffinity="com.android.launcher3.testcomponent.Affinity1"
+ android:theme="@style/Theme.TestActivities">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
@@ -343,6 +344,24 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity-alias>
+ <activity-alias android:name="AAAActivity"
+ android:label="AAA"
+ android:exported="true"
+ android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity-alias>
+ <activity-alias android:name="ZZZActivity"
+ android:label="ZZZ"
+ android:exported="true"
+ android:targetActivity="com.android.launcher3.testcomponent.BaseTestingActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity-alias>
<!-- [b/197780098] Disable eager initialization of Jetpack libraries. -->
<provider
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 5cf96c8..2596b75 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -23,6 +23,14 @@
<application android:debuggable="true">
<uses-library android:name="android.test.runner" />
+
+ <receiver android:name="com.android.launcher3.compat.PromiseIconUiTest$UnarchiveBroadcastReceiver"
+ android:enabled="true"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.UNARCHIVE_PACKAGE"/>
+ </intent-filter>
+ </receiver>
</application>
<instrumentation
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
index 0f27893..92caf23 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape.txt
@@ -55,7 +55,7 @@
bottomSheetCloseDuration: 500
bottomSheetWorkspaceScale: 0.97
bottomSheetDepth: 0.0
- allAppsShiftRange: 1496.0px (748.0dp)
+ allAppsShiftRange: 1600.0px (800.0dp)
allAppsOpenDuration: 500
allAppsCloseDuration: 500
allAppsIconSizePx: 120.0px (60.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
index 85f7ca1..3815fa9 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletLandscape3Button.txt
@@ -55,7 +55,7 @@
bottomSheetCloseDuration: 500
bottomSheetWorkspaceScale: 0.97
bottomSheetDepth: 0.0
- allAppsShiftRange: 1496.0px (748.0dp)
+ allAppsShiftRange: 1600.0px (800.0dp)
allAppsOpenDuration: 500
allAppsCloseDuration: 500
allAppsIconSizePx: 120.0px (60.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
index bd47777..7e0f316 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait.txt
@@ -55,7 +55,7 @@
bottomSheetCloseDuration: 500
bottomSheetWorkspaceScale: 0.97
bottomSheetDepth: 0.0
- allAppsShiftRange: 2019.0px (1009.5dp)
+ allAppsShiftRange: 2560.0px (1280.0dp)
allAppsOpenDuration: 500
allAppsCloseDuration: 500
allAppsIconSizePx: 120.0px (60.0dp)
@@ -66,7 +66,7 @@
allAppsBorderSpacePxX: 16.0px (8.0dp)
allAppsBorderSpacePxY: 32.0px (16.0dp)
numShownAllAppsColumns: 6
- allAppsPadding.top: 541.0px (270.5dp)
+ allAppsPadding.top: 104.0px (52.0dp)
allAppsPadding.left: 32.0px (16.0dp)
allAppsPadding.right: 32.0px (16.0dp)
allAppsLeftRightMargin: 152.0px (76.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
index 902885a..58c3890 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/tabletPortrait3Button.txt
@@ -55,7 +55,7 @@
bottomSheetCloseDuration: 500
bottomSheetWorkspaceScale: 0.97
bottomSheetDepth: 0.0
- allAppsShiftRange: 2019.0px (1009.5dp)
+ allAppsShiftRange: 2560.0px (1280.0dp)
allAppsOpenDuration: 500
allAppsCloseDuration: 500
allAppsIconSizePx: 120.0px (60.0dp)
@@ -66,7 +66,7 @@
allAppsBorderSpacePxX: 16.0px (8.0dp)
allAppsBorderSpacePxY: 32.0px (16.0dp)
numShownAllAppsColumns: 6
- allAppsPadding.top: 541.0px (270.5dp)
+ allAppsPadding.top: 104.0px (52.0dp)
allAppsPadding.left: 32.0px (16.0dp)
allAppsPadding.right: 32.0px (16.0dp)
allAppsLeftRightMargin: 152.0px (76.0dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
index 43e4a60..1e363a2 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape.txt
@@ -55,7 +55,7 @@
bottomSheetCloseDuration: 500
bottomSheetWorkspaceScale: 0.97
bottomSheetDepth: 1.0
- allAppsShiftRange: 1730.0px (659.0476dp)
+ allAppsShiftRange: 1840.0px (700.9524dp)
allAppsOpenDuration: 500
allAppsCloseDuration: 500
allAppsIconSizePx: 141.0px (53.714287dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
index e7ea839..617b54b 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelLandscape3Button.txt
@@ -55,7 +55,7 @@
bottomSheetCloseDuration: 500
bottomSheetWorkspaceScale: 0.97
bottomSheetDepth: 1.0
- allAppsShiftRange: 1730.0px (659.0476dp)
+ allAppsShiftRange: 1840.0px (700.9524dp)
allAppsOpenDuration: 500
allAppsCloseDuration: 500
allAppsIconSizePx: 141.0px (53.714287dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
index 043380c..483b5e7 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait.txt
@@ -55,7 +55,7 @@
bottomSheetCloseDuration: 500
bottomSheetWorkspaceScale: 0.97
bottomSheetDepth: 1.0
- allAppsShiftRange: 2075.0px (790.4762dp)
+ allAppsShiftRange: 2208.0px (841.1429dp)
allAppsOpenDuration: 500
allAppsCloseDuration: 500
allAppsIconSizePx: 141.0px (53.714287dp)
diff --git a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
index a1b3e95..8d0640c 100644
--- a/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
+++ b/tests/assets/dumpTests/DeviceProfileDumpTest/twoPanelPortrait3Button.txt
@@ -55,7 +55,7 @@
bottomSheetCloseDuration: 500
bottomSheetWorkspaceScale: 0.97
bottomSheetDepth: 1.0
- allAppsShiftRange: 2075.0px (790.4762dp)
+ allAppsShiftRange: 2208.0px (841.1429dp)
allAppsOpenDuration: 500
allAppsCloseDuration: 500
allAppsIconSizePx: 141.0px (53.714287dp)
diff --git a/tests/config/robolectric.properties b/tests/config/robolectric.properties
new file mode 100644
index 0000000..fab7251
--- /dev/null
+++ b/tests/config/robolectric.properties
@@ -0,0 +1 @@
+sdk=NEWEST_SDK
diff --git a/tests/res/values/styles.xml b/tests/res/values/styles.xml
new file mode 100644
index 0000000..1e1a2cd
--- /dev/null
+++ b/tests/res/values/styles.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (C) 2024 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.
+ -->
+
+<resources>
+ <style name="Theme.TestActivities" parent="@android:style/Theme.DeviceDefault.DayNight">
+ <!-- Hardcoded ActionBar height to avoid changes while emulating -->
+ <!-- (56dp - default for handheld) -->
+ <item name="android:actionBarSize">168px</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/tests/res/xml/invalid_responsive_spec_1.xml b/tests/res/xml/invalid_responsive_spec_1.xml
index d1bcf65..7845d1e 100644
--- a/tests/res/xml/invalid_responsive_spec_1.xml
+++ b/tests/res/xml/invalid_responsive_spec_1.xml
@@ -27,7 +27,7 @@
<workspaceSpec
launcher:maxAvailableSize="9999dp"
launcher:dimensionType="width">
- <cellSize launcher:ofRemainderSpace="0.25" />
+ <cellSize launcher:ofRemainderSpace="1" />
<endPadding launcher:fixedSize="22dp" />
<gutter launcher:fixedSize="16dp" />
<startPadding launcher:fixedSize="22dp" />
diff --git a/tests/res/xml/invalid_responsive_spec_2.xml b/tests/res/xml/invalid_responsive_spec_2.xml
index 49e1f93..ae30bb9 100644
--- a/tests/res/xml/invalid_responsive_spec_2.xml
+++ b/tests/res/xml/invalid_responsive_spec_2.xml
@@ -28,7 +28,7 @@
<workspaceSpec
launcher:maxAvailableSize="9999dp"
launcher:dimensionType="width">
- <cellSize launcher:ofRemainderSpace="0.25" />
+ <cellSize launcher:ofRemainderSpace="1" />
<endPadding launcher:fixedSize="22dp" />
<gutter launcher:fixedSize="16dp" />
<startPadding launcher:fixedSize="22dp" />
@@ -38,7 +38,7 @@
<workspaceSpec
launcher:maxAvailableSize="9999dp"
launcher:dimensionType="width">
- <cellSize launcher:ofRemainderSpace="0.25" />
+ <cellSize launcher:ofRemainderSpace="1" />
<endPadding launcher:fixedSize="22dp" />
<gutter launcher:fixedSize="16dp" />
<startPadding launcher:fixedSize="22dp" />
diff --git a/tests/res/xml/valid_responsive_spec_unsorted.xml b/tests/res/xml/valid_responsive_spec_unsorted.xml
index 9a463d5..8676f48 100644
--- a/tests/res/xml/valid_responsive_spec_unsorted.xml
+++ b/tests/res/xml/valid_responsive_spec_unsorted.xml
@@ -22,7 +22,7 @@
<startPadding launcher:fixedSize="0dp" />
<endPadding launcher:fixedSize="34dp" />
<gutter launcher:fixedSize="12dp" />
- <cellSize launcher:ofRemainderSpace="0.25" />
+ <cellSize launcher:ofRemainderSpace="1" />
</workspaceSpec>
<!-- Height spec -->
@@ -32,7 +32,7 @@
<startPadding launcher:fixedSize="0dp" />
<endPadding launcher:fixedSize="24dp" />
<gutter launcher:fixedSize="12dp" />
- <cellSize launcher:ofRemainderSpace="0.25" />
+ <cellSize launcher:ofRemainderSpace="1" />
</workspaceSpec>
<!-- Width spec -->
@@ -42,7 +42,7 @@
<startPadding launcher:fixedSize="16dp" />
<endPadding launcher:fixedSize="64dp" />
<gutter launcher:fixedSize="12dp" />
- <cellSize launcher:ofRemainderSpace="0.25" />
+ <cellSize launcher:ofRemainderSpace="1" />
</workspaceSpec>
<workspaceSpec
launcher:dimensionType="width"
@@ -50,7 +50,7 @@
<startPadding launcher:fixedSize="36dp" />
<endPadding launcher:fixedSize="80dp" />
<gutter launcher:fixedSize="12dp" />
- <cellSize launcher:ofRemainderSpace="0.25" />
+ <cellSize launcher:ofRemainderSpace="1" />
</workspaceSpec>
<workspaceSpec
launcher:dimensionType="width"
@@ -58,7 +58,7 @@
<startPadding launcher:fixedSize="0dp" />
<endPadding launcher:fixedSize="36dp" />
<gutter launcher:fixedSize="12dp" />
- <cellSize launcher:ofRemainderSpace="0.25" />
+ <cellSize launcher:ofRemainderSpace="1" />
</workspaceSpec>
</specs>
@@ -71,7 +71,7 @@
<startPadding launcher:fixedSize="2dp" />
<endPadding launcher:fixedSize="2dp" />
<gutter launcher:fixedSize="8dp" />
- <cellSize launcher:ofRemainderSpace="0.25" />
+ <cellSize launcher:ofRemainderSpace="1" />
</workspaceSpec>
<!-- Width spec -->
@@ -81,7 +81,7 @@
<startPadding launcher:fixedSize="1dp" />
<endPadding launcher:fixedSize="1dp" />
<gutter launcher:fixedSize="8dp" />
- <cellSize launcher:ofRemainderSpace="0.25" />
+ <cellSize launcher:ofRemainderSpace="1" />
</workspaceSpec>
</specs>
@@ -122,7 +122,7 @@
<startPadding launcher:fixedSize="22dp" />
<endPadding launcher:fixedSize="22dp" />
<gutter launcher:fixedSize="16dp" />
- <cellSize launcher:ofRemainderSpace="0.25" />
+ <cellSize launcher:ofRemainderSpace="1" />
</workspaceSpec>
</specs>
</workspaceSpecs>
diff --git a/tests/res/xml/valid_workspace_file.xml b/tests/res/xml/valid_workspace_file.xml
index 9c44502..dc9963a 100644
--- a/tests/res/xml/valid_workspace_file.xml
+++ b/tests/res/xml/valid_workspace_file.xml
@@ -54,7 +54,7 @@
<startPadding launcher:fixedSize="22dp" />
<endPadding launcher:fixedSize="22dp" />
<gutter launcher:fixedSize="16dp" />
- <cellSize launcher:ofRemainderSpace="0.25" />
+ <cellSize launcher:ofRemainderSpace="1" />
</workspaceSpec>
</specs>
@@ -67,7 +67,7 @@
<startPadding launcher:fixedSize="0dp" />
<endPadding launcher:fixedSize="24dp" />
<gutter launcher:fixedSize="12dp" />
- <cellSize launcher:ofRemainderSpace="0.25" />
+ <cellSize launcher:ofRemainderSpace="1" />
</workspaceSpec>
<workspaceSpec
launcher:dimensionType="height"
@@ -75,7 +75,7 @@
<startPadding launcher:fixedSize="0dp" />
<endPadding launcher:fixedSize="34dp" />
<gutter launcher:fixedSize="12dp" />
- <cellSize launcher:ofRemainderSpace="0.25" />
+ <cellSize launcher:ofRemainderSpace="1" />
</workspaceSpec>
<!-- Width spec -->
@@ -85,7 +85,7 @@
<startPadding launcher:fixedSize="0dp" />
<endPadding launcher:fixedSize="36dp" />
<gutter launcher:fixedSize="12dp" />
- <cellSize launcher:ofRemainderSpace="0.25" />
+ <cellSize launcher:ofRemainderSpace="1" />
</workspaceSpec>
<workspaceSpec
launcher:dimensionType="width"
@@ -93,7 +93,7 @@
<startPadding launcher:fixedSize="16dp" />
<endPadding launcher:fixedSize="64dp" />
<gutter launcher:fixedSize="12dp" />
- <cellSize launcher:ofRemainderSpace="0.25" />
+ <cellSize launcher:ofRemainderSpace="1" />
</workspaceSpec>
<workspaceSpec
launcher:dimensionType="width"
@@ -101,7 +101,7 @@
<startPadding launcher:fixedSize="36dp" />
<endPadding launcher:fixedSize="80dp" />
<gutter launcher:fixedSize="12dp" />
- <cellSize launcher:ofRemainderSpace="0.25" />
+ <cellSize launcher:ofRemainderSpace="1" />
</workspaceSpec>
</specs>
</workspaceSpecs>
diff --git a/tests/res/xml/valid_workspace_unsorted_file.xml b/tests/res/xml/valid_workspace_unsorted_file.xml
index 6bf7c78..f783e9f 100644
--- a/tests/res/xml/valid_workspace_unsorted_file.xml
+++ b/tests/res/xml/valid_workspace_unsorted_file.xml
@@ -52,7 +52,7 @@
<startPadding launcher:fixedSize="22dp" />
<endPadding launcher:fixedSize="22dp" />
<gutter launcher:fixedSize="16dp" />
- <cellSize launcher:ofRemainderSpace="0.25" />
+ <cellSize launcher:ofRemainderSpace="1" />
</workspaceSpec>
</specs>
</workspaceSpecs>
diff --git a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
index fcb5158..ccbe382 100644
--- a/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
+++ b/tests/shared/com/android/launcher3/testing/shared/TestProtocol.java
@@ -147,6 +147,8 @@
public static final String REQUEST_GET_GRID_TASK_SIZE_RECT_FOR_TABLET =
"get-grid-task-size-rect-for-tablet";
public static final String REQUEST_GET_OVERVIEW_PAGE_SPACING = "get-overview-page-spacing";
+ public static final String REQUEST_GET_OVERVIEW_CURRENT_PAGE_INDEX =
+ "get-overview-current-page-index";
public static final String REQUEST_ENABLE_ROTATION = "enable_rotation";
public static final String REQUEST_ENABLE_SUGGESTION = "enable-suggestion";
public static final String REQUEST_MODEL_QUEUE_CLEARED = "model-queue-cleared";
@@ -172,6 +174,9 @@
public static final String REQUEST_FLAG_ENABLE_GRID_ONLY_OVERVIEW = "enable-grid-only-overview";
+ public static final String REQUEST_UNSTASH_BUBBLE_BAR_IF_STASHED =
+ "unstash-bubble-bar-if-stashed";
+
/** Logs {@link Log#d(String, String)} if {@link #sDebugTracing} is true. */
public static void testLogD(String tag, String message) {
if (!sDebugTracing) {
diff --git a/tests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java b/tests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java
index 259f519..3068785 100644
--- a/tests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java
+++ b/tests/src/com/android/launcher3/allapps/AlphabeticalAppsListTest.java
@@ -19,6 +19,9 @@
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_PRIVATE_SPACE_HEADER;
+import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_BOTTOM_LEFT;
+import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_BOTTOM_RIGHT;
+import static com.android.launcher3.allapps.SectionDecorationInfo.ROUND_NOTHING;
import static com.android.launcher3.allapps.UserProfileManager.STATE_DISABLED;
import static com.android.launcher3.allapps.UserProfileManager.STATE_ENABLED;
import static com.android.launcher3.allapps.UserProfileManager.STATE_TRANSITION;
@@ -61,6 +64,8 @@
private static final int PRIVATE_SPACE_HEADER_ITEM_COUNT = 1;
private static final int MAIN_USER_APP_COUNT = 2;
private static final int PRIVATE_USER_APP_COUNT = 1;
+ private static final int NUM_APP_COLS = 4;
+ private static final int NUM_APP_ROWS = 3;
private AlphabeticalAppsList<?> mAlphabeticalAppsList;
@Mock
@@ -81,6 +86,7 @@
info != null && info.user.equals(PRIVATE_HANDLE));
mAlphabeticalAppsList = new AlphabeticalAppsList<>(mContext, mAllAppsStore,
null, mPrivateProfileManager);
+ mAlphabeticalAppsList.setNumAppsPerRowAllApps(NUM_APP_COLS);
}
@Test
@@ -182,6 +188,94 @@
.toList().size());
}
+ @Test
+ public void getRoundRegions_whenIndexIsMiddleOfLastRow_roundNothing() {
+ int index = 3;
+
+ int roundRegions = mAlphabeticalAppsList.getRoundRegions(index,
+ NUM_APP_COLS * NUM_APP_ROWS);
+
+ assertEquals(ROUND_NOTHING, roundRegions);
+ }
+
+ @Test
+ public void getRoundRegions_whenIndexIsInEndOfLastRow_roundBottomRight() {
+ int index = 11;
+
+ int roundRegions = mAlphabeticalAppsList.getRoundRegions(index,
+ NUM_APP_COLS * NUM_APP_ROWS);
+
+ assertEquals(ROUND_BOTTOM_RIGHT, roundRegions);
+ }
+
+ @Test
+ public void getRoundRegions_whenIndexIsInBeginningOfLastRow_roundBottomLeft() {
+ int index = 8;
+
+ int roundRegions = mAlphabeticalAppsList.getRoundRegions(index,
+ NUM_APP_COLS * NUM_APP_ROWS);
+
+ assertEquals(ROUND_BOTTOM_LEFT, roundRegions);
+ }
+
+ @Test
+ public void getRoundRegions_whenIndexIsInMiddleOfLastRow_roundNothing() {
+ int index = 9;
+
+ int roundRegions = mAlphabeticalAppsList.getRoundRegions(index,
+ NUM_APP_COLS * NUM_APP_ROWS);
+
+ assertEquals(ROUND_NOTHING, roundRegions);
+ }
+
+ @Test
+ public void getRoundRegions_whenIndexIsInMiddleRow_roundNothing() {
+ int index = 5;
+
+ int roundRegions = mAlphabeticalAppsList.getRoundRegions(index,
+ NUM_APP_COLS * NUM_APP_ROWS);
+
+ assertEquals(ROUND_NOTHING, roundRegions);
+ }
+
+ @Test
+ public void getRoundRegions_whenIndexIsInBeginningOfTopRow_roundNothing() {
+ int index = 0;
+
+ int roundRegions = mAlphabeticalAppsList.getRoundRegions(index,
+ NUM_APP_COLS * NUM_APP_ROWS);
+
+ assertEquals(ROUND_NOTHING, roundRegions);
+ }
+
+ @Test
+ public void getRoundRegions_whenIndexIsInLastOfTopRow_roundNothing() {
+ int index = 3;
+
+ int roundRegions = mAlphabeticalAppsList.getRoundRegions(index,
+ NUM_APP_COLS * NUM_APP_ROWS);
+
+ assertEquals(ROUND_NOTHING, roundRegions);
+ }
+
+ @Test
+ public void getRoundRegions_whenIndexIsInMiddleOfLastRowLastItem_roundBottomRight() {
+ int index = 9;
+
+ int roundRegions = mAlphabeticalAppsList.getRoundRegions(index, index+1);
+
+ assertEquals(ROUND_BOTTOM_RIGHT, roundRegions);
+ }
+
+ @Test
+ public void getRoundRegions_whenIndexIsInBeginningOfLastRowLastItem_roundBottomRight() {
+ int index = 8;
+
+ int roundRegions = mAlphabeticalAppsList.getRoundRegions(index, index+1);
+
+ assertEquals(ROUND_BOTTOM_RIGHT | ROUND_BOTTOM_LEFT, roundRegions);
+ }
+
private int addPrivateSpaceHeader(List<BaseAllAppsAdapter.AdapterItem> adapterItemList) {
adapterItemList.add(new BaseAllAppsAdapter.AdapterItem(VIEW_TYPE_PRIVATE_SPACE_HEADER));
return adapterItemList.size();
diff --git a/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java b/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java
index 92ff355..bc4c16e 100644
--- a/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java
+++ b/tests/src/com/android/launcher3/allapps/TaplOpenCloseAllAppsTest.java
@@ -181,10 +181,10 @@
executeOnLauncher(launcher -> assertTrue("flingBackward() didn't scroll App Apps",
flingBackwardY < flingForwardY));
- // Test scrolling down to YouTube.
- assertNotNull("All apps: can't find YouTube", allApps.getAppIcon("YouTube"));
- // Test scrolling up to Camera.
- assertNotNull("All apps: can't find Camera", allApps.getAppIcon("Camera"));
+ // Test scrolling down to the end of the app list.
+ assertNotNull("All apps: can't find YouTube", allApps.getAppIcon("ZZZ"));
+ // Test scrolling up to the beginning oof the app list.
+ assertNotNull("All apps: can't find Camera", allApps.getAppIcon("AAA"));
// Test failing to find a non-existing app.
final AllApps allAppsFinal = allApps;
expectFail("All apps: could find a non-existing app",
diff --git a/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java b/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
index fb364ad..dbb2715 100644
--- a/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
+++ b/tests/src/com/android/launcher3/celllayout/FavoriteItemsTransaction.java
@@ -64,7 +64,7 @@
runOnExecutorSync(MODEL_EXECUTOR, () -> {
ModelDbController controller = model.getModelDbController();
// Migrate any previous data so that the DB state is correct
- controller.tryMigrateDB();
+ controller.tryMigrateDB(null /* restoreEventLogger */);
// Create DB again to load fresh data
controller.createEmptyDB();
diff --git a/tests/src/com/android/launcher3/compat/TaplPromiseIconUiTest.java b/tests/src/com/android/launcher3/compat/TaplPromiseIconUiTest.java
index 8200c94..bfa0d34 100644
--- a/tests/src/com/android/launcher3/compat/TaplPromiseIconUiTest.java
+++ b/tests/src/com/android/launcher3/compat/TaplPromiseIconUiTest.java
@@ -15,9 +15,19 @@
*/
package com.android.launcher3.compat;
+import static com.android.launcher3.Flags.FLAG_ENABLE_SUPPORT_FOR_ARCHIVING;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.text.TextUtils;
import androidx.test.filters.LargeTest;
@@ -27,12 +37,15 @@
import com.android.launcher3.LauncherState;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.util.LauncherBindableItemsContainer.ItemOperator;
+import com.android.launcher3.util.TestUtil;
import com.android.launcher3.util.rule.ViewCaptureRule;
import org.junit.After;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.io.IOException;
import java.util.UUID;
@@ -43,6 +56,14 @@
@RunWith(AndroidJUnit4.class)
public class TaplPromiseIconUiTest extends AbstractLauncherUiTest {
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ public static final String PACKAGE_NAME = "test.promise.app";
+ public static final String DUMMY_PACKAGE = "com.example.android.aardwolf";
+ public static final String DUMMY_LABEL = "Aardwolf";
+
private int mSessionId = -1;
@Override
@@ -55,18 +76,19 @@
}
@After
- public void tearDown() {
+ public void tearDown() throws IOException {
if (mSessionId > -1) {
mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId);
}
+ TestUtil.uninstallDummyApp();
}
/**
* Create a session and return the id.
*/
- private int createSession(String label, Bitmap icon) throws Throwable {
+ private int createSession(String packageName, String label, Bitmap icon) throws Throwable {
SessionParams params = new SessionParams(SessionParams.MODE_FULL_INSTALL);
- params.setAppPackageName("test.promise.app");
+ params.setAppPackageName(packageName);
params.setAppLabel(label);
params.setAppIcon(icon);
params.setInstallReason(PackageManager.INSTALL_REASON_USER);
@@ -80,7 +102,8 @@
info != null && TextUtils.equals(info.title, appLabel);
// Create and add test session
- mSessionId = createSession(appLabel, Bitmap.createBitmap(100, 100, Bitmap.Config.ALPHA_8));
+ mSessionId = createSession(PACKAGE_NAME, appLabel,
+ Bitmap.createBitmap(100, 100, Bitmap.Config.ALPHA_8));
// Verify promise icon is added
waitForLauncherCondition("Test Promise App not found on workspace", launcher ->
@@ -103,7 +126,7 @@
info != null && TextUtils.equals(info.title, appLabel);
// Create and add test session without icon or label
- mSessionId = createSession(null, null);
+ mSessionId = createSession(PACKAGE_NAME, null, null);
// Sleep for duration of animation if a view was to be added + some buffer time.
Thread.sleep(Launcher.NEW_APPS_PAGE_MOVE_DELAY + Launcher.NEW_APPS_ANIMATION_DELAY + 500);
@@ -112,4 +135,40 @@
waitForLauncherCondition("Test Promise App not found on workspace", launcher ->
launcher.getWorkspace().getFirstMatch(findPromiseApp) == null);
}
+
+ @Test
+ @RequiresFlagsEnabled(FLAG_ENABLE_SUPPORT_FOR_ARCHIVING)
+ public void testPromiseIcon_addedArchivedApp() throws Throwable {
+ installDummyAppAndWaitForUIUpdate();
+ assertThat(mDevice.executeShellCommand(String.format("pm archive %s", DUMMY_PACKAGE)))
+ .isEqualTo("Success\n");
+
+ final ItemOperator findPromiseApp = (info, view) ->
+ info != null && TextUtils.equals(info.title, DUMMY_LABEL);
+
+ // Create and add test session
+ mSessionId = createSession(DUMMY_PACKAGE, /* label= */ "",
+ Bitmap.createBitmap(100, 100, Bitmap.Config.ALPHA_8));
+
+ // Verify promise icon is added
+ waitForLauncherCondition("Test Promise App not found on workspace", launcher ->
+ launcher.getWorkspace().getFirstMatch(findPromiseApp) != null);
+
+ // Remove session
+ mTargetContext.getPackageManager().getPackageInstaller().abandonSession(mSessionId);
+ mSessionId = -1;
+ }
+
+ // Dummy receiver to fulfill archiving platform requirements, unused in reality.
+ public static class UnarchiveBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ }
+ }
+
+ private void installDummyAppAndWaitForUIUpdate() throws IOException {
+ TestUtil.installDummyApp();
+ mLauncher.waitForModelQueueCleared();
+ mLauncher.waitForLauncherInitialized();
+ }
}
diff --git a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
index 10d9133..733f1e9 100644
--- a/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
+++ b/tests/src/com/android/launcher3/provider/RestoreDbTaskTest.java
@@ -59,6 +59,7 @@
import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.LauncherSettings.Favorites;
+import com.android.launcher3.backuprestore.LauncherRestoreEventLogger;
import com.android.launcher3.model.ModelDbController;
import com.android.launcher3.util.IntArray;
import com.android.launcher3.util.LauncherModelHelper;
@@ -90,6 +91,7 @@
private SQLiteDatabase mMockDb;
private Cursor mMockCursor;
private LauncherPrefs mPrefs;
+ private LauncherRestoreEventLogger mMockRestoreEventLogger;
@Before
public void setup() {
@@ -100,6 +102,7 @@
mMockDb = mock(SQLiteDatabase.class);
mMockCursor = mock(Cursor.class);
mPrefs = new LauncherPrefs(mContext);
+ mMockRestoreEventLogger = mock(LauncherRestoreEventLogger.class);
}
@After
@@ -178,7 +181,7 @@
assertEquals(10, getItemCountForProfile(db, myProfileId_old));
assertEquals(6, getItemCountForProfile(db, workProfileId_old));
- mTask.sanitizeDB(mContext, controller, controller.getDb(), bm);
+ mTask.sanitizeDB(mContext, controller, controller.getDb(), bm, mMockRestoreEventLogger);
// All the data has been migrated to the new user ids
assertEquals(0, getItemCountForProfile(db, myProfileId_old));
@@ -207,7 +210,7 @@
assertEquals(10, getItemCountForProfile(db, myProfileId_old));
assertEquals(6, getItemCountForProfile(db, workProfileId_old));
- mTask.sanitizeDB(mContext, controller, controller.getDb(), bm);
+ mTask.sanitizeDB(mContext, controller, controller.getDb(), bm, mMockRestoreEventLogger);
// All the data has been migrated to the new user ids
assertEquals(0, getItemCountForProfile(db, myProfileId_old));
@@ -219,7 +222,7 @@
@Test
public void givenLauncherPrefsHasNoIds_whenRestoreAppWidgetIdsIfExists_thenIdsAreRemoved() {
// When
- mTask.restoreAppWidgetIdsIfExists(mContext, mMockController);
+ mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger);
// Then
assertThat(mPrefs.has(OLD_APP_WIDGET_IDS, APP_WIDGET_IDS)).isFalse();
}
@@ -235,7 +238,7 @@
// When
setRestoredAppWidgetIds(mContext, expectedOldIds, expectedNewIds);
- mTask.restoreAppWidgetIdsIfExists(mContext, mMockController);
+ mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger);
// Then
assertThat(expectedHost.getAppWidgetIds()).isEqualTo(expectedOldIds);
@@ -257,7 +260,7 @@
// When
setRestoredAppWidgetIds(mContext, expectedOldIds, expectedNewIds);
- mTask.restoreAppWidgetIdsIfExists(mContext, mMockController);
+ mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger);
// Then
assertThat(expectedHost.getAppWidgetIds()).isEqualTo(expectedOldIds);
@@ -280,12 +283,13 @@
when(mMockDb.query(any(), any(), any(), any(), any(), any(), any()))
.thenReturn(mMockCursor);
when(mMockCursor.moveToFirst()).thenReturn(true);
+ when(mMockCursor.getColumnNames()).thenReturn(new String[] {});
when(mMockCursor.isAfterLast()).thenReturn(true);
RestoreDbTask.setPending(mContext);
// When
setRestoredAppWidgetIds(mContext, expectedOldIds, expectedNewIds);
- mTask.restoreAppWidgetIdsIfExists(mContext, mMockController);
+ mTask.restoreAppWidgetIdsIfExists(mContext, mMockController, mMockRestoreEventLogger);
// Then
assertThat(expectedHost.getAppWidgetIds()).isEqualTo(allExpectedIds);
diff --git a/tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt b/tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
index 9681ca8..54a1dc5 100644
--- a/tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
+++ b/tests/src/com/android/launcher3/responsive/ResponsiveSpecsProviderTest.kt
@@ -84,7 +84,7 @@
startPadding = SizeSpec(1f.dpToPx()),
endPadding = SizeSpec(1f.dpToPx()),
gutter = SizeSpec(8f.dpToPx()),
- cellSize = SizeSpec(ofRemainderSpace = .25f)
+ cellSize = SizeSpec(ofRemainderSpace = 1f)
)
)
@@ -97,7 +97,7 @@
startPadding = SizeSpec(2f.dpToPx()),
endPadding = SizeSpec(2f.dpToPx()),
gutter = SizeSpec(8f.dpToPx()),
- cellSize = SizeSpec(ofRemainderSpace = .25f)
+ cellSize = SizeSpec(ofRemainderSpace = 1f)
),
)
@@ -182,7 +182,7 @@
startPadding = SizeSpec(22f.dpToPx()),
endPadding = SizeSpec(22f.dpToPx()),
gutter = sizeSpec16,
- cellSize = SizeSpec(ofRemainderSpace = .25f)
+ cellSize = SizeSpec(ofRemainderSpace = 1f)
)
)
@@ -231,7 +231,7 @@
startPadding = SizeSpec(0f.dpToPx()),
endPadding = SizeSpec(36f.dpToPx()),
gutter = sizeSpec12,
- cellSize = SizeSpec(ofRemainderSpace = .25f)
+ cellSize = SizeSpec(ofRemainderSpace = 1f)
),
ResponsiveSpec(
maxAvailableSize = 716.dpToPx(),
@@ -240,7 +240,7 @@
startPadding = SizeSpec(16f.dpToPx()),
endPadding = SizeSpec(64f.dpToPx()),
gutter = sizeSpec12,
- cellSize = SizeSpec(ofRemainderSpace = .25f)
+ cellSize = SizeSpec(ofRemainderSpace = 1f)
),
ResponsiveSpec(
maxAvailableSize = 9999.dpToPx(),
@@ -249,7 +249,7 @@
startPadding = SizeSpec(36f.dpToPx()),
endPadding = SizeSpec(80f.dpToPx()),
gutter = sizeSpec12,
- cellSize = SizeSpec(ofRemainderSpace = .25f)
+ cellSize = SizeSpec(ofRemainderSpace = 1f)
)
)
@@ -262,7 +262,7 @@
startPadding = SizeSpec(0f),
endPadding = SizeSpec(24f.dpToPx()),
gutter = sizeSpec12,
- cellSize = SizeSpec(ofRemainderSpace = .25f)
+ cellSize = SizeSpec(ofRemainderSpace = 1f)
),
ResponsiveSpec(
maxAvailableSize = 9999.dpToPx(),
@@ -271,7 +271,7 @@
startPadding = SizeSpec(0f),
endPadding = SizeSpec(34f.dpToPx()),
gutter = sizeSpec12,
- cellSize = SizeSpec(ofRemainderSpace = .25f)
+ cellSize = SizeSpec(ofRemainderSpace = 1f)
),
)
diff --git a/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt b/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
index 9781645..17b0ee4 100644
--- a/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
+++ b/tests/src/com/android/launcher3/responsive/WorkspaceSpecsTest.kt
@@ -159,7 +159,7 @@
"maxSize=2147483647), " +
"cellSize=SizeSpec(fixedSize=0.0, " +
"ofAvailableSpace=0.0, " +
- "ofRemainderSpace=0.25, " +
+ "ofRemainderSpace=1.0, " +
"matchWorkspace=false, " +
"maxSize=2147483647)" +
")"
diff --git a/tests/src/com/android/launcher3/tapl/TaplUtilityTest.java b/tests/src/com/android/launcher3/tapl/TaplUtilityTest.java
index 4a1888a..b6ded97 100644
--- a/tests/src/com/android/launcher3/tapl/TaplUtilityTest.java
+++ b/tests/src/com/android/launcher3/tapl/TaplUtilityTest.java
@@ -23,16 +23,43 @@
public class TaplUtilityTest {
@Test
- public void testNewStringWithRegex() {
+ public void testMakeMultilinePattern() {
+ // Original title will match.
assertTrue(AppIcon.makeMultilinePattern("Play Store")
- .matcher("Play Store has 7 notifications").matches());
+ .matcher("Play Store").matches());
+ assertTrue(AppIcon.makeMultilinePattern("PlayStore")
+ .matcher("PlayStore").matches());
+
+ // Original title with whitespace added will match.
+ assertTrue(AppIcon.makeMultilinePattern("PlayStore")
+ .matcher("Play\nStore").matches());
+ assertTrue(AppIcon.makeMultilinePattern("PlayStore")
+ .matcher("Play Store").matches());
+ // Original title with whitespace removed will also match.
assertTrue(AppIcon.makeMultilinePattern("Play Store")
- .matcher("Play Store!").matches());
- assertFalse(AppIcon.makeMultilinePattern("Play Store")
- .matcher("play store").matches());
- assertFalse(AppIcon.makeMultilinePattern("Play Store")
- .matcher("").matches());
+ .matcher("PlayStore").matches());
+ // Or whitespace replaced with a different kind of whitespace (both of above conditions).
+ assertTrue(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("Play\nStore").matches());
assertTrue(AppIcon.makeMultilinePattern("Play Store")
.matcher("Play \n Store").matches());
+
+ // Any non-whitespace character added to the title will not match.
+ assertFalse(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("Play Store has 7 notifications").matches());
+ assertFalse(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("Play Store!").matches());
+ // Title is case-sensitive.
+ assertFalse(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("play store").matches());
+ assertFalse(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("play store").matches());
+ // Removing non whitespace characters will not match.
+ assertFalse(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("").matches());
+ assertFalse(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("Play Stor").matches());
+ assertFalse(AppIcon.makeMultilinePattern("Play Store")
+ .matcher("Play").matches());
}
}
diff --git a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
index f818564..cb30854 100644
--- a/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
+++ b/tests/src/com/android/launcher3/ui/TaplWorkProfileTest.java
@@ -19,7 +19,10 @@
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.allapps.AllAppsStore.DEFER_UPDATES_TEST;
+import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
import static com.android.launcher3.util.TestUtil.installDummyAppForUser;
+import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
+import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
@@ -39,12 +42,13 @@
import com.android.launcher3.allapps.WorkProfileManager;
import com.android.launcher3.tapl.LauncherInstrumentation;
import com.android.launcher3.util.TestUtil;
+import com.android.launcher3.util.rule.TestStabilityRule;
import org.junit.After;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
+import java.io.IOException;
import java.util.Objects;
import java.util.function.Predicate;
@@ -98,7 +102,17 @@
launcher.getAppsView().getAppsStore().disableDeferUpdates(DEFER_UPDATES_TEST);
});
TestUtil.uninstallDummyApp();
- mDevice.executeShellCommand("pm remove-user " + mProfileUserId);
+
+ mLauncher.runToState(
+ () -> {
+ try {
+ mDevice.executeShellCommand("pm remove-user " + mProfileUserId);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ },
+ NORMAL_STATE_ORDINAL,
+ "executing pm 'remove-user' command");
}
private void waitForWorkTabSetup() {
@@ -123,8 +137,10 @@
LauncherInstrumentation.WAIT_TIME_MS);
}
+ // Staging; will be promoted to presubmit if stable
+ @TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
+
@Test
- @Ignore("b/243855320")
public void toggleWorks() {
assumeTrue(mWorkProfileSetupSuccessful);
waitForWorkTabSetup();
diff --git a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
index d75b387..db38c68 100644
--- a/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/TaplAddWidgetTest.java
@@ -18,7 +18,6 @@
import static org.junit.Assert.assertNotNull;
import android.platform.test.annotations.PlatinumTest;
-import android.platform.test.rule.ScreenRecordRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.LargeTest;
@@ -29,9 +28,11 @@
import com.android.launcher3.ui.AbstractLauncherUiTest;
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.ui.TestViewHelpers;
+import com.android.launcher3.util.rule.ScreenRecordRule.ScreenRecord;
import com.android.launcher3.util.rule.ShellCommandRule;
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
+import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
@@ -47,9 +48,16 @@
@Rule
public ShellCommandRule mGrantWidgetRule = ShellCommandRule.grantWidgetBind();
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ AbstractLauncherUiTest.initialize(this);
+ }
+
@Test
@PortraitLandscape
- @ScreenRecordRule.ScreenRecord // b/289161193
+ @ScreenRecord // b/316910614
public void testDragIcon() throws Throwable {
mLauncher.enableDebugTracing(); // b/289161193
new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
@@ -99,6 +107,7 @@
*/
@PlatinumTest(focusArea = "launcher")
@Test
+ @ScreenRecord // b/316910614
public void testResizeWidget() throws Throwable {
new FavoriteItemsTransaction(mTargetContext).commitAndLoadHome(mLauncher);
diff --git a/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
index 59c82a7..4edeb42 100644
--- a/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
+++ b/tests/src/com/android/launcher3/ui/workspace/TaplWorkspaceTest.java
@@ -15,7 +15,6 @@
*/
package com.android.launcher3.ui.workspace;
-import static com.android.launcher3.ui.AbstractLauncherUiTest.initialize;
import static com.android.launcher3.util.TestConstants.AppNames.CHROME_APP_NAME;
import static org.junit.Assert.assertEquals;
@@ -23,8 +22,6 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import android.platform.test.annotations.PlatinumTest;
-
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
import com.android.launcher3.tapl.HomeAppIcon;
@@ -70,7 +67,6 @@
* move between workspaces. After, make sure we can launch an app from the Workspace.
* @throws Exception if we can't set the defaults icons that will appear at the beginning.
*/
- @PlatinumTest(focusArea = "launcher")
@Test
public void testWorkspace() throws Exception {
// Set workspace that includes the chrome Activity app icon on the hotseat.
@@ -123,7 +119,6 @@
* Similar to {@link TaplWorkspaceTest#testWorkspace} but here we also make sure we can delete
* the pages.
*/
- @PlatinumTest(focusArea = "launcher")
@Test
public void testAddAndDeletePageAndFling() {
Workspace workspace = mLauncher.getWorkspace();
diff --git a/tests/src/com/android/launcher3/util/ExecutorRunnableTest.kt b/tests/src/com/android/launcher3/util/ExecutorRunnableTest.kt
index c9d118f..972b592 100644
--- a/tests/src/com/android/launcher3/util/ExecutorRunnableTest.kt
+++ b/tests/src/com/android/launcher3/util/ExecutorRunnableTest.kt
@@ -16,7 +16,7 @@
package com.android.launcher3.util
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.util.rule.TestStabilityRule
import java.util.concurrent.ExecutorService
@@ -30,7 +30,7 @@
/** Unit test for [ExecutorRunnable] */
@SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RunWith(AndroidJUnit4::class)
class ExecutorRunnableTest {
private lateinit var underTest: ExecutorRunnable<Int>
@@ -63,8 +63,8 @@
fun run_and_complete() {
awaitAllExecutorCompleted()
- assertTrue(isTaskExecuted)
- assertTrue(isCallbackExecuted)
+ assertTrue("task should be executed", isTaskExecuted)
+ assertTrue("callback should be executed", isCallbackExecuted)
assertEquals(2, result)
}
@@ -76,7 +76,7 @@
underTest.cancel(false)
awaitAllExecutorCompleted()
- assertFalse(isCallbackExecuted)
+ assertFalse("callback should not be executed.", isCallbackExecuted)
assertEquals(0, result)
}
@@ -86,8 +86,8 @@
underTest.cancel(false)
- assertTrue(isTaskExecuted)
- assertTrue(isCallbackExecuted)
+ assertTrue("task should be executed", isTaskExecuted)
+ assertTrue("callback should be executed", isCallbackExecuted)
assertEquals(2, result)
}
diff --git a/tests/src/com/android/launcher3/util/ModelTestExtensions.kt b/tests/src/com/android/launcher3/util/ModelTestExtensions.kt
index 61ec669..6bd182b 100644
--- a/tests/src/com/android/launcher3/util/ModelTestExtensions.kt
+++ b/tests/src/com/android/launcher3/util/ModelTestExtensions.kt
@@ -1,7 +1,27 @@
package com.android.launcher3.util
+import android.content.ContentValues
import com.android.launcher3.LauncherModel
+import com.android.launcher3.LauncherSettings.Favorites
+import com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_ID
+import com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_PROVIDER
+import com.android.launcher3.LauncherSettings.Favorites.APPWIDGET_SOURCE
+import com.android.launcher3.LauncherSettings.Favorites.CELLX
+import com.android.launcher3.LauncherSettings.Favorites.CELLY
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER
+import com.android.launcher3.LauncherSettings.Favorites.CONTAINER_DESKTOP
+import com.android.launcher3.LauncherSettings.Favorites.INTENT
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE
+import com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION
+import com.android.launcher3.LauncherSettings.Favorites.PROFILE_ID
+import com.android.launcher3.LauncherSettings.Favorites.RESTORED
+import com.android.launcher3.LauncherSettings.Favorites.SCREEN
+import com.android.launcher3.LauncherSettings.Favorites.SPANX
+import com.android.launcher3.LauncherSettings.Favorites.SPANY
+import com.android.launcher3.LauncherSettings.Favorites.TITLE
+import com.android.launcher3.LauncherSettings.Favorites._ID
import com.android.launcher3.model.BgDataModel
+import com.android.launcher3.model.ModelDbController
object ModelTestExtensions {
/** Clears and reloads Launcher db to cleanup the workspace */
@@ -10,7 +30,7 @@
loadModelSync()
TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
modelDbController.run {
- tryMigrateDB()
+ tryMigrateDB(null /* restoreEventLogger */)
createEmptyDB()
clearEmptyDbFlag()
}
@@ -20,6 +40,7 @@
loadModelSync()
}
+ /** Loads the model in memory synchronously */
fun LauncherModel.loadModelSync() {
val mockCb: BgDataModel.Callbacks = object : BgDataModel.Callbacks {}
TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) { addCallbacksAndLoad(mockCb) }
@@ -27,4 +48,54 @@
TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) {}
TestUtil.runOnExecutorSync(Executors.MAIN_EXECUTOR) { removeCallbacks(mockCb) }
}
+
+ /** Adds and commits a new item to Launcher.db */
+ fun LauncherModel.addItem(
+ title: String = "LauncherTestApp",
+ intent: String =
+ "#Intent;action=android.intent.action.MAIN;category=android.intent.category.LAUNCHER;component=com.google.android.apps.nexuslauncher.tests/com.android.launcher3.testcomponent.BaseTestingActivity;launchFlags=0x10200000;end",
+ type: Int = ITEM_TYPE_APPLICATION,
+ restoreFlags: Int = 0,
+ screen: Int = 0,
+ container: Int = CONTAINER_DESKTOP,
+ x: Int,
+ y: Int,
+ spanX: Int = 1,
+ spanY: Int = 1,
+ id: Int = 0,
+ profileId: Int = 0,
+ tableName: String = Favorites.TABLE_NAME,
+ appWidgetId: Int = -1,
+ appWidgetSource: Int = -1,
+ appWidgetProvider: String? = null
+ ) {
+ loadModelSync()
+ TestUtil.runOnExecutorSync(Executors.MODEL_EXECUTOR) {
+ val controller: ModelDbController = modelDbController
+ controller.tryMigrateDB(null /* restoreEventLogger */)
+ modelDbController.newTransaction().use { transaction ->
+ val values =
+ ContentValues().apply {
+ values[_ID] = id
+ values[TITLE] = title
+ values[PROFILE_ID] = profileId
+ values[CONTAINER] = container
+ values[SCREEN] = screen
+ values[CELLX] = x
+ values[CELLY] = y
+ values[SPANX] = spanX
+ values[SPANY] = spanY
+ values[ITEM_TYPE] = type
+ values[RESTORED] = restoreFlags
+ values[INTENT] = intent
+ values[APPWIDGET_ID] = appWidgetId
+ values[APPWIDGET_SOURCE] = appWidgetSource
+ values[APPWIDGET_PROVIDER] = appWidgetProvider
+ }
+ // Migrate any previous data so that the DB state is correct
+ controller.insert(tableName, values)
+ transaction.commit()
+ }
+ }
+ }
}
diff --git a/tests/src/com/android/launcher3/util/TestUtil.java b/tests/src/com/android/launcher3/util/TestUtil.java
index 683f323..95444ba 100644
--- a/tests/src/com/android/launcher3/util/TestUtil.java
+++ b/tests/src/com/android/launcher3/util/TestUtil.java
@@ -103,7 +103,9 @@
out.close();
final String result = UiDevice.getInstance(instrumentation)
- .executeShellCommand("pm install --user " + userId + " " + apkFilename);
+ .executeShellCommand(String.format("pm install -i %s --user ",
+ instrumentation.getContext().getPackageName())
+ + userId + " " + apkFilename);
Assert.assertTrue(
"Failed to install wellbeing test apk; make sure the device is rooted",
"Success".equals(result.replaceAll("\\s+", "")));
diff --git a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
index b51045f..909aabd 100644
--- a/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
+++ b/tests/src/com/android/launcher3/util/rule/TestStabilityRule.java
@@ -83,6 +83,7 @@
public static int getRunFlavor() {
if (sRunFlavor != 0) return sRunFlavor;
+ if (isRobolectricTest()) return PLATFORM_POSTSUBMIT;
final String flavorOverride = InstrumentationRegistry.getArguments().getString("flavor");
@@ -150,4 +151,8 @@
public static boolean isPresubmit() {
return getRunFlavor() == PLATFORM_PRESUBMIT;
}
+
+ public static boolean isRobolectricTest() {
+ return Build.FINGERPRINT.contains("robolectric");
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/AllApps.java b/tests/tapl/com/android/launcher3/tapl/AllApps.java
index 0e78565..f057f56 100644
--- a/tests/tapl/com/android/launcher3/tapl/AllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/AllApps.java
@@ -21,6 +21,7 @@
import static com.android.launcher3.tapl.LauncherInstrumentation.DEFAULT_POLL_INTERVAL;
import static com.android.launcher3.tapl.LauncherInstrumentation.WAIT_TIME_MS;
+import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
import android.graphics.Point;
import android.graphics.Rect;
@@ -375,7 +376,7 @@
? mLauncher.waitForLauncherObject(FAST_SCROLLER_RES_ID) :
mLauncher.waitForLauncherObject(BOTTOM_SHEET_RES_ID);
- mLauncher.touchOutsideContainer(container, tapRight, false);
+ touchOutside(tapRight, container);
try (LauncherInstrumentation.Closable tapped = mLauncher.addContextLayer(
"tapped outside AllApps bottom sheet")) {
verifyVisibleContainerOnDismiss();
@@ -383,6 +384,10 @@
}
}
+ protected void touchOutside(boolean tapRight, UiObject2 container) {
+ mLauncher.touchOutsideContainer(container, tapRight, false);
+ }
+
/** Presses the meta keyboard shortcut to dismiss AllApps. */
public void dismissByKeyboardShortcut() {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
@@ -399,7 +404,10 @@
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_DOWN);
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_UP);
- mLauncher.getDevice().pressKeyCode(KEYCODE_ESCAPE);
+ mLauncher.runToState(
+ () -> mLauncher.getDevice().pressKeyCode(KEYCODE_ESCAPE),
+ NORMAL_STATE_ORDINAL,
+ "pressing esc key");
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"pressed esc key")) {
verifyVisibleContainerOnDismiss();
diff --git a/tests/tapl/com/android/launcher3/tapl/AppIcon.java b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
index 85098c8..867a1a8 100644
--- a/tests/tapl/com/android/launcher3/tapl/AppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/AppIcon.java
@@ -36,9 +36,23 @@
super(launcher, icon);
}
+ /**
+ * Find an app icon with the given name.
+ *
+ * @param appName app icon to look for
+ */
+ static BySelector getAppIconSelector(String appName) {
+ return By.clazz(TextView.class).text(makeMultilinePattern(appName));
+ }
+
+ /**
+ * Find an app icon with the given name.
+ *
+ * @param appName app icon to look for
+ * @param launcher (optional) - only match ui elements from Launcher's package
+ */
static BySelector getAppIconSelector(String appName, LauncherInstrumentation launcher) {
- return By.clazz(TextView.class).desc(makeMultilinePattern(appName))
- .pkg(launcher.getLauncherPackageName());
+ return getAppIconSelector(appName).pkg(launcher.getLauncherPackageName());
}
static BySelector getMenuItemSelector(String text, LauncherInstrumentation launcher) {
@@ -109,13 +123,16 @@
}
/**
- * Create a regular expression pattern that matches strings starting with the app name, where
- * spaces in the app name are replaced with zero or more occurrences of the "\s" character
- * (which represents a whitespace character in regular expressions), followed by any characters
- * after the app name.
+ * Create a regular expression pattern that matches strings containing all of the non-whitespace
+ * characters of the app name, with any amount of whitespace added between characters (e.g.
+ * newline for multiline app labels).
*/
static Pattern makeMultilinePattern(String appName) {
- return Pattern.compile(appName.replaceAll("\\s+", "\\\\s*") + ".*",
- Pattern.DOTALL);
+ // Remove any existing whitespace.
+ appName = appName.replaceAll("\\s", "");
+ // Allow whitespace between characters, e.g. newline for 2 line app label.
+ StringBuilder regexBuldier = new StringBuilder("\\s*");
+ appName.chars().forEach(letter -> regexBuldier.append((char) letter).append("\\s*"));
+ return Pattern.compile(regexBuldier.toString());
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
index b6b4a47..2013ce4 100644
--- a/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
+++ b/tests/tapl/com/android/launcher3/tapl/BaseOverview.java
@@ -16,7 +16,10 @@
package com.android.launcher3.tapl;
+import static android.view.KeyEvent.KEYCODE_ESCAPE;
+
import static com.android.launcher3.tapl.LauncherInstrumentation.TASKBAR_RES_ID;
+import static com.android.launcher3.testing.shared.TestProtocol.NORMAL_STATE_ORDINAL;
import android.graphics.Rect;
@@ -27,15 +30,24 @@
import androidx.test.uiautomator.Direction;
import androidx.test.uiautomator.UiObject2;
+import com.android.launcher3.testing.shared.TestProtocol;
+
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
/**
* Common overview panel for both Launcher and fallback recents
*/
public class BaseOverview extends LauncherInstrumentation.VisibleContainer {
+
+ private static final Pattern EVENT_ALT_ESC_DOWN = Pattern.compile(
+ "Key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_ESCAPE.*?metaState=0");
+ private static final Pattern EVENT_ALT_ESC_UP = Pattern.compile(
+ "Key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_ESCAPE.*?metaState=0");
+
private static final int FLINGS_FOR_DISMISS_LIMIT = 40;
BaseOverview(LauncherInstrumentation launcher) {
@@ -128,8 +140,19 @@
flingForwardImpl();
}
- mLauncher.clickLauncherObject(
- mLauncher.waitForObjectInContainer(verifyActiveContainer(), clearAllSelector));
+ final Runnable clickClearAll = () -> mLauncher.clickLauncherObject(
+ mLauncher.waitForObjectInContainer(verifyActiveContainer(),
+ clearAllSelector));
+ if (mLauncher.is3PLauncher()) {
+ mLauncher.executeAndWaitForLauncherStop(
+ clickClearAll,
+ "clicking 'Clear All'");
+ } else {
+ mLauncher.runToState(
+ clickClearAll,
+ NORMAL_STATE_ORDINAL,
+ "clicking 'Clear All'");
+ }
mLauncher.waitUntilLauncherObjectGone(clearAllSelector);
}
@@ -367,6 +390,23 @@
return !task.isTaskSplit();
}
+ /**
+ * Presses the esc key to dismiss Overview.
+ */
+ public Workspace dismissByEscKey() {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_DOWN);
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_UP);
+ mLauncher.runToState(
+ () -> mLauncher.getDevice().pressKeyCode(KEYCODE_ESCAPE),
+ NORMAL_STATE_ORDINAL, "pressing esc key");
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "pressed esc key")) {
+ return mLauncher.getWorkspace();
+ }
+ }
+ }
+
private void verifyActionsViewVisibility() {
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"want to assert overview actions view visibility")) {
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
index 9ca2dc8..11a0cfb 100644
--- a/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
+++ b/tests/tapl/com/android/launcher3/tapl/HomeAllApps.java
@@ -131,4 +131,12 @@
return new Workspace(mLauncher);
}
}
+
+ @Override
+ protected void touchOutside(boolean tapRight, UiObject2 container) {
+ mLauncher.runToState(
+ () -> super.touchOutside(tapRight, container),
+ NORMAL_STATE_ORDINAL,
+ "touching outside");
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/HomeQsb.java b/tests/tapl/com/android/launcher3/tapl/HomeQsb.java
index 5385c65..c1fc45f 100644
--- a/tests/tapl/com/android/launcher3/tapl/HomeQsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/HomeQsb.java
@@ -15,6 +15,8 @@
*/
package com.android.launcher3.tapl;
+import static com.android.launcher3.testing.shared.TestProtocol.ALL_APPS_STATE_ORDINAL;
+
import androidx.test.uiautomator.UiObject2;
/**
@@ -25,4 +27,13 @@
HomeQsb(LauncherInstrumentation launcher, UiObject2 hotseat) {
super(launcher, hotseat, "search_container_hotseat");
}
+
+ @Override
+ protected void clickQsb() {
+ // Clicking Qsb will switch to All Apps state.
+ mLauncher.runToState(
+ () -> super.clickQsb(),
+ ALL_APPS_STATE_ORDINAL,
+ "Clicking Qsb");
+ }
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Launchable.java b/tests/tapl/com/android/launcher3/tapl/Launchable.java
index fe927b3..28e2590 100644
--- a/tests/tapl/com/android/launcher3/tapl/Launchable.java
+++ b/tests/tapl/com/android/launcher3/tapl/Launchable.java
@@ -16,8 +16,6 @@
package com.android.launcher3.tapl;
-import static android.view.accessibility.AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED;
-
import static com.android.launcher3.testing.shared.TestProtocol.SPRING_LOADED_STATE_ORDINAL;
import android.graphics.Point;
@@ -97,12 +95,10 @@
LauncherInstrumentation.log("Launchable.launch before click "
+ mObject.getVisibleCenter() + " in " + mLauncher.getVisibleBounds(
mObject));
- mLauncher.executeAndWaitForLauncherEvent(
+
+ mLauncher.executeAndWaitForLauncherStop(
() -> mLauncher.clickLauncherObject(mObject),
- accessibilityEvent ->
- accessibilityEvent.getEventType() == TYPE_WINDOW_STATE_CHANGED,
- () -> "Unable to click object to launch split",
- "Click launcher object to launch split");
+ "clicking the launchable");
try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer("clicked")) {
mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, OverviewTask.SPLIT_START_EVENT);
diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
index edc6aac..d17f034 100644
--- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
+++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java
@@ -21,6 +21,7 @@
import static android.content.pm.PackageManager.MATCH_ALL;
import static android.content.pm.PackageManager.MATCH_DISABLED_COMPONENTS;
import static android.view.KeyEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_SCROLL;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.MotionEvent.AXIS_GESTURE_SWIPE_FINGER_COUNT;
@@ -277,21 +278,35 @@
assertNotNull("Cannot find content provider for " + testProviderAuthority, pi);
ComponentName cn = new ComponentName(pi.packageName, pi.name);
+ final int iterations = isLauncherTest ? 300 : 100;
+
if (pm.getComponentEnabledSetting(cn) != COMPONENT_ENABLED_STATE_ENABLED) {
if (TestHelpers.isInLauncherProcess()) {
pm.setComponentEnabledSetting(cn, COMPONENT_ENABLED_STATE_ENABLED, DONT_KILL_APP);
} else {
try {
final int userId = getContext().getUserId();
+ final String launcherPidCommand = "pidof " + pi.packageName;
+ final String initialPid = mDevice.executeShellCommand(launcherPidCommand);
+
mDevice.executeShellCommand(
"pm enable --user " + userId + " " + cn.flattenToString());
+
+ // Wait for Launcher restart after enabling test provider.
+ for (int i = 0; i < iterations; ++i) {
+ final String currentPid = mDevice.executeShellCommand(launcherPidCommand)
+ .replaceAll("\\s", "");
+ if (!currentPid.isEmpty() && !currentPid.equals(initialPid)) break;
+ if (i == iterations - 1) {
+ fail("Launcher didn't restart after enabling test provider");
+ }
+ SystemClock.sleep(100);
+ }
} catch (IOException e) {
fail(e.toString());
}
}
- final int iterations = isLauncherTest ? 300 : 100;
-
// Wait for Launcher content provider to become enabled.
for (int i = 0; i < iterations; ++i) {
final ContentProviderClient testProvider = getContext().getContentResolver()
@@ -405,6 +420,11 @@
.getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
+ public int getOverviewCurrentPageIndex() {
+ return getTestInfo(TestProtocol.REQUEST_GET_OVERVIEW_CURRENT_PAGE_INDEX)
+ .getInt(TestProtocol.TEST_INFO_RESPONSE_FIELD);
+ }
+
float getExactScreenCenterX() {
return getRealDisplaySize().x / 2f;
}
@@ -1529,7 +1549,8 @@
}
}
- void runToState(Runnable command, int expectedState, String actionName) {
+ /** Run an action and wait for the specified Launcher state. */
+ public void runToState(Runnable command, int expectedState, String actionName) {
final List<Integer> actualEvents = new ArrayList<>();
executeAndWaitForLauncherEvent(
command,
@@ -1702,6 +1723,16 @@
"scrolling");
}
+ void pointerScroll(float pointerX, float pointerY, Direction direction) {
+ executeAndWaitForLauncherEvent(
+ () -> injectEvent(getPointerMotionEvent(
+ ACTION_SCROLL, pointerX, pointerY, direction)),
+ event -> TestProtocol.SCROLL_FINISHED_MESSAGE.equals(event.getClassName()),
+ () -> "Didn't receive a scroll end message: " + direction + " scroll from ("
+ + pointerX + ", " + pointerY + ")",
+ "scrolling");
+ }
+
// Inject a swipe gesture. Inject exactly 'steps' motion points, incrementing event time by a
// fixed interval each time.
public void linearGesture(int startX, int startY, int endX, int endY, int steps,
@@ -1765,6 +1796,41 @@
return getContext().getResources();
}
+ private static MotionEvent getPointerMotionEvent(
+ int action, float x, float y, Direction direction) {
+ MotionEvent.PointerCoords[] coordinates = new MotionEvent.PointerCoords[1];
+ coordinates[0] = new MotionEvent.PointerCoords();
+ coordinates[0].x = x;
+ coordinates[0].y = y;
+ boolean isVertical = direction == Direction.UP || direction == Direction.DOWN;
+ boolean isForward = direction == Direction.RIGHT || direction == Direction.DOWN;
+ coordinates[0].setAxisValue(
+ isVertical ? MotionEvent.AXIS_VSCROLL : MotionEvent.AXIS_HSCROLL,
+ isForward ? 1f : -1f);
+
+ MotionEvent.PointerProperties[] properties = new MotionEvent.PointerProperties[1];
+ properties[0] = new MotionEvent.PointerProperties();
+ properties[0].id = 0;
+ properties[0].toolType = MotionEvent.TOOL_TYPE_MOUSE;
+
+ final long downTime = SystemClock.uptimeMillis();
+ return MotionEvent.obtain(
+ downTime,
+ downTime,
+ action,
+ /* pointerCount= */ 1,
+ properties,
+ coordinates,
+ /* metaState= */ 0,
+ /* buttonState= */ 0,
+ /* xPrecision= */ 1f,
+ /* yPrecision= */ 1f,
+ /* deviceId= */ 0,
+ /* edgeFlags= */ 0,
+ InputDevice.SOURCE_CLASS_POINTER,
+ /* flags= */ 0);
+ }
+
private static MotionEvent getTrackpadMotionEvent(long downTime, long eventTime,
int action, float x, float y, int pointerCount, TrackpadGestureType gestureType) {
MotionEvent.PointerProperties[] pointerProperties =
@@ -2094,6 +2160,11 @@
getTestInfo(TestProtocol.REQUEST_UNSTASH_TASKBAR_IF_STASHED);
}
+ /** Shows the bubble bar if it is stashed, otherwise this does nothing. */
+ public void showBubbleBarIfHidden() {
+ getTestInfo(TestProtocol.REQUEST_UNSTASH_BUBBLE_BAR_IF_STASHED);
+ }
+
/** Blocks the taskbar from automatically stashing based on time. */
public void enableBlockTimeout(boolean enable) {
getTestInfo(enable
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewActions.java b/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
index bd2c9c1..d7c40a0 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewActions.java
@@ -89,8 +89,7 @@
private SelectModeButtons getSelectModeButtons() {
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"want to get select mode buttons")) {
- UiObject2 selectModeButtons = mLauncher.waitForLauncherObject("select_mode_buttons");
- return new SelectModeButtons(selectModeButtons, mLauncher);
+ return new SelectModeButtons(mLauncher);
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
index 3d2914d..902ad5b 100644
--- a/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
+++ b/tests/tapl/com/android/launcher3/tapl/OverviewTaskMenu.java
@@ -16,6 +16,9 @@
package com.android.launcher3.tapl;
+import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_MODAL_TASK_STATE_ORDINAL;
+import static com.android.launcher3.testing.shared.TestProtocol.OVERVIEW_SPLIT_SELECT_ORDINAL;
+
import androidx.annotation.NonNull;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiObject2;
@@ -40,8 +43,11 @@
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"tap split menu item")) {
- mLauncher.clickLauncherObject(
- mLauncher.findObjectInContainer(mMenu, By.textStartsWith("Split")));
+ mLauncher.runToState(() -> mLauncher.clickLauncherObject(
+ mLauncher.findObjectInContainer(mMenu, By.textStartsWith("Split"))),
+ OVERVIEW_SPLIT_SELECT_ORDINAL,
+ "tapping split menu item"
+ );
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
"tapped split menu item")) {
@@ -72,6 +78,25 @@
}
}
+ /** Taps the select menu item from the overview task menu. */
+ @NonNull
+ public SelectModeButtons tapSelectMenuItem() {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck();
+ LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "before tapping the select menu item")) {
+
+ mLauncher.runToState(
+ () -> mLauncher.clickLauncherObject(
+ mLauncher.findObjectInContainer(mMenu, By.text("Select"))),
+ OVERVIEW_MODAL_TASK_STATE_ORDINAL, "tapping select menu item");
+
+ try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
+ "select menu item opened")) {
+ return new SelectModeButtons(mLauncher);
+ }
+ }
+ }
+
/** Returns true if an item matching the given string is present in the menu. */
public boolean hasMenuItem(String expectedMenuItemText) {
UiObject2 menuItem = mLauncher.findObjectInContainer(mMenu, By.text(expectedMenuItemText));
diff --git a/tests/tapl/com/android/launcher3/tapl/Qsb.java b/tests/tapl/com/android/launcher3/tapl/Qsb.java
index fe2a63d..d67b8a3 100644
--- a/tests/tapl/com/android/launcher3/tapl/Qsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/Qsb.java
@@ -118,9 +118,7 @@
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"want to open search result page");
LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
- mLauncher.clickLauncherObject(waitForQsbObject());
- // wait for the result rendering to complete
- mLauncher.waitForIdle();
+ clickQsb();
try (LauncherInstrumentation.Closable c2 = mLauncher.addContextLayer(
"clicked qsb to open search result page")) {
return createSearchResult();
@@ -128,6 +126,10 @@
}
}
+ protected void clickQsb() {
+ mLauncher.clickLauncherObject(waitForQsbObject());
+ }
+
@Override
public LauncherInstrumentation getLauncher() {
return mLauncher;
diff --git a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
index f0a8aa2..963bf79 100644
--- a/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
+++ b/tests/tapl/com/android/launcher3/tapl/SearchResultFromQsb.java
@@ -39,7 +39,7 @@
/** Find the app from search results with app name. */
public AppIcon findAppIcon(String appName) {
- UiObject2 icon = mLauncher.waitForLauncherObject(By.clazz(TextView.class).text(appName));
+ UiObject2 icon = mLauncher.waitForLauncherObject(AppIcon.getAppIconSelector(appName));
return createAppIcon(icon);
}
diff --git a/tests/tapl/com/android/launcher3/tapl/SelectModeButtons.java b/tests/tapl/com/android/launcher3/tapl/SelectModeButtons.java
index e1b73a4..146a67c 100644
--- a/tests/tapl/com/android/launcher3/tapl/SelectModeButtons.java
+++ b/tests/tapl/com/android/launcher3/tapl/SelectModeButtons.java
@@ -16,9 +16,15 @@
package com.android.launcher3.tapl;
+import static android.view.KeyEvent.KEYCODE_ESCAPE;
+
import androidx.annotation.NonNull;
import androidx.test.uiautomator.UiObject2;
+import com.android.launcher3.testing.shared.TestProtocol;
+
+import java.util.regex.Pattern;
+
/**
* View containing select mode buttons
*/
@@ -26,9 +32,14 @@
private final UiObject2 mSelectModeButtons;
private final LauncherInstrumentation mLauncher;
- SelectModeButtons(UiObject2 selectModeButtons,
- LauncherInstrumentation launcherInstrumentation) {
- mSelectModeButtons = selectModeButtons;
+ private static final Pattern EVENT_ALT_ESC_DOWN = Pattern.compile(
+ "Key event: KeyEvent.*?action=ACTION_DOWN.*?keyCode=KEYCODE_ESCAPE.*?metaState=0");
+ private static final Pattern EVENT_ALT_ESC_UP = Pattern.compile(
+ "Key event: KeyEvent.*?action=ACTION_UP.*?keyCode=KEYCODE_ESCAPE.*?metaState=0");
+
+
+ SelectModeButtons(LauncherInstrumentation launcherInstrumentation) {
+ mSelectModeButtons = launcherInstrumentation.waitForLauncherObject("select_mode_buttons");
mLauncher = launcherInstrumentation;
}
@@ -48,4 +59,22 @@
}
}
}
+
+ /**
+ * Close select mode when ESC key is pressed.
+ * @return The Overview
+ */
+ @NonNull
+ public Overview dismissByEscKey() {
+ try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_DOWN);
+ mLauncher.expectEvent(TestProtocol.SEQUENCE_MAIN, EVENT_ALT_ESC_UP);
+ mLauncher.getDevice().pressKeyCode(KEYCODE_ESCAPE);
+ try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
+ "pressed esc key")) {
+ return new Overview(mLauncher);
+ }
+ }
+ }
+
}
diff --git a/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java
index 064f80c..d05c112 100644
--- a/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java
+++ b/tests/tapl/com/android/launcher3/tapl/TaskbarAppIcon.java
@@ -68,6 +68,7 @@
@Override
protected boolean launcherStopsAfterLaunch() {
+ // false because if taskbar is showing then launcher is already stopped.
return false;
}
}
diff --git a/tests/tapl/com/android/launcher3/tapl/Workspace.java b/tests/tapl/com/android/launcher3/tapl/Workspace.java
index f8fa00c..a911de4 100644
--- a/tests/tapl/com/android/launcher3/tapl/Workspace.java
+++ b/tests/tapl/com/android/launcher3/tapl/Workspace.java
@@ -121,7 +121,10 @@
LauncherInstrumentation.Closable c =
mLauncher.addContextLayer("want to open all apps search")) {
verifyActiveContainer();
- mLauncher.getDevice().pressKeyCode(KEYCODE_META_RIGHT);
+ mLauncher.runToState(
+ () -> mLauncher.getDevice().pressKeyCode(KEYCODE_META_RIGHT),
+ ALL_APPS_STATE_ORDINAL,
+ "pressing keyboard shortcut");
try (LauncherInstrumentation.Closable c1 = mLauncher.addContextLayer(
"pressed meta key")) {
return new HomeAllApps(mLauncher);
@@ -192,16 +195,24 @@
}
/**
- * Ensures that workspace is scrollable. If it's not, drags an icon icons from hotseat to the
- * second screen.
+ * Ensures that workspace is scrollable. If it's not, drags a chrome app icon from hotseat
+ * to the second screen.
*/
public void ensureWorkspaceIsScrollable() {
+ ensureWorkspaceIsScrollable("Chrome");
+ }
+
+ /**
+ * Ensures that workspace is scrollable. If it's not, drags an icon of a given app name from
+ * hotseat to the second screen.
+ */
+ public void ensureWorkspaceIsScrollable(String appName) {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
final UiObject2 workspace = verifyActiveContainer();
if (!isWorkspaceScrollable(workspace)) {
try (LauncherInstrumentation.Closable c = mLauncher.addContextLayer(
"dragging icon to a second page of workspace to make it scrollable")) {
- dragIcon(workspace, getHotseatAppIcon("Chrome"), pagesPerScreen());
+ dragIcon(workspace, getHotseatAppIcon(appName), pagesPerScreen());
verifyActiveContainer();
}
}
@@ -450,7 +461,12 @@
}
/** Returns the index of the current page */
- private static int geCurrentPage(LauncherInstrumentation launcher) {
+ public int getCurrentPage() {
+ return getCurrentPage(mLauncher);
+ }
+
+ /** Returns the index of the current page */
+ private static int getCurrentPage(LauncherInstrumentation launcher) {
return launcher.getTestInfo(TestProtocol.REQUEST_WORKSPACE_CURRENT_PAGE_INDEX).getInt(
TestProtocol.TEST_INFO_RESPONSE_FIELD);
}
@@ -637,7 +653,7 @@
Point currentPosition, int destinationWorkspaceIndex, int y) {
final long downTime = SystemClock.uptimeMillis();
int displayX = launcher.getRealDisplaySize().x;
- int currentPage = Workspace.geCurrentPage(launcher);
+ int currentPage = Workspace.getCurrentPage(launcher);
int counter = 0;
while (currentPage != destinationWorkspaceIndex) {
counter++;
@@ -656,7 +672,7 @@
() -> launcher.movePointer(finalDragStart, screenEdge, DEFAULT_DRAG_STEPS,
true, downTime, downTime, true,
LauncherInstrumentation.GestureScope.DONT_EXPECT_PILFER));
- currentPage = Workspace.geCurrentPage(launcher);
+ currentPage = Workspace.getCurrentPage(launcher);
currentPosition = screenEdge;
}
return currentPosition;
@@ -695,10 +711,9 @@
*/
public void flingForward() {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
- final UiObject2 workspace = verifyActiveContainer();
- mLauncher.scroll(workspace, Direction.RIGHT,
- new Rect(0, 0, mLauncher.getEdgeSensitivityWidth() + 1, 0),
- FLING_STEPS, false);
+ Rect workspaceBounds = mLauncher.getVisibleBounds(verifyActiveContainer());
+ mLauncher.pointerScroll(
+ workspaceBounds.centerX(), workspaceBounds.centerY(), Direction.RIGHT);
verifyActiveContainer();
}
}
@@ -709,10 +724,9 @@
*/
public void flingBackward() {
try (LauncherInstrumentation.Closable e = mLauncher.eventsCheck()) {
- final UiObject2 workspace = verifyActiveContainer();
- mLauncher.scroll(workspace, Direction.LEFT,
- new Rect(mLauncher.getEdgeSensitivityWidth() + 1, 0, 0, 0),
- FLING_STEPS, false);
+ Rect workspaceBounds = mLauncher.getVisibleBounds(verifyActiveContainer());
+ mLauncher.pointerScroll(
+ workspaceBounds.centerX(), workspaceBounds.centerY(), Direction.LEFT);
verifyActiveContainer();
}
}