Merge "Workspace pagination UI update" into tm-qpr-dev
diff --git a/go/quickstep/res/values-ky/strings.xml b/go/quickstep/res/values-ky/strings.xml
index e4a2474..55e70c8 100644
--- a/go/quickstep/res/values-ky/strings.xml
+++ b/go/quickstep/res/values-ky/strings.xml
@@ -9,7 +9,7 @@
<string name="dialog_cancel" msgid="6464336969134856366">"ЖОККО ЧЫГАРУУ"</string>
<string name="dialog_settings" msgid="6564397136021186148">"ЖӨНДӨӨЛӨР"</string>
<string name="niu_actions_confirmation_title" msgid="3863451714863526143">"Экрандагы текстти которуу же угуу"</string>
- <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Экрандагы текст, веб-даректер жана скриншоттор сыяктуу маалымат Google менен бөлүшүлүшү мүмкүн.\n\nБөлүшүлгөн маалыматты өзгөртүү үчүн"<b>"Жөндөөлөр > Колдонмолор > Демейки колдонмолор > Санариптик жардамчы колдонмосуна өтүңүз"</b>"."</string>
+ <string name="niu_actions_confirmation_text" msgid="2105271481950866089">"Экрандагы текст, веб-даректер жана скриншоттор сыяктуу маалымат Google менен бөлүшүлүшү мүмкүн.\n\nБөлүшүлгөн маалыматты өзгөртүү үчүн"<b>"Параметрлер > Колдонмолор > Демейки колдонмолор > Санариптик жардамчы колдонмосуна өтүңүз"</b>"."</string>
<string name="assistant_not_selected_title" msgid="5017072974603345228">"Бул функцияны колдонуу үчүн жардамчыны тандаңыз"</string>
<string name="assistant_not_selected_text" msgid="3244613673884359276">"Экраныңыздагы текстти угуу же которуу үчүн Жөндөөлөрдөн санариптик жардамчы колдонмосун тандаңыз"</string>
<string name="assistant_not_supported_title" msgid="1675788067597484142">"Бул функцияны колдонуу үчүн жардамчыңызды өзгөртүңүз"</string>
diff --git a/go/src/com/android/launcher3/model/LoaderResults.java b/go/src/com/android/launcher3/model/LauncherBinder.java
similarity index 82%
rename from go/src/com/android/launcher3/model/LoaderResults.java
rename to go/src/com/android/launcher3/model/LauncherBinder.java
index 5f71061..437d8ca 100644
--- a/go/src/com/android/launcher3/model/LoaderResults.java
+++ b/go/src/com/android/launcher3/model/LauncherBinder.java
@@ -22,11 +22,11 @@
import com.android.launcher3.model.BgDataModel.Callbacks;
/**
- * Helper class to handle results of {@link com.android.launcher3.model.LoaderTask}.
+ * Binds the results of {@link com.android.launcher3.model.LoaderTask} to the Callbacks objects.
*/
-public class LoaderResults extends BaseLoaderResults {
+public class LauncherBinder extends BaseLauncherBinder {
- public LoaderResults(LauncherAppState app, BgDataModel dataModel,
+ public LauncherBinder(LauncherAppState app, BgDataModel dataModel,
AllAppsList allAppsList, Callbacks[] callbacks) {
super(app, dataModel, allAppsList, callbacks, MAIN_EXECUTOR);
}
diff --git a/quickstep/res/values-ky/strings.xml b/quickstep/res/values-ky/strings.xml
index 55e5a11..bb22c2f 100644
--- a/quickstep/res/values-ky/strings.xml
+++ b/quickstep/res/values-ky/strings.xml
@@ -37,7 +37,7 @@
<string name="hotseat_edu_message_migrate_landscape" msgid="4248943380443387697">"Көп иштетилген колдонмолорго Башкы экрандан оңой кириңиз. Сунуштар тартиптин негизинде өзгөрөт. Тандалмалардын катарындагы колдонмолор башкы экраныңызга жылдырылат."</string>
<string name="hotseat_edu_accept" msgid="1611544083278999837">"Сунушталган колдонолорду алуу"</string>
<string name="hotseat_edu_dismiss" msgid="2781161822780201689">"Жок, рахмат"</string>
- <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Жөндөөлөр"</string>
+ <string name="hotseat_prediction_settings" msgid="6246554993566070818">"Параметрлер"</string>
<string name="hotseat_auto_enrolled" msgid="522100018967146807">"Көп иштетилген колдонмолор ушул жерде көрүнүп, тартиптин негизинде өзгөрөт"</string>
<string name="hotseat_tip_no_empty_slots" msgid="1325212677738179185">"Сунуштарды алып туруу үчүн ылдый жактагы тилкедеги колдонмолорду сүйрөп келиңиз"</string>
<string name="hotseat_tip_gaps_filled" msgid="3035673010274223538">"Сунушталган колдонмолор бош жерге кошулат"</string>
@@ -71,7 +71,7 @@
<string name="overview_gesture_spoken_intro_subtitle" msgid="3853371838260201751">"Бир колдонмодон экинчисине өтүү үчүн экранды 2 манжа менен ылдыйдан өйдө сүрүп, коё бербей туруңуз."</string>
<string name="gesture_tutorial_confirm_title" msgid="6201516182040074092">"Дапдаяр!"</string>
<string name="gesture_tutorial_action_button_label" msgid="6249846312991332122">"Бүттү"</string>
- <string name="gesture_tutorial_action_button_label_settings" msgid="2923621047916486604">"Жөндөөлөр"</string>
+ <string name="gesture_tutorial_action_button_label_settings" msgid="2923621047916486604">"Параметрлер"</string>
<string name="gesture_tutorial_try_again" msgid="65962545858556697">"Кайталап көрүңүз"</string>
<string name="gesture_tutorial_nice" msgid="2936275692616928280">"Сонун!"</string>
<string name="gesture_tutorial_step" msgid="1279786122817620968">"Үйрөткүч: <xliff:g id="CURRENT">%1$d</xliff:g>/<xliff:g id="TOTAL">%2$d</xliff:g>"</string>
diff --git a/quickstep/res/values-sw600dp-land/dimens.xml b/quickstep/res/values-sw600dp-land/dimens.xml
index dc10c24..4ee388a 100644
--- a/quickstep/res/values-sw600dp-land/dimens.xml
+++ b/quickstep/res/values-sw600dp-land/dimens.xml
@@ -15,9 +15,6 @@
*/
-->
<resources>
- <!-- Overview actions -->
- <dimen name="overview_actions_top_margin">12dp</dimen>
-
<!-- All Set page -->
<dimen name="allset_page_margin_horizontal">48dp</dimen>
diff --git a/quickstep/res/values-sw600dp/dimens.xml b/quickstep/res/values-sw600dp/dimens.xml
index 5899814..daf1f63 100644
--- a/quickstep/res/values-sw600dp/dimens.xml
+++ b/quickstep/res/values-sw600dp/dimens.xml
@@ -33,6 +33,8 @@
<dimen name="overview_page_spacing">36dp</dimen>
<!-- The space to the left and to the right of the "Clear all" button -->
<dimen name="overview_grid_side_margin">64dp</dimen>
+ <!-- Overview actions -->
+ <dimen name="overview_actions_top_margin">24dp</dimen>
<!-- All Set page -->
<dimen name="allset_page_margin_horizontal">120dp</dimen>
diff --git a/quickstep/res/values-sw720dp-land/dimens.xml b/quickstep/res/values-sw720dp-land/dimens.xml
deleted file mode 100644
index 02d1189..0000000
--- a/quickstep/res/values-sw720dp-land/dimens.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
- * Copyright (c) 2022, 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>
- <!-- Overview actions -->
- <dimen name="overview_actions_top_margin">20dp</dimen>
-</resources>
diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
index 668567e..aa9e272 100644
--- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
+++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java
@@ -1560,7 +1560,8 @@
RemoteAnimationTarget[] wallpaperTargets,
boolean fromUnlock,
RectF startRect,
- float startWindowCornerRadius) {
+ float startWindowCornerRadius,
+ boolean fromPredictiveBack) {
AnimatorSet anim = null;
RectFSpringAnim rectFSpringAnim = null;
@@ -1594,7 +1595,11 @@
rectFSpringAnim = getClosingWindowAnimators(
anim, appTargets, launcherView, velocity, startRect,
startWindowCornerRadius);
- if (!mLauncher.isInState(LauncherState.ALL_APPS)) {
+ if (mLauncher.isInState(LauncherState.ALL_APPS)) {
+ // Skip scaling all apps, otherwise FloatingIconView will get wrong
+ // layout bounds.
+ skipAllAppsScale = true;
+ } else if (!fromPredictiveBack) {
anim.play(new StaggeredWorkspaceAnim(mLauncher, velocity.y,
true /* animateOverviewScrim */, launcherView).getAnimators());
@@ -1606,10 +1611,6 @@
// We play StaggeredWorkspaceAnim as a part of the closing window animation.
playWorkspaceReveal = false;
- } else {
- // Skip scaling all apps, otherwise FloatingIconView will get wrong
- // layout bounds.
- skipAllAppsScale = true;
}
} else {
anim.play(getFallbackClosingWindowAnimators(appTargets));
@@ -1686,7 +1687,8 @@
new RectF(getWindowTargetBounds(appTargets, getRotationChange(appTargets)));
Pair<RectFSpringAnim, AnimatorSet> pair = createWallpaperOpenAnimations(
appTargets, wallpaperTargets, mFromUnlock, windowTargetBounds,
- QuickStepContract.getWindowCornerRadius(mLauncher));
+ QuickStepContract.getWindowCornerRadius(mLauncher),
+ false /* fromPredictiveBack */);
mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
result.setAnimation(pair.second, mLauncher);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
index 731eea7..4e795d9 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java
@@ -598,9 +598,6 @@
}
public void onNavButtonsDarkIntensityChanged(float darkIntensity) {
- if (!isUserSetupComplete()) {
- return;
- }
mControllers.navbarButtonsViewController.getTaskbarNavButtonDarkIntensity()
.updateValue(darkIntensity);
}
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
index 7a75661..4ad3858 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarLauncherStateController.java
@@ -160,7 +160,6 @@
mIconAlignment.finishAnimation();
- Log.d("b/260135164", "onDestroy - updateIconAlphaForHome(1)");
mLauncher.getHotseat().setIconsAlpha(1f);
mLauncher.getStateManager().removeStateListener(mStateListener);
@@ -405,8 +404,6 @@
public void onAnimationEnd(Animator animation) {
if (isInStashedState && committed) {
// Reset hotseat alpha to default
- Log.d("b/260135164",
- "playStateTransitionAnim#onAnimationEnd - setIconsAlpha(1)");
mLauncher.getHotseat().setIconsAlpha(1);
}
}
@@ -455,9 +452,6 @@
* Hide Launcher Hotseat icons when Taskbar icons have opacity. Both icon sets
* should not be visible at the same time.
*/
- Log.d("b/260135164",
- "updateIconAlphaForHome - setIconsAlpha(" + (hotseatVisible ? 1 : 0)
- + "), isTaskbarPresent: " + mLauncher.getDeviceProfile().isTaskbarPresent);
mLauncher.getHotseat().setIconsAlpha(hotseatVisible ? 1 : 0);
mLauncher.getHotseat().setQsbAlpha(
mLauncher.getDeviceProfile().isQsbInline && !hotseatVisible ? 0 : 1);
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
index 86e1911..98c45d5 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java
@@ -32,7 +32,6 @@
import android.os.Handler;
import android.os.SystemProperties;
import android.provider.Settings;
-import android.util.Log;
import android.view.Display;
import androidx.annotation.NonNull;
@@ -227,10 +226,6 @@
mActivity = activity;
UnfoldTransitionProgressProvider unfoldTransitionProgressProvider =
getUnfoldTransitionProgressProviderForActivity(activity);
- if (unfoldTransitionProgressProvider == null) {
- Log.e("b/261320823", "UnfoldTransitionProgressProvider null in setActivity. "
- + "Unfold animation for launcher will not work.");
- }
mUnfoldProgressProvider.setSourceProvider(unfoldTransitionProgressProvider);
if (mTaskbarActivityContext != null) {
diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
index c269648..6031b49 100644
--- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
+++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStashController.java
@@ -364,7 +364,12 @@
* Returns the height that taskbar will be touchable.
*/
public int getTouchableHeight() {
- return mIsStashed ? mStashedHeight : mUnstashedHeight;
+ int bottomMargin = 0;
+ if (DisplayController.isTransientTaskbar(mActivity)) {
+ bottomMargin = mActivity.getResources().getDimensionPixelSize(
+ R.dimen.transient_taskbar_margin);
+ }
+ return mIsStashed ? mStashedHeight : (mUnstashedHeight + bottomMargin);
}
/**
diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
index 28c8980..a7651b6 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java
@@ -43,7 +43,6 @@
import static com.android.launcher3.testing.shared.TestProtocol.QUICK_SWITCH_STATE_ORDINAL;
import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN;
import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MODE;
-import static com.android.launcher3.util.Executors.THREAD_POOL_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
@@ -63,15 +62,18 @@
import android.os.CancellationSignal;
import android.os.IBinder;
import android.os.SystemProperties;
-import android.util.Log;
import android.view.Display;
import android.view.HapticFeedbackConstants;
import android.view.RemoteAnimationTarget;
import android.view.View;
import android.view.WindowManagerGlobal;
+import android.window.BackEvent;
+import android.window.OnBackAnimationCallback;
+import android.window.OnBackInvokedDispatcher;
import android.window.SplashScreen;
import androidx.annotation.BinderThread;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.app.viewcapture.ViewCapture;
@@ -105,6 +107,8 @@
import com.android.launcher3.statemanager.StateManager.StateHandler;
import com.android.launcher3.taskbar.LauncherTaskbarUIController;
import com.android.launcher3.taskbar.TaskbarManager;
+import com.android.launcher3.testing.TestLogging;
+import com.android.launcher3.testing.shared.TestProtocol;
import com.android.launcher3.uioverrides.QuickstepWidgetHolder.QuickstepHolderFactory;
import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
@@ -623,6 +627,29 @@
}
}
+ @Override
+ protected void registerBackDispatcher() {
+ getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ new OnBackAnimationCallback() {
+ @Override
+ public void onBackInvoked() {
+ onBackPressed();
+ TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onBackInvoked");
+ }
+
+ @Override
+ public void onBackProgressed(@NonNull BackEvent backEvent) {
+ QuickstepLauncher.this.onBackProgressed(backEvent.getProgress());
+ }
+
+ @Override
+ public void onBackCancelled() {
+ QuickstepLauncher.this.onBackCancelled();
+ }
+ });
+ }
+
private void onTaskbarInAppDisplayProgressUpdate(float progress, int flag) {
if (mTaskbarManager == null
|| mTaskbarManager.getCurrentActivityContext() == null
@@ -722,7 +749,7 @@
getSystemService(SensorManager.class),
getMainThreadHandler(),
getMainExecutor(),
- /* backgroundExecutor= */ THREAD_POOL_EXECUTOR,
+ /* backgroundExecutor= */ UI_HELPER_EXECUTOR,
/* tracingTagPrefix= */ "launcher",
WindowManagerGlobal.getWindowManagerService()
);
@@ -739,7 +766,6 @@
mUnfoldTransitionProgressProvider,
mRotationChangeProvider
);
- Log.d("b/261320823", "initUnfoldTransitionProgressProvider completed");
}
}
@@ -1002,6 +1028,14 @@
mPendingSplitSelectInfo = null;
}
+ @Override
+ public boolean areFreeformTasksVisible() {
+ if (mDesktopVisibilityController != null) {
+ return mDesktopVisibilityController.areFreeformTasksVisible();
+ }
+ return false;
+ }
+
private static final class LauncherTaskViewController extends
TaskViewTouchController<Launcher> {
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
index 733c6a8..95eb128 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/BackgroundAppState.java
@@ -28,6 +28,7 @@
import com.android.launcher3.allapps.AllAppsTransitionController;
import com.android.launcher3.config.FeatureFlags;
import com.android.quickstep.util.LayoutUtils;
+import com.android.quickstep.views.DesktopTaskView;
import com.android.quickstep.views.RecentsView;
/**
@@ -91,6 +92,12 @@
@Override
protected float getDepthUnchecked(Context context) {
+ if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+ if (Launcher.getLauncher(context).areFreeformTasksVisible()) {
+ // Don't blur the background while freeform tasks are visible
+ return 0;
+ }
+ }
return 1;
}
diff --git a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
index 969abc2..7392469 100644
--- a/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
+++ b/quickstep/src/com/android/launcher3/uioverrides/states/QuickSwitchState.java
@@ -17,10 +17,13 @@
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
+import android.graphics.Color;
+
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.R;
import com.android.launcher3.util.Themes;
+import com.android.quickstep.views.DesktopTaskView;
/**
* State to indicate we are about to launch a recent task. Note that this state is only used when
@@ -43,6 +46,12 @@
@Override
public int getWorkspaceScrimColor(Launcher launcher) {
+ if (DesktopTaskView.DESKTOP_MODE_SUPPORTED) {
+ if (launcher.areFreeformTasksVisible()) {
+ // No scrim while freeform tasks are visible
+ return Color.TRANSPARENT;
+ }
+ }
DeviceProfile dp = launcher.getDeviceProfile();
if (dp.isTaskbarPresentInApps) {
return launcher.getColor(R.color.taskbar_background);
diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
index 8409475..14b01fe 100644
--- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
+++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java
@@ -323,6 +323,8 @@
private final boolean mIsTransientTaskbar;
// May be set to false when mIsTransientTaskbar is true.
private boolean mCanSlowSwipeGoHome = true;
+ private boolean mHasReachedOverviewThreshold = false;
+ private boolean mDividerHiddenBeforeAnimation = false;
@Nullable
private RemoteAnimationTargets.ReleaseCheck mSwipePipToHomeReleaseCheck = null;
@@ -368,10 +370,13 @@
.getDimensionPixelSize(ENABLE_TASKBAR_REVISED_THRESHOLDS.get()
? R.dimen.taskbar_app_window_threshold_v2
: R.dimen.taskbar_app_window_threshold);
- mTaskbarHomeOverviewThreshold = res.getDimensionPixelSize(
- ENABLE_TASKBAR_REVISED_THRESHOLDS.get()
- ? R.dimen.taskbar_home_overview_threshold_v2
- : R.dimen.taskbar_home_overview_threshold);
+ boolean swipeWillNotShowTaskbar = mTaskbarAlreadyOpen;
+ mTaskbarHomeOverviewThreshold = swipeWillNotShowTaskbar
+ ? 0
+ : res.getDimensionPixelSize(
+ ENABLE_TASKBAR_REVISED_THRESHOLDS.get()
+ ? R.dimen.taskbar_home_overview_threshold_v2
+ : R.dimen.taskbar_home_overview_threshold);
mTaskbarCatchUpThreshold = res.getDimensionPixelSize(R.dimen.taskbar_catch_up_threshold);
}
@@ -765,6 +770,10 @@
private void setIsLikelyToStartNewTask(boolean isLikelyToStartNewTask, boolean animate) {
if (mIsLikelyToStartNewTask != isLikelyToStartNewTask) {
+ if (isLikelyToStartNewTask && mIsTransientTaskbar) {
+ setDividerShown(false /* shown */, true /* immediate */);
+ }
+
mIsLikelyToStartNewTask = isLikelyToStartNewTask;
maybeUpdateRecentsAttachedState(animate);
}
@@ -1669,7 +1678,10 @@
mRecentsAnimationController.enableInputConsumer();
// Start hiding the divider
- setDividerShown(false /* shown */, true /* immediate */);
+ if (!mIsTransientTaskbar || mTaskbarAlreadyOpen || mIsTaskbarAllAppsOpen
+ || mDividerHiddenBeforeAnimation) {
+ setDividerShown(false /* shown */, true /* immediate */);
+ }
}
private void computeRecentsScrollIfInvisible() {
@@ -2305,6 +2317,10 @@
// "Catch up" with the displacement at mTaskbarCatchUpThreshold.
if (displacement < mTaskbarCatchUpThreshold) {
+ if (!mHasReachedOverviewThreshold) {
+ setDividerShown(false /* shown */, true /* immediate */);
+ mHasReachedOverviewThreshold = true;
+ }
return Utilities.mapToRange(displacement, mTaskbarAppWindowThreshold,
mTaskbarCatchUpThreshold, 0, mTaskbarCatchUpThreshold, ACCEL_DEACCEL);
}
@@ -2313,6 +2329,12 @@
}
private void setDividerShown(boolean shown, boolean immediate) {
+ if (mRecentsAnimationTargets == null) {
+ if (!shown) {
+ mDividerHiddenBeforeAnimation = true;
+ }
+ return;
+ }
if (mDividerAnimator != null) {
mDividerAnimator.cancel();
}
diff --git a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
index 3edbbdf..03042c9 100644
--- a/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
+++ b/quickstep/src/com/android/quickstep/LauncherBackAnimationController.java
@@ -290,7 +290,8 @@
new RemoteAnimationTarget[0],
false /* fromUnlock */,
mCurrentRect,
- cornerRadius);
+ cornerRadius,
+ mBackInProgress /* fromPredictiveBack */);
startTransitionAnimations(pair.first, pair.second);
mLauncher.clearForceInvisibleFlag(INVISIBLE_ALL);
}
diff --git a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
index 9e25555..f1c0f3e 100644
--- a/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
+++ b/quickstep/src/com/android/quickstep/RecentsAnimationDeviceState.java
@@ -17,7 +17,6 @@
import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-import static android.content.Intent.ACTION_USER_UNLOCKED;
import static android.view.Display.DEFAULT_DISPLAY;
import static com.android.launcher3.util.DisplayController.CHANGE_ALL;
@@ -50,10 +49,8 @@
import android.graphics.Region;
import android.inputmethodservice.InputMethodService;
import android.net.Uri;
-import android.os.Process;
import android.os.RemoteException;
import android.os.SystemProperties;
-import android.os.UserManager;
import android.provider.Settings;
import android.view.MotionEvent;
@@ -63,9 +60,9 @@
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.DisplayController.DisplayInfoChangeListener;
import com.android.launcher3.util.DisplayController.Info;
+import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.NavigationMode;
import com.android.launcher3.util.SettingsCache;
-import com.android.launcher3.util.SimpleBroadcastReceiver;
import com.android.quickstep.TopTaskTracker.CachedTaskInfo;
import com.android.quickstep.util.NavBarPosition;
import com.android.systemui.shared.system.ActivityManagerWrapper;
@@ -109,15 +106,6 @@
private final boolean mIsOneHandedModeSupported;
private boolean mPipIsActive;
- private boolean mIsUserUnlocked;
- private final ArrayList<Runnable> mUserUnlockedActions = new ArrayList<>();
- private final SimpleBroadcastReceiver mUserUnlockedReceiver = new SimpleBroadcastReceiver(i -> {
- if (ACTION_USER_UNLOCKED.equals(i.getAction())) {
- mIsUserUnlocked = true;
- notifyUserUnlocked();
- }
- });
-
private int mGestureBlockingTaskId = -1;
private @NonNull Region mExclusionRegion = new Region();
private SystemGestureExclusionListenerCompat mExclusionListener;
@@ -143,14 +131,6 @@
runOnDestroy(mRotationTouchHelper::destroy);
}
- // Register for user unlocked if necessary
- mIsUserUnlocked = context.getSystemService(UserManager.class)
- .isUserUnlocked(Process.myUserHandle());
- if (!mIsUserUnlocked) {
- mUserUnlockedReceiver.register(mContext, ACTION_USER_UNLOCKED);
- }
- runOnDestroy(() -> mUserUnlockedReceiver.unregisterReceiverSafely(mContext));
-
// Register for exclusion updates
mExclusionListener = new SystemGestureExclusionListenerCompat(mDisplayId) {
@Override
@@ -310,39 +290,12 @@
}
/**
- * Adds a callback for when a user is unlocked. If the user is already unlocked, this listener
- * will be called back immediately.
- */
- public void runOnUserUnlocked(Runnable action) {
- if (mIsUserUnlocked) {
- action.run();
- } else {
- mUserUnlockedActions.add(action);
- }
- }
-
- /**
- * @return whether the user is unlocked.
- */
- public boolean isUserUnlocked() {
- return mIsUserUnlocked;
- }
-
- /**
* @return whether the user has completed setup wizard
*/
public boolean isUserSetupComplete() {
return mIsUserSetupComplete;
}
- private void notifyUserUnlocked() {
- for (Runnable action : mUserUnlockedActions) {
- action.run();
- }
- mUserUnlockedActions.clear();
- mUserUnlockedReceiver.unregisterReceiverSafely(mContext);
- }
-
/**
* Sets the task id where gestures should be blocked
*/
@@ -585,7 +538,7 @@
pw.println(" assistantAvailable=" + mAssistantAvailable);
pw.println(" assistantDisabled="
+ QuickStepContract.isAssistantGestureDisabled(mSystemUiStateFlags));
- pw.println(" isUserUnlocked=" + mIsUserUnlocked);
+ pw.println(" isUserUnlocked=" + LockedUserState.get(mContext).isUserUnlocked());
pw.println(" isOneHandedModeEnabled=" + mIsOneHandedModeEnabled);
pw.println(" isSwipeToNotificationEnabled=" + mIsSwipeToNotificationEnabled);
pw.println(" deferredGestureRegion=" + mDeferredGestureRegion.getBounds());
diff --git a/quickstep/src/com/android/quickstep/RotationTouchHelper.java b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
index f8b6966..4c66dbb 100644
--- a/quickstep/src/com/android/quickstep/RotationTouchHelper.java
+++ b/quickstep/src/com/android/quickstep/RotationTouchHelper.java
@@ -321,9 +321,9 @@
if (enable && !mInOverview && !TestProtocol.sDisableSensorRotation) {
// Clear any previous state from sensor manager
mSensorRotation = mCurrentAppRotation;
- mOrientationListener.enable();
+ UI_HELPER_EXECUTOR.execute(mOrientationListener::enable);
} else {
- mOrientationListener.disable();
+ UI_HELPER_EXECUTOR.execute(mOrientationListener::disable);
}
}
diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java
index 08a0ab3..d19f124 100644
--- a/quickstep/src/com/android/quickstep/TouchInteractionService.java
+++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java
@@ -87,6 +87,7 @@
import com.android.launcher3.tracing.TouchInteractionServiceProto;
import com.android.launcher3.uioverrides.plugins.PluginManagerWrapper;
import com.android.launcher3.util.DisplayController;
+import com.android.launcher3.util.LockedUserState;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.launcher3.util.TraceHelper;
import com.android.quickstep.inputconsumers.AccessibilityInputConsumer;
@@ -406,8 +407,8 @@
mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
// Call runOnUserUnlocked() before any other callbacks to ensure everything is initialized.
- mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
- mDeviceState.runOnUserUnlocked(mTaskbarManager::onUserUnlocked);
+ LockedUserState.get(this).runOnUserUnlocked(this::onUserUnlocked);
+ LockedUserState.get(this).runOnUserUnlocked(mTaskbarManager::onUserUnlocked);
mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
ProtoTracer.INSTANCE.get(this).add(this);
@@ -477,7 +478,7 @@
}
private void resetHomeBounceSeenOnQuickstepEnabledFirstTime() {
- if (!mDeviceState.isUserUnlocked() || mDeviceState.isButtonNavMode()) {
+ if (!LockedUserState.get(this).isUserUnlocked() || mDeviceState.isButtonNavMode()) {
// Skip if not yet unlocked (can't read user shared prefs) or if the current navigation
// mode doesn't have gestures
return;
@@ -520,7 +521,7 @@
@UiThread
private void onSystemUiFlagsChanged(int lastSysUIFlags) {
- if (mDeviceState.isUserUnlocked()) {
+ if (LockedUserState.get(this).isUserUnlocked()) {
int systemUiStateFlags = mDeviceState.getSystemUiStateFlags();
SystemUiProxy.INSTANCE.get(this).setLastSystemUiStateFlags(systemUiStateFlags);
mOverviewComponentObserver.onSystemUiStateChanged();
@@ -565,7 +566,7 @@
@UiThread
private void onAssistantVisibilityChanged() {
- if (mDeviceState.isUserUnlocked()) {
+ if (LockedUserState.get(this).isUserUnlocked()) {
mOverviewComponentObserver.getActivityInterface().onAssistantVisibilityChanged(
mDeviceState.getAssistantVisibility());
}
@@ -575,7 +576,7 @@
public void onDestroy() {
Log.d(TAG, "Touch service destroyed: user=" + getUserId());
sIsInitialized = false;
- if (mDeviceState.isUserUnlocked()) {
+ if (LockedUserState.get(this).isUserUnlocked()) {
mInputConsumer.unregisterInputConsumer();
mOverviewComponentObserver.onDestroy();
}
@@ -609,7 +610,7 @@
TestLogging.recordMotionEvent(
TestProtocol.SEQUENCE_TIS, "TouchInteractionService.onInputEvent", event);
- if (!mDeviceState.isUserUnlocked()) {
+ if (!LockedUserState.get(this).isUserUnlocked()) {
return;
}
@@ -631,7 +632,8 @@
mGestureState = newGestureState;
mConsumer = newConsumer(prevGestureState, mGestureState, event);
mUncheckedConsumer = mConsumer;
- } else if (mDeviceState.isUserUnlocked() && mDeviceState.isFullyGesturalNavMode()
+ } else if (LockedUserState.get(this).isUserUnlocked()
+ && mDeviceState.isFullyGesturalNavMode()
&& mDeviceState.canTriggerAssistantAction(event)) {
mGestureState = createGestureState(mGestureState);
// Do not change mConsumer as if there is an ongoing QuickSwitch gesture, we
@@ -751,7 +753,7 @@
boolean canStartSystemGesture = mDeviceState.canStartSystemGesture();
- if (!mDeviceState.isUserUnlocked()) {
+ if (!LockedUserState.get(this).isUserUnlocked()) {
CompoundString reasonString = newCompoundString("device locked");
InputConsumer consumer;
if (canStartSystemGesture) {
@@ -1098,7 +1100,7 @@
}
private void preloadOverview(boolean fromInit, boolean forSUWAllSet) {
- if (!mDeviceState.isUserUnlocked()) {
+ if (!LockedUserState.get(this).isUserUnlocked()) {
return;
}
@@ -1130,7 +1132,7 @@
@Override
public void onConfigurationChanged(Configuration newConfig) {
- if (!mDeviceState.isUserUnlocked()) {
+ if (!LockedUserState.get(this).isUserUnlocked()) {
return;
}
final BaseActivityInterface activityInterface =
@@ -1171,7 +1173,7 @@
} else {
// Dump everything
FeatureFlags.dump(pw);
- if (mDeviceState.isUserUnlocked()) {
+ if (LockedUserState.get(this).isUserUnlocked()) {
PluginManagerWrapper.INSTANCE.get(getBaseContext()).dump(pw);
}
mDeviceState.dump(pw);
diff --git a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
index 5efc45e..3d5c143 100644
--- a/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
+++ b/quickstep/src/com/android/quickstep/logging/SettingsChangeLogger.java
@@ -16,6 +16,7 @@
package com.android.quickstep.logging;
+import static com.android.launcher3.LauncherPrefs.THEMED_ICONS;
import static com.android.launcher3.LauncherPrefs.getDevicePrefs;
import static com.android.launcher3.LauncherPrefs.getPrefs;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_HOME_SCREEN_SUGGESTIONS_DISABLED;
@@ -39,6 +40,7 @@
import android.util.Xml;
import com.android.launcher3.AutoInstallsLayout;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
@@ -178,7 +180,7 @@
logger::log);
SharedPreferences prefs = getPrefs(mContext);
- logger.log(prefs.getBoolean(KEY_THEMED_ICONS, false)
+ logger.log(LauncherPrefs.get(mContext).get(THEMED_ICONS)
? LAUNCHER_THEMED_ICON_ENABLED
: LAUNCHER_THEMED_ICON_DISABLED);
diff --git a/quickstep/src/com/android/quickstep/util/BaseDepthController.java b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
index 877e28a..cecf58d 100644
--- a/quickstep/src/com/android/quickstep/util/BaseDepthController.java
+++ b/quickstep/src/com/android/quickstep/util/BaseDepthController.java
@@ -108,7 +108,10 @@
float depth = mDepth;
IBinder windowToken = mLauncher.getRootView().getWindowToken();
if (windowToken != null) {
- mWallpaperManager.setWallpaperZoomOut(windowToken, depth);
+ // The API's full zoom-out is three times larger than the zoom-out we apply to the
+ // icons. To keep the two consistent throughout the animation while keeping Launcher's
+ // concept of full depth unchanged, we divide the depth by 3 here.
+ mWallpaperManager.setWallpaperZoomOut(windowToken, depth / 3);
}
if (!BlurUtils.supportsBlursOnWindows()) {
diff --git a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
index ad54a70..cd5edab 100644
--- a/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
+++ b/quickstep/src/com/android/quickstep/util/StaggeredWorkspaceAnim.java
@@ -124,7 +124,7 @@
for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) {
View child = hotseatIcons.getChildAt(i);
CellLayoutLayoutParams lp = ((CellLayoutLayoutParams) child.getLayoutParams());
- addStaggeredAnimationForView(child, lp.cellY + 1, totalRows, duration);
+ addStaggeredAnimationForView(child, lp.getCellY() + 1, totalRows, duration);
}
} else {
final int hotseatRow, qsbRow;
@@ -194,7 +194,7 @@
for (int i = itemsContainer.getChildCount() - 1; i >= 0; i--) {
View child = itemsContainer.getChildAt(i);
CellLayoutLayoutParams lp = ((CellLayoutLayoutParams) child.getLayoutParams());
- addStaggeredAnimationForView(child, lp.cellY + lp.cellVSpan, totalRows, duration);
+ addStaggeredAnimationForView(child, lp.getCellY() + lp.cellVSpan, totalRows, duration);
}
mAnimators.addListener(new AnimatorListenerAdapter() {
diff --git a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
index c878278..858f6ab 100644
--- a/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
+++ b/quickstep/src/com/android/quickstep/views/DesktopTaskView.java
@@ -81,6 +81,8 @@
private final ArrayList<CancellableTask<?>> mPendingThumbnailRequests = new ArrayList<>();
+ private ShapeDrawable mBackground;
+
public DesktopTaskView(Context context) {
this(context, null);
}
@@ -99,10 +101,11 @@
float[] outerRadii = new float[8];
Arrays.fill(outerRadii, getTaskCornerRadius());
RoundRectShape shape = new RoundRectShape(outerRadii, null, null);
- ShapeDrawable background = new ShapeDrawable(shape);
- background.setTint(getResources().getColor(android.R.color.system_neutral2_300));
+ mBackground = new ShapeDrawable(shape);
+ mBackground.setTint(getResources().getColor(android.R.color.system_neutral2_300,
+ getContext().getTheme()));
// TODO(b/244348395): this should be wallpaper
- setBackground(background);
+ setBackground(mBackground);
mSnapshotViews.add(mSnapshotView);
}
@@ -427,6 +430,12 @@
// TODO(b/249371338): this copies parent implementation and makes it work for N thumbs
progress = Utilities.boundToRange(progress, 0, 1);
mFullscreenProgress = progress;
+ if (mFullscreenProgress > 0) {
+ // Don't show background while we are transitioning to/from fullscreen
+ setBackground(null);
+ } else {
+ setBackground(mBackground);
+ }
for (int i = 0; i < mSnapshotViewMap.size(); i++) {
TaskThumbnailView thumbnailView = mSnapshotViewMap.valueAt(i);
thumbnailView.getTaskOverlay().setFullscreenProgress(progress);
diff --git a/quickstep/src/com/android/quickstep/views/RecentsView.java b/quickstep/src/com/android/quickstep/views/RecentsView.java
index 9cf0601..c11f7ed 100644
--- a/quickstep/src/com/android/quickstep/views/RecentsView.java
+++ b/quickstep/src/com/android/quickstep/views/RecentsView.java
@@ -3040,7 +3040,7 @@
false /* fadeWithThumbnail */, true /* isStagedTask */);
}
- // TODO (b/257513449): Launch animation not fully complete. OK to remove flag once it is.
+ // Allow user to click staged app to launch into fullscreen
if (ENABLE_LAUNCH_FROM_STAGED_APP.get()) {
mFirstFloatingTaskView.setOnClickListener(this::animateToFullscreen);
}
@@ -3111,7 +3111,9 @@
false /* fadeWithThumbnail */,
true /* isStagedTask */);
- pendingAnimation.addEndListener(success -> launchStagedTask());
+ pendingAnimation.addEndListener(animationSuccess ->
+ mSplitSelectStateController.launchSplitTasks(launchSuccess ->
+ resetFromSplitSelectionState()));
pendingAnimation.buildAnim().start();
}
@@ -4818,16 +4820,6 @@
return mPendingAnimation;
}
- protected void launchStagedTask() {
- if (mSplitHiddenTaskView != null) {
- // Split staging was started from an existing running task (in Overview)
- mSplitHiddenTaskView.launchTask(success -> resetFromSplitSelectionState());
- } else {
- // Split staging was started from a new intent (from app menu in Home/AllApps)
- mActivity.startActivity(mSplitSelectSource.intent);
- }
- }
-
protected void onTaskLaunchAnimationEnd(boolean success) {
if (success) {
resetTaskVisuals();
diff --git a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
index 5c2e14f..1129a33 100644
--- a/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
+++ b/quickstep/tests/src/com/android/quickstep/DigitalWellBeingToastTest.java
@@ -46,7 +46,8 @@
runWithShellPermission(() ->
usageStatsManager.registerAppUsageLimitObserver(observerId, packages,
Duration.ofSeconds(600), Duration.ofSeconds(300),
- PendingIntent.getActivity(mTargetContext, -1, new Intent(),
+ PendingIntent.getActivity(mTargetContext, -1, new Intent()
+ .setPackage(mTargetContext.getPackageName()),
PendingIntent.FLAG_MUTABLE)));
mLauncher.goHome();
diff --git a/res/values-pt/strings.xml b/res/values-pt/strings.xml
index 98e8607..d97ba0f 100644
--- a/res/values-pt/strings.xml
+++ b/res/values-pt/strings.xml
@@ -32,7 +32,7 @@
<string name="split_screen_position_left" msgid="7537793098851830883">"Dividir para a esquerda"</string>
<string name="split_screen_position_right" msgid="1569377524925193369">"Dividir para a direita"</string>
<string name="split_app_info_accessibility" msgid="5475288491241414932">"Informações do app %1$s"</string>
- <string name="long_press_widget_to_add" msgid="3587712543577675817">"Toque e mantenha pressionado para mover um widget."</string>
+ <string name="long_press_widget_to_add" msgid="3587712543577675817">"Toque e pressione para mover um widget."</string>
<string name="long_accessible_way_to_add" msgid="2733588281439571974">"Toque duas vezes e mantenha a tela pressionada para mover um widget ou usar ações personalizadas."</string>
<string name="widget_dims_format" msgid="2370757736025621599">"%1$d × %2$d"</string>
<string name="widget_accessible_dims_format" msgid="3640149169885301790">"%1$d de largura por %2$d de altura"</string>
diff --git a/res/values/id.xml b/res/values/id.xml
index 9fc0ff8..52a7e98 100644
--- a/res/values/id.xml
+++ b/res/values/id.xml
@@ -37,4 +37,6 @@
<item type="id" name="quick_settings_button" />
<item type="id" name="notifications_button" />
<item type="id" name="cache_entry_tag_id" />
+
+ <item type="id" name="saved_clip_children_tag_id" />
</resources>
diff --git a/src/com/android/launcher3/AppWidgetResizeFrame.java b/src/com/android/launcher3/AppWidgetResizeFrame.java
index 76a91c0..85bd2d3 100644
--- a/src/com/android/launcher3/AppWidgetResizeFrame.java
+++ b/src/com/android/launcher3/AppWidgetResizeFrame.java
@@ -268,8 +268,10 @@
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) mWidgetView.getLayoutParams();
ItemInfo widgetInfo = (ItemInfo) mWidgetView.getTag();
- lp.cellX = lp.tmpCellX = widgetInfo.cellX;
- lp.cellY = lp.tmpCellY = widgetInfo.cellY;
+ lp.setCellX(widgetInfo.cellX);
+ lp.setTmpCellX(widgetInfo.cellX);
+ lp.setCellY(widgetInfo.cellY);
+ lp.setTmpCellY(widgetInfo.cellY);
lp.cellHSpan = widgetInfo.spanX;
lp.cellVSpan = widgetInfo.spanY;
lp.isLockedToGrid = true;
@@ -425,8 +427,8 @@
int spanX = lp.cellHSpan;
int spanY = lp.cellVSpan;
- int cellX = lp.useTmpCoords ? lp.tmpCellX : lp.cellX;
- int cellY = lp.useTmpCoords ? lp.tmpCellY : lp.cellY;
+ int cellX = lp.useTmpCoords ? lp.getTmpCellX() : lp.getCellX();
+ int cellY = lp.useTmpCoords ? lp.getTmpCellY() : lp.getCellY();
// For each border, we bound the resizing based on the minimum width, and the maximum
// expandability.
@@ -467,8 +469,8 @@
mLauncher.getString(R.string.widget_resized, spanX, spanY));
}
- lp.tmpCellX = cellX;
- lp.tmpCellY = cellY;
+ lp.setTmpCellX(cellX);
+ lp.setTmpCellY(cellY);
lp.cellHSpan = spanX;
lp.cellVSpan = spanY;
mRunningVInc += vSpanDelta;
diff --git a/src/com/android/launcher3/BaseActivity.java b/src/com/android/launcher3/BaseActivity.java
index 61707df..e71391f 100644
--- a/src/com/android/launcher3/BaseActivity.java
+++ b/src/com/android/launcher3/BaseActivity.java
@@ -176,14 +176,7 @@
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- if (Utilities.ATLEAST_T) {
- getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_DEFAULT,
- () -> {
- onBackPressed();
- TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onBackInvoked");
- });
- }
+ registerBackDispatcher();
}
@Override
@@ -246,6 +239,17 @@
}
+ protected void registerBackDispatcher() {
+ if (Utilities.ATLEAST_T) {
+ getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+ OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+ () -> {
+ onBackPressed();
+ TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onBackInvoked");
+ });
+ }
+ }
+
public boolean isStarted() {
return (mActivityFlags & ACTIVITY_STATE_STARTED) != 0;
}
diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java
index 9f54f09..edbce10 100644
--- a/src/com/android/launcher3/BubbleTextView.java
+++ b/src/com/android/launcher3/BubbleTextView.java
@@ -16,11 +16,14 @@
package com.android.launcher3;
+import static com.android.launcher3.config.FeatureFlags.ENABLE_DOWNLOAD_APP_UX_V2;
import static com.android.launcher3.config.FeatureFlags.ENABLE_ICON_LABEL_AUTO_SCALING;
import static com.android.launcher3.graphics.PreloadIconDrawable.newPendingIcon;
import static com.android.launcher3.icons.BitmapInfo.FLAG_NO_BADGE;
import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
import static com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INCREMENTAL_DOWNLOAD_ACTIVE;
+import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -291,7 +294,7 @@
@UiThread
public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean animate, int staggerIndex) {
- applyFromWorkspaceItem(info, false);
+ applyFromWorkspaceItem(info, null);
}
/**
@@ -320,10 +323,10 @@
}
@UiThread
- public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean promiseStateChanged) {
+ public void applyFromWorkspaceItem(WorkspaceItemInfo info, PreloadIconDrawable icon) {
applyIconAndLabel(info);
setItemInfo(info);
- applyLoadingState(promiseStateChanged);
+ applyLoadingState(icon);
applyDotState(info, false /* animate */);
setDownloadStateContentDescription(info, info.getProgressLevel());
}
@@ -710,23 +713,23 @@
* If this app is installed and downloading incrementally, the progress bar will be updated
* with the total download progress.
*/
- public void applyLoadingState(boolean promiseStateChanged) {
+ public void applyLoadingState(PreloadIconDrawable icon) {
if (getTag() instanceof ItemInfoWithIcon) {
WorkspaceItemInfo info = (WorkspaceItemInfo) getTag();
- if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_INCREMENTAL_DOWNLOAD_ACTIVE)
- != 0) {
- updateProgressBarUi(info.getProgressLevel() == 100);
- } else if (info.hasPromiseIconUi() || (info.runtimeStatusFlags
- & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
- updateProgressBarUi(promiseStateChanged);
+ if ((info.runtimeStatusFlags & FLAG_INCREMENTAL_DOWNLOAD_ACTIVE) != 0
+ || info.hasPromiseIconUi()
+ || (info.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0
+ || (ENABLE_DOWNLOAD_APP_UX_V2.get() && icon != null)) {
+ updateProgressBarUi(icon);
}
}
}
- private void updateProgressBarUi(boolean maybePerformFinishedAnimation) {
+ private void updateProgressBarUi(PreloadIconDrawable oldIcon) {
+ FastBitmapDrawable originalIcon = mIcon;
PreloadIconDrawable preloadDrawable = applyProgressLevel();
- if (preloadDrawable != null && maybePerformFinishedAnimation) {
- preloadDrawable.maybePerformFinishedAnimation();
+ if (preloadDrawable != null && oldIcon != null) {
+ preloadDrawable.maybePerformFinishedAnimation(oldIcon, () -> setIcon(originalIcon));
}
}
@@ -824,12 +827,12 @@
!= 0) {
String percentageString = NumberFormat.getPercentInstance()
.format(progressLevel * 0.01);
- if ((info.runtimeStatusFlags & ItemInfoWithIcon.FLAG_INSTALL_SESSION_ACTIVE) != 0) {
+ if ((info.runtimeStatusFlags & FLAG_INSTALL_SESSION_ACTIVE) != 0) {
setContentDescription(getContext()
.getString(
R.string.app_installing_title, info.title, percentageString));
} else if ((info.runtimeStatusFlags
- & ItemInfoWithIcon.FLAG_INCREMENTAL_DOWNLOAD_ACTIVE) != 0) {
+ & FLAG_INCREMENTAL_DOWNLOAD_ACTIVE) != 0) {
setContentDescription(getContext()
.getString(
R.string.app_downloading_title, info.title, percentageString));
diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java
index ecfd230..05b225c 100644
--- a/src/com/android/launcher3/CellLayout.java
+++ b/src/com/android/launcher3/CellLayout.java
@@ -623,8 +623,8 @@
if (alpha <= 0) continue;
mVisualizeGridPaint.setAlpha(255);
- int x = mDragOutlines[i].cellX;
- int y = mDragOutlines[i].cellY;
+ int x = mDragOutlines[i].getCellX();
+ int y = mDragOutlines[i].getCellY();
int spanX = mDragOutlines[i].cellHSpan;
int spanY = mDragOutlines[i].cellVSpan;
@@ -764,7 +764,8 @@
// Generate an id for each view, this assumes we have at most 256x256 cells
// per workspace screen
- if (lp.cellX >= 0 && lp.cellX <= mCountX - 1 && lp.cellY >= 0 && lp.cellY <= mCountY - 1) {
+ if (lp.getCellX() >= 0 && lp.getCellX() <= mCountX - 1
+ && lp.getCellY() >= 0 && lp.getCellY() <= mCountY - 1) {
// If the horizontal or vertical span is set to -1, it is taken to
// mean that it spans the extent of the CellLayout
if (lp.cellHSpan < 0) lp.cellHSpan = mCountX;
@@ -1072,7 +1073,7 @@
if (adjustOccupied) {
GridOccupancy occupied = permanent ? mOccupied : mTmpOccupied;
- occupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
+ occupied.markCells(lp.getCellX(), lp.getCellY(), lp.cellHSpan, lp.cellVSpan, false);
occupied.markCells(cellX, cellY, lp.cellHSpan, lp.cellVSpan, true);
}
@@ -1083,11 +1084,11 @@
final int oldY = lp.y;
lp.isLockedToGrid = true;
if (permanent) {
- lp.cellX = info.cellX = cellX;
- lp.cellY = info.cellY = cellY;
+ lp.setCellX(info.cellX = cellX);
+ lp.setCellY(info.cellY = cellY);
} else {
- lp.tmpCellX = cellX;
- lp.tmpCellY = cellY;
+ lp.setTmpCellX(cellX);
+ lp.setTmpCellY(cellY);
}
clc.setupLp(child);
final int newX = lp.x;
@@ -1167,8 +1168,8 @@
mDragOutlineCurrent = (oldIndex + 1) % mDragOutlines.length;
CellLayoutLayoutParams cell = mDragOutlines[mDragOutlineCurrent];
- cell.cellX = cellX;
- cell.cellY = cellY;
+ cell.setCellX(cellX);
+ cell.setCellY(cellY);
cell.cellHSpan = spanX;
cell.cellVSpan = spanY;
@@ -1385,8 +1386,8 @@
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
CellAndSpan c = solution.map.get(child);
if (c != null) {
- lp.tmpCellX = c.cellX;
- lp.tmpCellY = c.cellY;
+ lp.setTmpCellX(c.cellX);
+ lp.setTmpCellY(c.cellY);
lp.cellHSpan = c.spanX;
lp.cellVSpan = c.spanY;
mTmpOccupied.markCells(c, true);
@@ -1433,7 +1434,7 @@
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
if (c != null && !skip && (child instanceof Reorderable)) {
ReorderPreviewAnimation rha = new ReorderPreviewAnimation((Reorderable) child,
- mode, lp.cellX, lp.cellY, c.cellX, c.cellY, c.spanX, c.spanY);
+ mode, lp.getCellX(), lp.getCellY(), c.cellX, c.cellY, c.spanX, c.spanY);
rha.animate();
}
}
@@ -1626,12 +1627,14 @@
// We do a null check here because the item info can be null in the case of the
// AllApps button in the hotseat.
if (info != null && child != dragView) {
- final boolean requiresDbUpdate = (info.cellX != lp.tmpCellX
- || info.cellY != lp.tmpCellY || info.spanX != lp.cellHSpan
+ final boolean requiresDbUpdate = (info.cellX != lp.getTmpCellX()
+ || info.cellY != lp.getTmpCellY() || info.spanX != lp.cellHSpan
|| info.spanY != lp.cellVSpan);
- info.cellX = lp.cellX = lp.tmpCellX;
- info.cellY = lp.cellY = lp.tmpCellY;
+ lp.setCellX(lp.getTmpCellX());
+ info.cellX = lp.getTmpCellX();
+ info.cellY = lp.getTmpCellY();
+ lp.setCellY(lp.getTmpCellY());
info.spanX = lp.cellHSpan;
info.spanY = lp.cellVSpan;
@@ -1697,7 +1700,8 @@
if (child == dragView) continue;
CellLayoutLayoutParams
lp = (CellLayoutLayoutParams) child.getLayoutParams();
- r1.set(lp.cellX, lp.cellY, lp.cellX + lp.cellHSpan, lp.cellY + lp.cellVSpan);
+ r1.set(lp.getCellX(), lp.getCellY(), lp.getCellX() + lp.cellHSpan,
+ lp.getCellY() + lp.cellVSpan);
if (Rect.intersects(r0, r1)) {
mIntersectingViews.add(child);
if (boundingRect != null) {
@@ -1723,11 +1727,11 @@
View child = mShortcutsAndWidgets.getChildAt(i);
CellLayoutLayoutParams
lp = (CellLayoutLayoutParams) child.getLayoutParams();
- if (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY) {
- lp.tmpCellX = lp.cellX;
- lp.tmpCellY = lp.cellY;
- animateChildToPosition(child, lp.cellX, lp.cellY, REORDER_ANIMATION_DURATION,
- 0, false, false);
+ if (lp.getTmpCellX() != lp.getCellX() || lp.getTmpCellY() != lp.getCellY()) {
+ lp.setTmpCellX(lp.getCellX());
+ lp.setTmpCellY(lp.getCellY());
+ animateChildToPosition(child, lp.getCellX(), lp.getCellY(),
+ REORDER_ANIMATION_DURATION, 0, false, false);
}
}
setItemPlacementDirty(false);
@@ -2449,9 +2453,9 @@
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
CellAndSpan c;
if (temp) {
- c = new CellAndSpan(lp.tmpCellX, lp.tmpCellY, lp.cellHSpan, lp.cellVSpan);
+ c = new CellAndSpan(lp.getTmpCellX(), lp.getTmpCellY(), lp.cellHSpan, lp.cellVSpan);
} else {
- c = new CellAndSpan(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan);
+ c = new CellAndSpan(lp.getCellX(), lp.getCellY(), lp.cellHSpan, lp.cellVSpan);
}
solution.add(child, c);
}
@@ -2794,7 +2798,7 @@
if (view == null || view.getParent() != mShortcutsAndWidgets) return;
CellLayoutLayoutParams
lp = (CellLayoutLayoutParams) view.getLayoutParams();
- mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, true);
+ mOccupied.markCells(lp.getCellX(), lp.getCellY(), lp.cellHSpan, lp.cellVSpan, true);
}
public void markCellsAsUnoccupiedForView(View view) {
@@ -2807,7 +2811,7 @@
if (view == null || view.getParent() != mShortcutsAndWidgets) return;
CellLayoutLayoutParams
lp = (CellLayoutLayoutParams) view.getLayoutParams();
- mOccupied.markCells(lp.cellX, lp.cellY, lp.cellHSpan, lp.cellVSpan, false);
+ mOccupied.markCells(lp.getCellX(), lp.getCellY(), lp.cellHSpan, lp.cellVSpan, false);
}
public int getDesiredWidth() {
diff --git a/src/com/android/launcher3/DeviceProfile.java b/src/com/android/launcher3/DeviceProfile.java
index 25520e1..f124940 100644
--- a/src/com/android/launcher3/DeviceProfile.java
+++ b/src/com/android/launcher3/DeviceProfile.java
@@ -910,12 +910,24 @@
cellHeightPx = cellContentHeight;
cellLayoutBorderSpacePx.y -= extraHeightRequired / numBorders;
} else {
- // If it still doesn't fit, set borderSpace to 0 and distribute the space for
- // cellHeight, and reduce iconSize.
+ // If it still doesn't fit, set borderSpace to 0 to recover space.
cellHeightPx = (cellHeightPx * inv.numRows
+ cellLayoutBorderSpacePx.y * numBorders) / inv.numRows;
- iconSizePx = Math.min(iconSizePx, cellHeightPx - cellTextAndPaddingHeight);
cellLayoutBorderSpacePx.y = 0;
+ // Reduce iconDrawablePaddingPx to make cellContentHeight smaller.
+ int cellContentWithoutPadding = cellContentHeight - iconDrawablePaddingPx;
+ if (cellContentWithoutPadding <= cellHeightPx) {
+ iconDrawablePaddingPx = cellContentHeight - cellHeightPx;
+ } else {
+ // If it still doesn't fit, set iconDrawablePaddingPx to 0 to recover space,
+ // then proportional reduce iconSizePx and iconTextSizePx to fit.
+ iconDrawablePaddingPx = 0;
+ float ratio = cellHeightPx / (float) cellContentWithoutPadding;
+ iconSizePx = (int) (iconSizePx * ratio);
+ iconTextSizePx = (int) (iconTextSizePx * ratio);
+ }
+ cellTextAndPaddingHeight =
+ iconDrawablePaddingPx + Utilities.calculateTextHeight(iconTextSizePx);
}
cellContentHeight = iconSizePx + cellTextAndPaddingHeight;
}
diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java
index 517a2d3..af6935f 100644
--- a/src/com/android/launcher3/Launcher.java
+++ b/src/com/android/launcher3/Launcher.java
@@ -121,6 +121,7 @@
import android.widget.Toast;
import androidx.annotation.CallSuper;
+import androidx.annotation.FloatRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
@@ -2074,6 +2075,14 @@
mStateManager.getState().onBackPressed(this);
}
+ protected void onBackProgressed(@FloatRange(from = 0.0, to = 1.0) float backProgress) {
+ mStateManager.getState().onBackProgressed(this, backProgress);
+ }
+
+ protected void onBackCancelled() {
+ mStateManager.getState().onBackCancelled(this);
+ }
+
protected void onScreenOff() {
// Reset AllApps to its initial state only if we are not in the middle of
// processing a multi-step drop
@@ -3326,4 +3335,12 @@
return false; // Return false to continue iterating through all the items.
});
}
+
+ /**
+ * Returns {@code true} if there are visible tasks with windowing mode set to
+ * {@link android.app.WindowConfiguration#WINDOWING_MODE_FREEFORM}
+ */
+ public boolean areFreeformTasksVisible() {
+ return false; // Base launcher does not track freeform tasks
+ }
}
diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java
index 4965936..3461601 100644
--- a/src/com/android/launcher3/LauncherAppState.java
+++ b/src/com/android/launcher3/LauncherAppState.java
@@ -18,7 +18,8 @@
import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_RESOURCE_UPDATED;
-import static com.android.launcher3.LauncherPrefs.getDevicePrefs;
+import static com.android.launcher3.LauncherPrefs.ICON_STATE;
+import static com.android.launcher3.LauncherPrefs.THEMED_ICONS;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.launcher3.util.SettingsCache.NOTIFICATION_BADGING_URI;
@@ -55,7 +56,7 @@
public class LauncherAppState implements SafeCloseable {
public static final String ACTION_FORCE_ROLOAD = "force-reload-launcher";
- private static final String KEY_ICON_STATE = "pref_icon_shape_path";
+ public static final String KEY_ICON_STATE = "pref_icon_shape_path";
// We do not need any synchronization for this variable as its only written on UI thread.
public static final MainThreadInitializedObject<LauncherAppState> INSTANCE =
@@ -117,10 +118,9 @@
observer, MODEL_EXECUTOR.getHandler());
mOnTerminateCallback.add(iconChangeTracker::close);
MODEL_EXECUTOR.execute(observer::verifyIconChanged);
- SharedPreferences prefs = LauncherPrefs.getPrefs(mContext);
- prefs.registerOnSharedPreferenceChangeListener(observer);
+ LauncherPrefs.get(context).addListener(observer, THEMED_ICONS);
mOnTerminateCallback.add(
- () -> prefs.unregisterOnSharedPreferenceChangeListener(observer));
+ () -> LauncherPrefs.get(mContext).removeListener(observer, THEMED_ICONS));
InstallSessionTracker installSessionTracker =
InstallSessionHelper.INSTANCE.get(context).registerInstallTracker(mModel);
@@ -207,12 +207,12 @@
public void onSystemIconStateChanged(String iconState) {
IconShape.init(mContext);
refreshAndReloadLauncher();
- getDevicePrefs(mContext).edit().putString(KEY_ICON_STATE, iconState).apply();
+ LauncherPrefs.get(mContext).put(ICON_STATE, iconState);
}
void verifyIconChanged() {
String iconState = mIconProvider.getSystemIconState();
- if (!iconState.equals(getDevicePrefs(mContext).getString(KEY_ICON_STATE, ""))) {
+ if (!iconState.equals(LauncherPrefs.get(mContext).get(ICON_STATE))) {
onSystemIconStateChanged(iconState);
}
}
diff --git a/src/com/android/launcher3/LauncherModel.java b/src/com/android/launcher3/LauncherModel.java
index 20df897..2c6458b 100644
--- a/src/com/android/launcher3/LauncherModel.java
+++ b/src/com/android/launcher3/LauncherModel.java
@@ -47,7 +47,7 @@
import com.android.launcher3.model.BgDataModel.Callbacks;
import com.android.launcher3.model.CacheDataUpdatedTask;
import com.android.launcher3.model.ItemInstallQueue;
-import com.android.launcher3.model.LoaderResults;
+import com.android.launcher3.model.LauncherBinder;
import com.android.launcher3.model.LoaderTask;
import com.android.launcher3.model.ModelDelegate;
import com.android.launcher3.model.ModelWriter;
@@ -405,22 +405,22 @@
MAIN_EXECUTOR.execute(cb::clearPendingBinds);
}
- LoaderResults loaderResults = new LoaderResults(
+ LauncherBinder launcherBinder = new LauncherBinder(
mApp, mBgDataModel, mBgAllAppsList, callbacksList);
if (bindDirectly) {
// Divide the set of loaded items into those that we are binding synchronously,
// and everything else that is to be bound normally (asynchronously).
- loaderResults.bindWorkspace(bindAllCallbacks);
+ launcherBinder.bindWorkspace(bindAllCallbacks);
// For now, continue posting the binding of AllApps as there are other
// issues that arise from that.
- loaderResults.bindAllApps();
- loaderResults.bindDeepShortcuts();
- loaderResults.bindWidgets();
+ launcherBinder.bindAllApps();
+ launcherBinder.bindDeepShortcuts();
+ launcherBinder.bindWidgets();
return true;
} else {
stopLoader();
mLoaderTask = new LoaderTask(
- mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, loaderResults);
+ mApp, mBgAllAppsList, mBgDataModel, mModelDelegate, launcherBinder);
// Always post the loader task, instead of running directly
// (even on same thread) so that we exit any nested synchronized blocks
diff --git a/src/com/android/launcher3/LauncherPrefs.kt b/src/com/android/launcher3/LauncherPrefs.kt
index 0d6ed04..1fb2dce 100644
--- a/src/com/android/launcher3/LauncherPrefs.kt
+++ b/src/com/android/launcher3/LauncherPrefs.kt
@@ -2,24 +2,255 @@
import android.content.Context
import android.content.SharedPreferences
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener
+import androidx.annotation.VisibleForTesting
+import com.android.launcher3.allapps.WorkProfileManager
+import com.android.launcher3.model.DeviceGridState
+import com.android.launcher3.pm.InstallSessionHelper
+import com.android.launcher3.provider.RestoreDbTask
+import com.android.launcher3.util.MainThreadInitializedObject
+import com.android.launcher3.util.Themes
-object LauncherPrefs {
+/**
+ * Use same context for shared preferences, so that we use a single cached instance
+ * TODO(b/262721340): Replace all direct SharedPreference refs with LauncherPrefs / Item methods.
+ */
+class LauncherPrefs(private val context: Context) {
- @JvmStatic
- fun getPrefs(context: Context): SharedPreferences {
- // Use application context for shared preferences, so that we use a single cached instance
- return context.applicationContext.getSharedPreferences(
- LauncherFiles.SHARED_PREFERENCES_KEY,
- Context.MODE_PRIVATE
- )
+ /**
+ * Retrieves the value for an [Item] from [SharedPreferences]. It handles method typing via the
+ * default value type, and will throw an error if the type of the item provided is not a
+ * `String`, `Boolean`, `Float`, `Int`, `Long`, or `Set<String>`.
+ */
+ @Suppress("IMPLICIT_CAST_TO_ANY", "UNCHECKED_CAST")
+ fun <T : Any> get(item: Item<T>): T {
+ val sp = context.getSharedPreferences(item.sharedPrefFile, Context.MODE_PRIVATE)
+
+ return when (item.defaultValue::class.java) {
+ String::class.java -> sp.getString(item.sharedPrefKey, item.defaultValue as String)
+ Boolean::class.java,
+ java.lang.Boolean::class.java ->
+ sp.getBoolean(item.sharedPrefKey, item.defaultValue as Boolean)
+ Int::class.java,
+ java.lang.Integer::class.java -> sp.getInt(item.sharedPrefKey, item.defaultValue as Int)
+ Float::class.java,
+ java.lang.Float::class.java ->
+ sp.getFloat(item.sharedPrefKey, item.defaultValue as Float)
+ Long::class.java,
+ java.lang.Long::class.java -> sp.getLong(item.sharedPrefKey, item.defaultValue as Long)
+ Set::class.java -> sp.getStringSet(item.sharedPrefKey, item.defaultValue as Set<String>)
+ else ->
+ throw IllegalArgumentException(
+ "item type: ${item.defaultValue::class.java}" +
+ " is not compatible with sharedPref methods"
+ )
+ }
+ as T
}
- @JvmStatic
- fun getDevicePrefs(context: Context): SharedPreferences {
- // Use application context for shared preferences, so that we use a single cached instance
- return context.applicationContext.getSharedPreferences(
- LauncherFiles.DEVICE_PREFERENCES_KEY,
- Context.MODE_PRIVATE
- )
+ /**
+ * Stores each of the values provided in `SharedPreferences` according to the configuration
+ * contained within the associated items provided. Internally, it uses apply, so the caller
+ * cannot assume that the values that have been put are immediately available for use.
+ *
+ * The forEach loop is necessary here since there is 1 `SharedPreference.Editor` returned from
+ * prepareToPutValue(itemsToValues) for every distinct `SharedPreferences` file present in the
+ * provided item configurations.
+ */
+ fun put(vararg itemsToValues: Pair<Item<*>, Any>): Unit =
+ prepareToPutValues(itemsToValues).forEach { it.apply() }
+
+ /**
+ * Stores the value provided in `SharedPreferences` according to the item configuration provided
+ * It is asynchronous, so the caller can't assume that the value put is immediately available.
+ */
+ fun <T : Any> put(item: Item<T>, value: T): Unit =
+ context
+ .getSharedPreferences(item.sharedPrefFile, Context.MODE_PRIVATE)
+ .edit()
+ .putValue(item, value)
+ .apply()
+
+ /**
+ * Synchronously stores all the values provided according to their associated Item
+ * configuration.
+ */
+ fun putSync(vararg itemsToValues: Pair<Item<*>, Any>): Unit =
+ prepareToPutValues(itemsToValues).forEach { it.commit() }
+
+ /**
+ * Update each shared preference file with the item - value pairs provided. This method is
+ * optimized to avoid retrieving the same shared preference file multiple times.
+ *
+ * @return `List<SharedPreferences.Editor>` 1 for each distinct shared preference file among the
+ * items given as part of the itemsToValues parameter
+ */
+ private fun prepareToPutValues(
+ itemsToValues: Array<out Pair<Item<*>, Any>>
+ ): List<SharedPreferences.Editor> =
+ itemsToValues
+ .groupBy { it.first.sharedPrefFile }
+ .map { fileToItemValueList ->
+ context
+ .getSharedPreferences(fileToItemValueList.key, Context.MODE_PRIVATE)
+ .edit()
+ .apply {
+ fileToItemValueList.value.forEach { itemToValue ->
+ putValue(itemToValue.first, itemToValue.second)
+ }
+ }
+ }
+
+ /**
+ * Handles adding values to `SharedPreferences` regardless of type. This method is especially
+ * helpful for updating `SharedPreferences` values for `List<<Item>Any>` that have multiple
+ * types of Item values.
+ */
+ @Suppress("UNCHECKED_CAST")
+ private fun SharedPreferences.Editor.putValue(
+ item: Item<*>,
+ value: Any
+ ): SharedPreferences.Editor =
+ when (value::class.java) {
+ String::class.java -> putString(item.sharedPrefKey, value as String)
+ Boolean::class.java,
+ java.lang.Boolean::class.java -> putBoolean(item.sharedPrefKey, value as Boolean)
+ Int::class.java,
+ java.lang.Integer::class.java -> putInt(item.sharedPrefKey, value as Int)
+ Float::class.java,
+ java.lang.Float::class.java -> putFloat(item.sharedPrefKey, value as Float)
+ Long::class.java,
+ java.lang.Long::class.java -> putLong(item.sharedPrefKey, value as Long)
+ Set::class.java -> putStringSet(item.sharedPrefKey, value as Set<String>)
+ else ->
+ throw IllegalArgumentException(
+ "item type: " +
+ "${item.defaultValue!!::class} is not compatible with sharedPref methods"
+ )
+ }
+
+ /**
+ * After calling this method, the listener will be notified of any future updates to the
+ * `SharedPreferences` files associated with the provided list of items. The listener will need
+ * to filter update notifications so they don't activate for non-relevant updates.
+ */
+ fun addListener(listener: OnSharedPreferenceChangeListener, vararg items: Item<*>) {
+ items
+ .map { it.sharedPrefFile }
+ .distinct()
+ .forEach {
+ context
+ .getSharedPreferences(it, Context.MODE_PRIVATE)
+ .registerOnSharedPreferenceChangeListener(listener)
+ }
}
+
+ /**
+ * Stops the listener from getting notified of any more updates to any of the
+ * `SharedPreferences` files associated with any of the provided list of [Item].
+ */
+ fun removeListener(listener: OnSharedPreferenceChangeListener, vararg items: Item<*>) {
+ // If a listener is not registered to a SharedPreference, unregistering it does nothing
+ items
+ .map { it.sharedPrefFile }
+ .distinct()
+ .forEach {
+ context
+ .getSharedPreferences(it, Context.MODE_PRIVATE)
+ .unregisterOnSharedPreferenceChangeListener(listener)
+ }
+ }
+
+ /**
+ * Checks if all the provided [Item] have values stored in their corresponding
+ * `SharedPreferences` files.
+ */
+ fun has(vararg items: Item<*>): Boolean {
+ items
+ .groupBy { it.sharedPrefFile }
+ .forEach { (file, itemsSublist) ->
+ val prefs: SharedPreferences =
+ context.getSharedPreferences(file, Context.MODE_PRIVATE)
+ if (!itemsSublist.none { !prefs.contains(it.sharedPrefKey) }) return false
+ }
+ return true
+ }
+
+ /**
+ * Asynchronously removes the [Item]'s value from its corresponding `SharedPreferences` file.
+ */
+ fun remove(vararg items: Item<*>) = prepareToRemove(items).forEach { it.apply() }
+
+ /** Synchronously removes the [Item]'s value from its corresponding `SharedPreferences` file. */
+ fun removeSync(vararg items: Item<*>) = prepareToRemove(items).forEach { it.commit() }
+
+ /**
+ * Creates `SharedPreferences.Editor` transactions for removing all the provided [Item] values
+ * from their respective `SharedPreferences` files. These returned `Editors` can then be
+ * committed or applied for synchronous or async behavior.
+ */
+ private fun prepareToRemove(items: Array<out Item<*>>): List<SharedPreferences.Editor> =
+ items
+ .groupBy { it.sharedPrefFile }
+ .map { (file, items) ->
+ context.getSharedPreferences(file, Context.MODE_PRIVATE).edit().also { editor ->
+ items.forEach { item -> editor.remove(item.sharedPrefKey) }
+ }
+ }
+
+ companion object {
+ @JvmField var INSTANCE = MainThreadInitializedObject { LauncherPrefs(it) }
+
+ @JvmStatic fun get(context: Context): LauncherPrefs = INSTANCE.get(context)
+
+ @JvmField val ICON_STATE = nonRestorableItem(LauncherAppState.KEY_ICON_STATE, "")
+ @JvmField val THEMED_ICONS = backedUpItem(Themes.KEY_THEMED_ICONS, false)
+ @JvmField val PROMISE_ICON_IDS = backedUpItem(InstallSessionHelper.PROMISE_ICON_IDS, "")
+ @JvmField val WORK_EDU_STEP = backedUpItem(WorkProfileManager.KEY_WORK_EDU_STEP, 0)
+ @JvmField val WORKSPACE_SIZE = backedUpItem(DeviceGridState.KEY_WORKSPACE_SIZE, "")
+ @JvmField val HOTSEAT_COUNT = backedUpItem(DeviceGridState.KEY_HOTSEAT_COUNT, -1)
+ @JvmField
+ val DEVICE_TYPE =
+ backedUpItem(DeviceGridState.KEY_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE)
+ @JvmField val DB_FILE = backedUpItem(DeviceGridState.KEY_DB_FILE, "")
+ @JvmField
+ val RESTORE_DEVICE =
+ backedUpItem(RestoreDbTask.RESTORED_DEVICE_TYPE, InvariantDeviceProfile.TYPE_PHONE)
+ @JvmField val APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_IDS, "")
+ @JvmField val OLD_APP_WIDGET_IDS = backedUpItem(RestoreDbTask.APPWIDGET_OLD_IDS, "")
+
+ @VisibleForTesting
+ @JvmStatic
+ fun <T> backedUpItem(sharedPrefKey: String, defaultValue: T): Item<T> =
+ Item(sharedPrefKey, LauncherFiles.SHARED_PREFERENCES_KEY, defaultValue)
+
+ @VisibleForTesting
+ @JvmStatic
+ fun <T> nonRestorableItem(sharedPrefKey: String, defaultValue: T): Item<T> =
+ Item(sharedPrefKey, LauncherFiles.DEVICE_PREFERENCES_KEY, defaultValue)
+
+ @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
+ @JvmStatic
+ fun getPrefs(context: Context): SharedPreferences {
+ // Use application context for shared preferences, so we use single cached instance
+ return context.applicationContext.getSharedPreferences(
+ LauncherFiles.SHARED_PREFERENCES_KEY,
+ Context.MODE_PRIVATE
+ )
+ }
+
+ @Deprecated("Don't use shared preferences directly. Use other LauncherPref methods.")
+ @JvmStatic
+ fun getDevicePrefs(context: Context): SharedPreferences {
+ // Use application context for shared preferences, so we use a single cached instance
+ return context.applicationContext.getSharedPreferences(
+ LauncherFiles.DEVICE_PREFERENCES_KEY,
+ Context.MODE_PRIVATE
+ )
+ }
+ }
+}
+
+data class Item<T>(val sharedPrefKey: String, val sharedPrefFile: String, val defaultValue: T) {
+ fun to(value: T): Pair<Item<T>, T> = Pair(this, value)
}
diff --git a/src/com/android/launcher3/LauncherState.java b/src/com/android/launcher3/LauncherState.java
index 5dddc6f..b9e4c17 100644
--- a/src/com/android/launcher3/LauncherState.java
+++ b/src/com/android/launcher3/LauncherState.java
@@ -34,6 +34,8 @@
import android.graphics.Color;
import android.view.animation.Interpolator;
+import androidx.annotation.FloatRange;
+
import com.android.launcher3.statemanager.BaseState;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.states.HintState;
@@ -342,6 +344,27 @@
}
}
+ /**
+ * Find {@link StateManager} and target {@link LauncherState} to handle back progress in
+ * predictive back gesture.
+ */
+ public void onBackProgressed(
+ Launcher launcher, @FloatRange(from = 0.0, to = 1.0) float backProgress) {
+ StateManager<LauncherState> lsm = launcher.getStateManager();
+ LauncherState toState = lsm.getLastState();
+ lsm.onBackProgressed(toState, backProgress);
+ }
+
+ /**
+ * Find {@link StateManager} and target {@link LauncherState} to handle backProgress in
+ * predictive back gesture.
+ */
+ public void onBackCancelled(Launcher launcher) {
+ StateManager<LauncherState> lsm = launcher.getStateManager();
+ LauncherState toState = lsm.getLastState();
+ lsm.onBackCancelled(toState);
+ }
+
public static abstract class PageAlphaProvider {
public final Interpolator interpolator;
diff --git a/src/com/android/launcher3/ShortcutAndWidgetContainer.java b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
index 7a74d7e..5f39f7e 100644
--- a/src/com/android/launcher3/ShortcutAndWidgetContainer.java
+++ b/src/com/android/launcher3/ShortcutAndWidgetContainer.java
@@ -83,8 +83,8 @@
View child = getChildAt(i);
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) child.getLayoutParams();
- if ((lp.cellX <= cellX) && (cellX < lp.cellX + lp.cellHSpan)
- && (lp.cellY <= cellY) && (cellY < lp.cellY + lp.cellVSpan)) {
+ if ((lp.getCellX() <= cellX) && (cellX < lp.getCellX() + lp.cellHSpan)
+ && (lp.getCellY() <= cellY) && (cellY < lp.getCellY() + lp.cellVSpan)) {
return child;
}
}
@@ -260,7 +260,7 @@
lp.canReorder = false;
if (mContainerType == HOTSEAT) {
CellLayout cl = (CellLayout) getParent();
- cl.setFolderLeaveBehindCell(lp.cellX, lp.cellY);
+ cl.setFolderLeaveBehindCell(lp.getCellX(), lp.getCellY());
}
}
diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java
index 2b9c135..460c658 100644
--- a/src/com/android/launcher3/Workspace.java
+++ b/src/com/android/launcher3/Workspace.java
@@ -1757,7 +1757,8 @@
boolean willCreateUserFolder(ItemInfo info, View dropOverView, boolean considerTimeout) {
if (dropOverView != null) {
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) dropOverView.getLayoutParams();
- if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY)) {
+ if (lp.useTmpCoords && (lp.getTmpCellX() != lp.getCellX()
+ || lp.getTmpCellY() != lp.getCellY())) {
return false;
}
}
@@ -1792,7 +1793,8 @@
boolean willAddToExistingUserFolder(ItemInfo dragInfo, View dropOverView) {
if (dropOverView != null) {
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) dropOverView.getLayoutParams();
- if (lp.useTmpCoords && (lp.tmpCellX != lp.cellX || lp.tmpCellY != lp.cellY)) {
+ if (lp.useTmpCoords && (lp.getTmpCellX() != lp.getCellX()
+ || lp.getTmpCellY() != lp.getCellY())) {
return false;
}
}
@@ -2009,8 +2011,10 @@
// update the item's position after drop
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) cell.getLayoutParams();
- lp.cellX = lp.tmpCellX = mTargetCell[0];
- lp.cellY = lp.tmpCellY = mTargetCell[1];
+ lp.setTmpCellX(mTargetCell[0]);
+ lp.setCellX(mTargetCell[0]);
+ lp.setTmpCellY(mTargetCell[1]);
+ lp.setCellY(mTargetCell[1]);
lp.cellHSpan = item.spanX;
lp.cellVSpan = item.spanY;
lp.isLockedToGrid = true;
@@ -2024,7 +2028,7 @@
(LauncherAppWidgetHostView) cell, dropTargetLayout);
}
mLauncher.getModelWriter().modifyItemInDatabase(info, container, screenId,
- lp.cellX, lp.cellY, item.spanX, item.spanY);
+ lp.getCellX(), lp.getCellY(), item.spanX, item.spanY);
} else {
if (!returnToOriginalCellToPreventShuffling) {
onNoCellFound(dropTargetLayout, d.dragInfo, d.logInstanceId);
@@ -2035,8 +2039,8 @@
// If we can't find a drop location, we return the item to its original position
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) cell.getLayoutParams();
- mTargetCell[0] = lp.cellX;
- mTargetCell[1] = lp.cellY;
+ mTargetCell[0] = lp.getCellX();
+ mTargetCell[1] = lp.getCellY();
CellLayout layout = (CellLayout) cell.getParent().getParent();
layout.markCellsAsOccupiedForView(cell);
}
diff --git a/src/com/android/launcher3/WorkspaceLayoutManager.java b/src/com/android/launcher3/WorkspaceLayoutManager.java
index 91e12fa..bf448c9 100644
--- a/src/com/android/launcher3/WorkspaceLayoutManager.java
+++ b/src/com/android/launcher3/WorkspaceLayoutManager.java
@@ -117,8 +117,8 @@
lp = new CellLayoutLayoutParams(x, y, spanX, spanY, screenId);
} else {
lp = (CellLayoutLayoutParams) genericLp;
- lp.cellX = x;
- lp.cellY = y;
+ lp.setCellX(x);
+ lp.setCellY(y);
lp.cellHSpan = spanX;
lp.cellVSpan = spanY;
}
@@ -136,7 +136,8 @@
// TODO: This branch occurs when the workspace is adding views
// outside of the defined grid
// maybe we should be deleting these items from the LauncherModel?
- Log.e(TAG, "Failed to add to item at (" + lp.cellX + "," + lp.cellY + ") to CellLayout");
+ Log.e(TAG, "Failed to add to item at (" + lp.getCellX() + "," + lp.getCellY()
+ + ") to CellLayout");
}
child.setHapticFeedbackEnabled(false);
diff --git a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
index 063b82e..3c316b8 100644
--- a/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
+++ b/src/com/android/launcher3/accessibility/LauncherAccessibilityDelegate.java
@@ -259,7 +259,7 @@
if (((host.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL)
&& layout.isRegionVacant(info.cellX - 1, info.cellY, 1, info.spanY))
|| !layout.isRegionVacant(info.cellX + info.spanX, info.cellY, 1, info.spanY)) {
- lp.cellX --;
+ lp.setCellX(lp.getCellX() - 1);
info.cellX --;
}
lp.cellHSpan ++;
@@ -269,7 +269,7 @@
info.spanX --;
} else if (action == R.string.action_increase_height) {
if (!layout.isRegionVacant(info.cellX, info.cellY + info.spanY, info.spanX, 1)) {
- lp.cellY --;
+ lp.setCellY(lp.getCellY() - 1);
info.cellY --;
}
lp.cellVSpan ++;
diff --git a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
index 63e6d13..112d47e 100644
--- a/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
+++ b/src/com/android/launcher3/allapps/AllAppsGridAdapter.java
@@ -21,6 +21,7 @@
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
+import androidx.annotation.Px;
import androidx.core.view.accessibility.AccessibilityEventCompat;
import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
import androidx.core.view.accessibility.AccessibilityRecordCompat;
@@ -144,6 +145,19 @@
}
/**
+ * We need to extend all apps' RecyclerView's bottom by 5% of view height to ensure extra
+ * roll(s) of app icons is rendered at the bottom, so that they can fill the bottom gap
+ * created during predictive back's scale animation from all apps to home.
+ */
+ @Override
+ protected void calculateExtraLayoutSpace(RecyclerView.State state, int[] extraLayoutSpace) {
+ super.calculateExtraLayoutSpace(state, extraLayoutSpace);
+ @Px int extraSpacePx = (int) (getHeight()
+ * (1 - AllAppsTransitionController.SWIPE_ALL_APPS_TO_HOME_MIN_SCALE) / 2);
+ extraLayoutSpace[1] = Math.max(extraLayoutSpace[1], extraSpacePx);
+ }
+
+ /**
* Returns the number of rows before {@param adapterPosition}, including this position
* which should not be counted towards the collection info.
*/
diff --git a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
index d308fcb..8cb31fa 100644
--- a/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
+++ b/src/com/android/launcher3/allapps/AllAppsRecyclerView.java
@@ -17,11 +17,15 @@
import static com.android.launcher3.logger.LauncherAtom.ContainerInfo;
import static com.android.launcher3.logger.LauncherAtom.SearchResultContainer;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_SCROLLED_DOWN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_PERSONAL_SCROLLED_DOWN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_PERSONAL_SCROLLED_UP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_SCROLLED_UNKNOWN_DIRECTION;
-import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_SCROLLED_UP;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_SEARCH_SCROLLED_DOWN;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_SEARCH_SCROLLED_UP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_VERTICAL_SWIPE_BEGIN;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_ALLAPPS_VERTICAL_SWIPE_END;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORK_FAB_BUTTON_COLLAPSE;
+import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WORK_FAB_BUTTON_EXTEND;
import static com.android.launcher3.util.LogConfig.SEARCH_LOGGING;
import android.content.Context;
@@ -275,17 +279,37 @@
private void logCumulativeVerticalScroll() {
ActivityContext context = ActivityContext.lookupContext(getContext());
StatsLogManager mgr = context.getStatsLogManager();
- ExtendedEditText editText = context.getAppsView().getSearchUiManager().getEditText();
+ ActivityAllAppsContainerView<?> appsView = context.getAppsView();
+ ExtendedEditText editText = appsView.getSearchUiManager().getEditText();
ContainerInfo containerInfo = ContainerInfo.newBuilder().setSearchResultContainer(
SearchResultContainer
.newBuilder()
.setQueryLength((editText == null) ? -1 : editText.length())).build();
-
- // mCumulativeVerticalScroll == 0 when user comes back to original position, we don't
- // know the direction of scrolling.
- mgr.logger().withContainerInfo(containerInfo).log(
- mCumulativeVerticalScroll == 0 ? LAUNCHER_ALLAPPS_SCROLLED_UNKNOWN_DIRECTION
- : (mCumulativeVerticalScroll > 0) ? LAUNCHER_ALLAPPS_SCROLLED_DOWN
- : LAUNCHER_ALLAPPS_SCROLLED_UP);
+ if (mCumulativeVerticalScroll == 0) {
+ // mCumulativeVerticalScroll == 0 when user comes back to original position, we
+ // don't know the direction of scrolling.
+ mgr.logger().withContainerInfo(containerInfo).log(
+ LAUNCHER_ALLAPPS_SCROLLED_UNKNOWN_DIRECTION);
+ return;
+ } else if (appsView.isSearching()) {
+ // In search results page
+ mgr.logger().withContainerInfo(containerInfo).log((mCumulativeVerticalScroll > 0)
+ ? LAUNCHER_ALLAPPS_SEARCH_SCROLLED_DOWN
+ : LAUNCHER_ALLAPPS_SEARCH_SCROLLED_UP);
+ return;
+ } else if (appsView.mViewPager != null) {
+ int currentPage = appsView.mViewPager.getCurrentPage();
+ if (currentPage == BaseAllAppsContainerView.AdapterHolder.WORK) {
+ // In work A-Z list
+ mgr.logger().withContainerInfo(containerInfo).log((mCumulativeVerticalScroll > 0)
+ ? LAUNCHER_WORK_FAB_BUTTON_COLLAPSE
+ : LAUNCHER_WORK_FAB_BUTTON_EXTEND);
+ return;
+ }
+ }
+ // In personal A-Z list
+ mgr.logger().withContainerInfo(containerInfo).log((mCumulativeVerticalScroll > 0)
+ ? LAUNCHER_ALLAPPS_PERSONAL_SCROLLED_DOWN
+ : LAUNCHER_ALLAPPS_PERSONAL_SCROLLED_UP);
}
}
diff --git a/src/com/android/launcher3/allapps/AllAppsTransitionController.java b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
index 9930abe..6f6f86b 100644
--- a/src/com/android/launcher3/allapps/AllAppsTransitionController.java
+++ b/src/com/android/launcher3/allapps/AllAppsTransitionController.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.allapps;
+import static com.android.launcher3.LauncherAnimUtils.SCALE_PROPERTY;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.ALL_APPS_CONTENT;
@@ -31,13 +32,21 @@
import android.util.FloatProperty;
import android.view.HapticFeedbackConstants;
import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
import android.view.animation.Interpolator;
+import androidx.annotation.FloatRange;
+import androidx.annotation.Nullable;
+
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherState;
+import com.android.launcher3.R;
+import com.android.launcher3.anim.AnimatedFloat;
import com.android.launcher3.anim.AnimatorListeners;
+import com.android.launcher3.anim.Interpolators;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.anim.PropertySetter;
import com.android.launcher3.statemanager.StateManager.StateHandler;
@@ -61,6 +70,8 @@
implements StateHandler<LauncherState>, OnDeviceProfileChangeListener {
// This constant should match the second derivative of the animator interpolator.
public static final float INTERP_COEFF = 1.7f;
+ public static final float SWIPE_ALL_APPS_TO_HOME_MIN_SCALE = 0.9f;
+ private static final int REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS = 200;
public static final FloatProperty<AllAppsTransitionController> ALL_APPS_PROGRESS =
new FloatProperty<AllAppsTransitionController>("allAppsProgress") {
@@ -139,6 +150,7 @@
private ActivityAllAppsContainerView<Launcher> mAppsView;
private final Launcher mLauncher;
+ private final AnimatedFloat mAllAppScale = new AnimatedFloat(this::onScaleProgressChanged);
private boolean mIsVerticalLayout;
// Whether this class should take care of closing the keyboard.
@@ -160,6 +172,8 @@
private boolean mIsTablet;
+ private boolean mHasScaleEffect;
+
public AllAppsTransitionController(Launcher l) {
mLauncher = l;
DeviceProfile dp = mLauncher.getDeviceProfile();
@@ -232,6 +246,62 @@
onProgressAnimationEnd();
}
+ @Override
+ public void onBackProgressed(
+ LauncherState toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) {
+ if (!mLauncher.isInState(ALL_APPS) || !NORMAL.equals(toState)) {
+ return;
+ }
+
+ float deceleratedProgress =
+ Interpolators.PREDICTIVE_BACK_DECELERATED_EASE.getInterpolation(backProgress);
+ float scaleProgress = SWIPE_ALL_APPS_TO_HOME_MIN_SCALE
+ + (1 - SWIPE_ALL_APPS_TO_HOME_MIN_SCALE) * (1 - deceleratedProgress);
+
+ mAllAppScale.updateValue(scaleProgress);
+ }
+
+ @Override
+ public void onBackCancelled(LauncherState toState) {
+ if (!mLauncher.isInState(ALL_APPS) || !NORMAL.equals(toState)) {
+ return;
+ }
+
+ // TODO: once ag/20649618 is picked into tm-qpr, we don't need to animate back on cancel
+ // swipe because framework will do that for us in {@link #onBackProgressed}.
+ animateAllAppsToNoScale();
+ }
+
+ private void onScaleProgressChanged() {
+ final float scaleProgress = mAllAppScale.value;
+ SCALE_PROPERTY.set(mLauncher.getAppsView(), scaleProgress);
+ mLauncher.getScrimView().setScrimHeaderScale(scaleProgress);
+
+ AllAppsRecyclerView rv = mLauncher.getAppsView().getActiveRecyclerView();
+ if (rv != null && rv.getScrollbar() != null) {
+ rv.getScrollbar().setVisibility(scaleProgress < 1f ? View.INVISIBLE : View.VISIBLE);
+ }
+
+ // Disable view clipping from all apps' RecyclerView up to all apps view during scale
+ // animation, and vice versa. The goal is to display extra roll(s) app icons (rendered in
+ // {@link AppsGridLayoutManager#calculateExtraLayoutSpace}) during scale animation.
+ boolean hasScaleEffect = scaleProgress < 1f;
+ if (hasScaleEffect != mHasScaleEffect) {
+ mHasScaleEffect = hasScaleEffect;
+ if (mHasScaleEffect) {
+ setClipChildrenOnViewTree(rv, mLauncher.getAppsView(), false);
+ } else {
+ restoreClipChildrenOnViewTree(rv, mLauncher.getAppsView());
+ }
+ }
+ }
+
+ private void animateAllAppsToNoScale() {
+ mAllAppScale.animateToValue(1f)
+ .setDuration(REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS)
+ .start();
+ }
+
/**
* Creates an animation which updates the vertical transition progress and updates all the
* dependent UI using various animation events
@@ -258,6 +328,8 @@
if (config.userControlled && success && mShouldControlKeyboard) {
mLauncher.getAppsView().getSearchUiManager().getEditText().hideKeyboard();
}
+
+ mAllAppScale.updateValue(1f);
});
}
@@ -325,6 +397,79 @@
}
/**
+ * Recursively call {@link ViewGroup#setClipChildren(boolean)} from {@link View} to ts parent
+ * (direct or indirect) inclusive. This method will also save the old clipChildren value on each
+ * view with {@link View#setTag(int, Object)}, which can be restored in
+ * {@link #restoreClipChildrenOnViewTree(View, ViewParent)}.
+ *
+ * Note that if parent is null or not a parent of the view, this method will be applied all the
+ * way to root view.
+ *
+ * @param v child view
+ * @param parent direct or indirect parent of child view
+ * @param clipChildren whether we should clip children
+ */
+ private static void setClipChildrenOnViewTree(
+ @Nullable View v,
+ @Nullable ViewParent parent,
+ boolean clipChildren) {
+ if (v == null) {
+ return;
+ }
+
+ if (v instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) v;
+ boolean oldClipChildren = viewGroup.getClipChildren();
+ if (oldClipChildren != clipChildren) {
+ v.setTag(R.id.saved_clip_children_tag_id, oldClipChildren);
+ viewGroup.setClipChildren(clipChildren);
+ }
+ }
+
+ if (v == parent) {
+ return;
+ }
+
+ if (v.getParent() instanceof View) {
+ setClipChildrenOnViewTree((View) v.getParent(), parent, clipChildren);
+ }
+ }
+
+ /**
+ * Recursively call {@link ViewGroup#setClipChildren(boolean)} to restore clip children value
+ * set in {@link #setClipChildrenOnViewTree(View, ViewParent, boolean)} on view to its parent
+ * (direct or indirect) inclusive.
+ *
+ * Note that if parent is null or not a parent of the view, this method will be applied all the
+ * way to root view.
+ *
+ * @param v child view
+ * @param parent direct or indirect parent of child view
+ */
+ private static void restoreClipChildrenOnViewTree(
+ @Nullable View v, @Nullable ViewParent parent) {
+ if (v == null) {
+ return;
+ }
+ if (v instanceof ViewGroup) {
+ ViewGroup viewGroup = (ViewGroup) v;
+ Object viewTag = viewGroup.getTag(R.id.saved_clip_children_tag_id);
+ if (viewTag instanceof Boolean) {
+ viewGroup.setClipChildren((boolean) viewTag);
+ viewGroup.setTag(R.id.saved_clip_children_tag_id, null);
+ }
+ }
+
+ if (v == parent) {
+ return;
+ }
+
+ if (v.getParent() instanceof View) {
+ restoreClipChildrenOnViewTree((View) v.getParent(), parent);
+ }
+ }
+
+ /**
* Updates the total scroll range but does not update the UI.
*/
public void setShiftRange(float shiftRange) {
diff --git a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
index 00e89ba..1c67691 100644
--- a/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
+++ b/src/com/android/launcher3/allapps/BaseAllAppsContainerView.java
@@ -57,7 +57,6 @@
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.Insettable;
import com.android.launcher3.InsettableFrameLayout;
-import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.search.DefaultSearchAdapterProvider;
@@ -161,10 +160,8 @@
R.dimen.dynamic_grid_cell_border_spacing);
mHeaderProtectionColor = Themes.getAttrColor(context, R.attr.allappsHeaderProtectionColor);
- mWorkManager = new WorkProfileManager(
- mActivityContext.getSystemService(UserManager.class),
- this, LauncherPrefs.getPrefs(mActivityContext),
- mActivityContext.getStatsLogManager());
+ mWorkManager = new WorkProfileManager(mActivityContext.getSystemService(UserManager.class),
+ this, mActivityContext.getStatsLogManager());
mAH = Arrays.asList(null, null, null);
mNavBarScrimPaint = new Paint();
mNavBarScrimPaint.setColor(Themes.getAttrColor(context, R.attr.allAppsNavBarScrimColor));
@@ -808,7 +805,7 @@
}
@Override
- public void drawOnScrim(Canvas canvas) {
+ public void drawOnScrimWithScale(Canvas canvas, float scale) {
boolean isTablet = mActivityContext.getDeviceProfile().isTablet;
// Draw full background panel for tablets.
@@ -833,7 +830,9 @@
if (mHeaderPaint.getColor() == mScrimColor || mHeaderPaint.getColor() == 0) {
return;
}
- int bottom = getHeaderBottom() + getVisibleContainerView().getPaddingTop();
+ final float offset = (getVisibleContainerView().getHeight() * (1 - scale) / 2);
+ final float bottom =
+ scale * (getHeaderBottom() + getVisibleContainerView().getPaddingTop()) + offset;
FloatingHeaderView headerView = getFloatingHeaderView();
if (isTablet) {
// Start adding header protection if search bar or tabs will attach to the top.
diff --git a/src/com/android/launcher3/allapps/WorkEduCard.java b/src/com/android/launcher3/allapps/WorkEduCard.java
index b3245ee..b4cdc96 100644
--- a/src/com/android/launcher3/allapps/WorkEduCard.java
+++ b/src/com/android/launcher3/allapps/WorkEduCard.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.allapps;
+import static com.android.launcher3.LauncherPrefs.WORK_EDU_STEP;
import static com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.getTabWidth;
import android.content.Context;
@@ -85,8 +86,7 @@
@Override
public void onClick(View view) {
startAnimation(mDismissAnim);
- LauncherPrefs.getPrefs(getContext()).edit().putInt(WorkProfileManager.KEY_WORK_EDU_STEP,
- 1).apply();
+ LauncherPrefs.get(getContext()).put(WORK_EDU_STEP, 1);
}
@Override
diff --git a/src/com/android/launcher3/allapps/WorkModeSwitch.java b/src/com/android/launcher3/allapps/WorkModeSwitch.java
index 11ce738..3f2f21d 100644
--- a/src/com/android/launcher3/allapps/WorkModeSwitch.java
+++ b/src/com/android/launcher3/allapps/WorkModeSwitch.java
@@ -203,12 +203,10 @@
public void extend() {
mTextView.setVisibility(VISIBLE);
- mStatsLogManager.logger().log(LAUNCHER_WORK_FAB_BUTTON_EXTEND);
}
public void shrink(){
mTextView.setVisibility(GONE);
- mStatsLogManager.logger().log(LAUNCHER_WORK_FAB_BUTTON_COLLAPSE);
}
public int getScrollThreshold() {
diff --git a/src/com/android/launcher3/allapps/WorkProfileManager.java b/src/com/android/launcher3/allapps/WorkProfileManager.java
index 279f0d3..fa03905 100644
--- a/src/com/android/launcher3/allapps/WorkProfileManager.java
+++ b/src/com/android/launcher3/allapps/WorkProfileManager.java
@@ -15,6 +15,7 @@
*/
package com.android.launcher3.allapps;
+import static com.android.launcher3.LauncherPrefs.WORK_EDU_STEP;
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_DISABLED_CARD;
import static com.android.launcher3.allapps.BaseAllAppsAdapter.VIEW_TYPE_WORK_EDU_CARD;
import static com.android.launcher3.allapps.BaseAllAppsContainerView.AdapterHolder.MAIN;
@@ -26,7 +27,6 @@
import static com.android.launcher3.model.BgDataModel.Callbacks.FLAG_QUIET_MODE_ENABLED;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import android.content.SharedPreferences;
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
@@ -40,6 +40,7 @@
import androidx.annotation.RequiresApi;
import androidx.recyclerview.widget.RecyclerView;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.allapps.BaseAllAppsAdapter.AdapterItem;
@@ -86,14 +87,12 @@
@WorkProfileState
private int mCurrentState;
- private SharedPreferences mPreferences;
public WorkProfileManager(
- UserManager userManager, BaseAllAppsContainerView<?> allApps, SharedPreferences prefs,
+ UserManager userManager, BaseAllAppsContainerView<?> allApps,
StatsLogManager statsLogManager) {
mUserManager = userManager;
mAllApps = allApps;
- mPreferences = prefs;
mMatcher = mAllApps.mPersonalMatcher.negate();
mStatsLogManager = statsLogManager;
}
@@ -225,7 +224,7 @@
}
private boolean isEduSeen() {
- return mPreferences.getInt(KEY_WORK_EDU_STEP, 0) != 0;
+ return LauncherPrefs.get(mAllApps.getContext()).get(WORK_EDU_STEP) != 0;
}
private void onWorkFabClicked(View view) {
diff --git a/src/com/android/launcher3/anim/Interpolators.java b/src/com/android/launcher3/anim/Interpolators.java
index b55a1e4..e886543 100644
--- a/src/com/android/launcher3/anim/Interpolators.java
+++ b/src/com/android/launcher3/anim/Interpolators.java
@@ -56,6 +56,8 @@
public static final Interpolator DECELERATED_EASE = new PathInterpolator(0, 0, .2f, 1f);
public static final Interpolator ACCELERATED_EASE = new PathInterpolator(0.4f, 0, 1f, 1f);
+ public static final Interpolator PREDICTIVE_BACK_DECELERATED_EASE =
+ new PathInterpolator(0, 0, 0, 1f);
/**
* The default emphasized interpolator. Used for hero / emphasized movement of content.
diff --git a/src/com/android/launcher3/celllayout/CellLayoutLayoutParams.java b/src/com/android/launcher3/celllayout/CellLayoutLayoutParams.java
index abd4682..726ef05 100644
--- a/src/com/android/launcher3/celllayout/CellLayoutLayoutParams.java
+++ b/src/com/android/launcher3/celllayout/CellLayoutLayoutParams.java
@@ -29,29 +29,17 @@
*/
public class CellLayoutLayoutParams extends ViewGroup.MarginLayoutParams {
- public int screenId = -1;
+ private int mScreenId = -1;
- /**
- * Horizontal location of the item in the grid.
- */
@ViewDebug.ExportedProperty
- public int cellX;
+ private int mCellX;
- /**
- * Vertical location of the item in the grid.
- */
@ViewDebug.ExportedProperty
- public int cellY;
+ private int mCellY;
- /**
- * Temporary horizontal location of the item in the grid during reorder
- */
- public int tmpCellX;
+ private int mTmpCellX;
- /**
- * Temporary vertical location of the item in the grid during reorder
- */
- public int tmpCellY;
+ private int mTmpCellY;
/**
* Indicates that the temporary coordinates should be used to layout the items
@@ -105,24 +93,24 @@
public CellLayoutLayoutParams(CellLayoutLayoutParams source) {
super(source);
- this.cellX = source.cellX;
- this.cellY = source.cellY;
+ this.mCellX = source.getCellX();
+ this.mCellY = source.getCellY();
this.cellHSpan = source.cellHSpan;
this.cellVSpan = source.cellVSpan;
- this.screenId = source.screenId;
- this.tmpCellX = source.tmpCellX;
- this.tmpCellY = source.tmpCellY;
+ this.mScreenId = source.getScreenId();
+ this.mTmpCellX = source.getTmpCellX();
+ this.mTmpCellY = source.getTmpCellY();
this.useTmpCoords = source.useTmpCoords;
}
public CellLayoutLayoutParams(int cellX, int cellY, int cellHSpan, int cellVSpan,
int screenId) {
super(CellLayoutLayoutParams.MATCH_PARENT, CellLayoutLayoutParams.MATCH_PARENT);
- this.cellX = cellX;
- this.cellY = cellY;
+ this.mCellX = cellX;
+ this.mCellY = cellY;
this.cellHSpan = cellHSpan;
this.cellVSpan = cellVSpan;
- this.screenId = screenId;
+ this.mScreenId = screenId;
}
/**
@@ -148,8 +136,8 @@
if (isLockedToGrid) {
final int myCellHSpan = cellHSpan;
final int myCellVSpan = cellVSpan;
- int myCellX = useTmpCoords ? tmpCellX : cellX;
- int myCellY = useTmpCoords ? tmpCellY : cellY;
+ int myCellX = useTmpCoords ? getTmpCellX() : getCellX();
+ int myCellY = useTmpCoords ? getTmpCellY() : getCellY();
if (invertHorizontally) {
myCellX = colCount - myCellX - cellHSpan;
@@ -179,14 +167,66 @@
* Sets the position to the provided point
*/
public void setCellXY(Point point) {
- cellX = point.x;
- cellY = point.y;
+ setCellX(point.x);
+ setCellY(point.y);
}
/**
* @return the string representation of the position of the {@link CellLayoutLayoutParams}
*/
public String toString() {
- return "(" + this.cellX + ", " + this.cellY + ")";
+ return "(" + this.getCellX() + ", " + this.getCellY() + ")";
+ }
+
+ public int getScreenId() {
+ return mScreenId;
+ }
+
+ public void setScreenId(int screenId) {
+ this.mScreenId = screenId;
+ }
+
+ /**
+ * Horizontal location of the item in the grid.
+ */
+ public int getCellX() {
+ return mCellX;
+ }
+
+ public void setCellX(int cellX) {
+ this.mCellX = cellX;
+ }
+
+ /**
+ * Vertical location of the item in the grid.
+ */
+ public int getCellY() {
+ return mCellY;
+ }
+
+ public void setCellY(int cellY) {
+ this.mCellY = cellY;
+ }
+
+ /**
+ * Temporary horizontal location of the item in the grid during reorder
+ */
+ public int getTmpCellX() {
+ return mTmpCellX;
+ }
+
+ public void setTmpCellX(int tmpCellX) {
+ this.mTmpCellX = tmpCellX;
+ }
+
+ /**
+ * Temporary vertical location of the item in the grid during reorder
+ */
+ public int getTmpCellY() {
+ return mTmpCellY;
+ }
+
+ public void setTmpCellY(int tmpCellY) {
+ this.mTmpCellY = tmpCellY;
}
}
diff --git a/src/com/android/launcher3/config/FeatureFlags.java b/src/com/android/launcher3/config/FeatureFlags.java
index 082f6a1..daf83d4 100644
--- a/src/com/android/launcher3/config/FeatureFlags.java
+++ b/src/com/android/launcher3/config/FeatureFlags.java
@@ -273,6 +273,10 @@
"ENABLE_SEARCH_RESULT_BACKGROUND_DRAWABLES", false,
"Enable option to replace decorator-based search result backgrounds with drawables");
+ public static final BooleanFlag ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION = new DeviceFlag(
+ "ENABLE_SEARCH_RESULT_LAUNCH_TRANSITION", false,
+ "Enable option to launch search results using the new standardized transitions");
+
public static final BooleanFlag TWO_PREDICTED_ROWS_ALL_APPS_SEARCH = new DeviceFlag(
"TWO_PREDICTED_ROWS_ALL_APPS_SEARCH", false,
"Use 2 rows of app predictions in All Apps search zero-state");
@@ -364,7 +368,7 @@
"ENABLE_DEVICE_PROFILE_LOGGING", false, "Allows DeviceProfile logging");
public static final BooleanFlag ENABLE_LAUNCH_FROM_STAGED_APP = getDebugFlag(
- "ENABLE_LAUNCH_FROM_STAGED_APP", false,
+ "ENABLE_LAUNCH_FROM_STAGED_APP", true,
"Enable the ability to tap a staged app during split select to launch it in full screen"
);
diff --git a/src/com/android/launcher3/folder/FolderIcon.java b/src/com/android/launcher3/folder/FolderIcon.java
index dd00f07..60442f4 100644
--- a/src/com/android/launcher3/folder/FolderIcon.java
+++ b/src/com/android/launcher3/folder/FolderIcon.java
@@ -281,7 +281,7 @@
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) getLayoutParams();
CellLayout cl = (CellLayout) getParent().getParent();
- mBackground.animateToAccept(cl, lp.cellX, lp.cellY);
+ mBackground.animateToAccept(cl, lp.getCellX(), lp.getCellY());
mOpenAlarm.setOnAlarmListener(mOnOpenListener);
if (SPRING_LOADING_ENABLED &&
((dragInfo instanceof WorkspaceItemFactory)
diff --git a/src/com/android/launcher3/folder/FolderPagedView.java b/src/com/android/launcher3/folder/FolderPagedView.java
index b89c715..10a2637 100644
--- a/src/com/android/launcher3/folder/FolderPagedView.java
+++ b/src/com/android/launcher3/folder/FolderPagedView.java
@@ -224,8 +224,8 @@
textView.setLayoutParams(new CellLayoutLayoutParams(
item.cellX, item.cellY, item.spanX, item.spanY, item.screenId));
} else {
- lp.cellX = item.cellX;
- lp.cellY = item.cellY;
+ lp.setCellX(item.cellX);
+ lp.setCellY(item.cellY);
lp.cellHSpan = lp.cellVSpan = 1;
}
return textView;
diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
index feadafa..9426c22 100644
--- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
+++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java
@@ -1,8 +1,7 @@
package com.android.launcher3.graphics;
-import static com.android.launcher3.LauncherPrefs.getPrefs;
+import static com.android.launcher3.LauncherPrefs.THEMED_ICONS;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
-import static com.android.launcher3.util.Themes.KEY_THEMED_ICONS;
import static com.android.launcher3.util.Themes.isThemedIconEnabled;
import android.annotation.TargetApi;
@@ -25,6 +24,7 @@
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.InvariantDeviceProfile.GridOption;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.Utilities;
import com.android.launcher3.util.Executors;
@@ -142,9 +142,8 @@
}
case ICON_THEMED:
case SET_ICON_THEMED: {
- getPrefs(getContext()).edit()
- .putBoolean(KEY_THEMED_ICONS, values.getAsBoolean(BOOLEAN_VALUE))
- .apply();
+ LauncherPrefs.get(getContext())
+ .put(THEMED_ICONS, values.getAsBoolean(BOOLEAN_VALUE));
return 1;
}
default:
diff --git a/src/com/android/launcher3/graphics/PreloadIconDrawable.java b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
index de47cb5..9001a52 100644
--- a/src/com/android/launcher3/graphics/PreloadIconDrawable.java
+++ b/src/com/android/launcher3/graphics/PreloadIconDrawable.java
@@ -28,6 +28,7 @@
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
+import android.graphics.ColorFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
@@ -41,6 +42,7 @@
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.AnimatedFloat;
+import com.android.launcher3.anim.AnimatorListeners;
import com.android.launcher3.icons.FastBitmapDrawable;
import com.android.launcher3.icons.GraphicsUtils;
import com.android.launcher3.model.data.ItemInfoWithIcon;
@@ -53,7 +55,7 @@
/**
* Extension of {@link FastBitmapDrawable} which shows a progress bar around the icon.
*/
-public class PreloadIconDrawable extends FastBitmapDrawable implements Runnable {
+public class PreloadIconDrawable extends FastBitmapDrawable {
private static final Property<PreloadIconDrawable, Float> INTERNAL_STATE =
new Property<PreloadIconDrawable, Float>(Float.TYPE, "internalStateProgress") {
@@ -78,16 +80,19 @@
// The smaller the number, the faster the animation would be.
// Duration = COMPLETE_ANIM_FRACTION * DURATION_SCALE
- private static final float COMPLETE_ANIM_FRACTION = 0.3f;
+ private static final float COMPLETE_ANIM_FRACTION = 1f;
private static final float SMALL_SCALE = ENABLE_DOWNLOAD_APP_UX_V2.get() ? 0.867f : 0.7f;
- private static final float PROGRESS_STROKE_SCALE = 0.075f;
+ private static final float PROGRESS_STROKE_SCALE = ENABLE_DOWNLOAD_APP_UX_V2.get()
+ ? 0.0655f
+ : 0.075f;
+ private static final float PROGRESS_BOUNDS_SCALE = 0.075f;
private static final int PRELOAD_ACCENT_COLOR_INDEX = 0;
private static final int PRELOAD_BACKGROUND_COLOR_INDEX = 1;
private static final int ALPHA_DURATION_MILLIS = 3000;
- private static final float OVERLAY_ALPHA_RANGE = 127.5f;
+ private static final int OVERLAY_ALPHA_RANGE = 127;
private static final long WAVE_MOTION_DELAY_FACTOR_MILLIS = 100;
private static final WeakHashMap<Integer, PorterDuffColorFilter> COLOR_FILTER_MAP =
new WeakHashMap<>();
@@ -111,19 +116,17 @@
private final int mSystemBackgroundColor;
private final boolean mIsDarkMode;
- private int mTrackAlpha;
private float mTrackLength;
private boolean mRanFinishAnimation;
-
private final int mRefreshRateMillis;
- private final AnimatedFloat mIconScale = new AnimatedFloat(this::invalidateSelf);
- private final AnimatedFloat mOverlayAlpha = new AnimatedFloat(this::updateOverlayAlpha);
- private boolean mShouldAnimateScaleAndAlpha;
// Progress of the internal state. [0, 1] indicates the fraction of completed progress,
// [1, (1 + COMPLETE_ANIM_FRACTION)] indicates the progress of zoom animation.
private float mInternalStateProgress;
+ // This multiplier is used to animate scale when going from 0 to non-zero and expanding
+ private final Runnable mInvalidateRunnable = this::invalidateSelf;
+ private final AnimatedFloat mIconScaleMultiplier = new AnimatedFloat(mInvalidateRunnable);
private ObjectAnimator mCurrentAnim;
@@ -160,10 +163,7 @@
mRefreshRateMillis = refreshRateMillis;
// If it's a pending app we will animate scale and alpha when it's no longer pending.
- if (ENABLE_DOWNLOAD_APP_UX_V2.get() && info.getProgressLevel() == 0) {
- mShouldAnimateScaleAndAlpha = true;
- mOverlayAlpha.updateValue(127);
- }
+ mIconScaleMultiplier.updateValue(info.getProgressLevel() == 0 ? 0 : 1);
setLevel(info.getProgressLevel());
setIsStartable(info.isAppStartable());
@@ -173,14 +173,17 @@
protected void onBoundsChange(Rect bounds) {
super.onBoundsChange(bounds);
- float progressWidth = PROGRESS_STROKE_SCALE * bounds.width();
+
+ float progressWidth = bounds.width() * (ENABLE_DOWNLOAD_APP_UX_V2.get()
+ ? PROGRESS_BOUNDS_SCALE
+ : PROGRESS_STROKE_SCALE);
mTmpMatrix.setScale(
(bounds.width() - 2 * progressWidth) / DEFAULT_PATH_SIZE,
(bounds.height() - 2 * progressWidth) / DEFAULT_PATH_SIZE);
mTmpMatrix.postTranslate(bounds.left + progressWidth, bounds.top + progressWidth);
mShapePath.transform(mTmpMatrix, mScaledTrackPath);
- mProgressPaint.setStrokeWidth(progressWidth);
+ mProgressPaint.setStrokeWidth(PROGRESS_STROKE_SCALE * bounds.width());
mPathMeasure.setPath(mScaledTrackPath, true);
mTrackLength = mPathMeasure.getLength();
@@ -195,26 +198,35 @@
return;
}
- // Draw background.
- mProgressPaint.setStyle(Paint.Style.FILL_AND_STROKE);
- mProgressPaint.setColor(mSystemBackgroundColor);
- canvas.drawPath(mScaledTrackPath, mProgressPaint);
+ if (!ENABLE_DOWNLOAD_APP_UX_V2.get() && mInternalStateProgress > 0) {
+ // Draw background.
+ mProgressPaint.setStyle(Paint.Style.FILL_AND_STROKE);
+ mProgressPaint.setColor(mSystemBackgroundColor);
+ canvas.drawPath(mScaledTrackPath, mProgressPaint);
+ }
- // Draw track and progress.
- mProgressPaint.setStyle(Paint.Style.STROKE);
- mProgressPaint.setColor(mIsStartable ? mIndicatorColor : mSystemAccentColor);
- mProgressPaint.setAlpha(TRACK_ALPHA);
- canvas.drawPath(mScaledTrackPath, mProgressPaint);
- mProgressPaint.setAlpha(mTrackAlpha);
- canvas.drawPath(mScaledProgressPath, mProgressPaint);
+ if (!ENABLE_DOWNLOAD_APP_UX_V2.get() || mInternalStateProgress > 0) {
+ // Draw track and progress.
+ mProgressPaint.setStyle(Paint.Style.STROKE);
+ mProgressPaint.setColor(mSystemAccentColor);
+ mProgressPaint.setAlpha(TRACK_ALPHA);
+ canvas.drawPath(mScaledTrackPath, mProgressPaint);
+ mProgressPaint.setAlpha(MAX_PAINT_ALPHA);
+ canvas.drawPath(mScaledProgressPath, mProgressPaint);
+ }
int saveCount = canvas.save();
- canvas.scale(
- mIconScale.value, mIconScale.value, bounds.exactCenterX(), bounds.exactCenterY());
+ float scale = ENABLE_DOWNLOAD_APP_UX_V2.get()
+ ? 1 - mIconScaleMultiplier.value * (1 - SMALL_SCALE)
+ : SMALL_SCALE;
+ canvas.scale(scale, scale, bounds.exactCenterX(), bounds.exactCenterY());
+
+ ColorFilter filter = getOverlayFilter();
+ mPaint.setColorFilter(filter);
super.drawInternal(canvas, bounds);
canvas.restoreToCount(saveCount);
- if (ENABLE_DOWNLOAD_APP_UX_V2.get() && mInternalStateProgress == 0) {
+ if (ENABLE_DOWNLOAD_APP_UX_V2.get() && filter != null) {
reschedule();
}
}
@@ -232,7 +244,7 @@
@Override
protected boolean onLevelChange(int level) {
// Run the animation if we have already been bound.
- updateInternalState(level * 0.01f, getBounds().width() > 0, false);
+ updateInternalState(level * 0.01f, false, null);
return true;
}
@@ -240,12 +252,18 @@
* Runs the finish animation if it is has not been run after last call to
* {@link #onLevelChange}
*/
- public void maybePerformFinishedAnimation() {
+ public void maybePerformFinishedAnimation(
+ PreloadIconDrawable oldIcon, Runnable onFinishCallback) {
+
+ if (oldIcon.mInternalStateProgress >= 1) {
+ mInternalStateProgress = oldIcon.mInternalStateProgress;
+ }
+
// If the drawable was recently initialized, skip the progress animation.
if (mInternalStateProgress == 0) {
mInternalStateProgress = 1;
}
- updateInternalState(1 + COMPLETE_ANIM_FRACTION, true, true);
+ updateInternalState(1 + COMPLETE_ANIM_FRACTION, true, onFinishCallback);
}
public boolean hasNotCompleted() {
@@ -260,26 +278,29 @@
}
}
- private void updateInternalState(float finalProgress, boolean shouldAnimate, boolean isFinish) {
+ private void updateInternalState(
+ float finalProgress, boolean isFinish, Runnable onFinishCallback) {
if (mCurrentAnim != null) {
mCurrentAnim.cancel();
mCurrentAnim = null;
}
- if (Float.compare(finalProgress, mInternalStateProgress) == 0) {
- return;
- }
- if (finalProgress < mInternalStateProgress) {
- shouldAnimate = false;
- }
- if (!shouldAnimate || mRanFinishAnimation) {
+ boolean animateProgress =
+ finalProgress >= mInternalStateProgress && getBounds().width() > 0;
+ if (!animateProgress || mRanFinishAnimation) {
setInternalProgress(finalProgress);
+ if (isFinish && onFinishCallback != null) {
+ onFinishCallback.run();
+ }
} else {
mCurrentAnim = ObjectAnimator.ofFloat(this, INTERNAL_STATE, finalProgress);
mCurrentAnim.setDuration(
(long) ((finalProgress - mInternalStateProgress) * DURATION_SCALE));
mCurrentAnim.setInterpolator(LINEAR);
if (isFinish) {
+ if (onFinishCallback != null) {
+ mCurrentAnim.addListener(AnimatorListeners.forEndCallback(onFinishCallback));
+ }
mCurrentAnim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
@@ -297,62 +318,38 @@
* - icon with pending motion
* - progress track is not visible
* - progress bar is not visible
- * for progress < 1
+ * for progress < 1:
* - icon without pending motion
* - progress track is visible
* - progress bar is visible. Progress bar is drawn as a fraction of
* {@link #mScaledTrackPath}.
* @see PathMeasure#getSegment(float, float, Path, boolean)
- * for 1 <= progress < (1 + COMPLETE_ANIM_FRACTION)
- * - we calculate fraction of progress in the above range
- * - progress track is drawn with alpha based on fraction
- * - progress bar is drawn at 100% with alpha based on fraction
- * - icon is scaled up based on fraction and is drawn in enabled state
- * for progress >= (1 + COMPLETE_ANIM_FRACTION)
- * - only icon is drawn in normal state
+ * for progress > 1:
+ * - scale the icon back to full size
*/
private void setInternalProgress(float progress) {
// Animate scale and alpha from pending to downloading state.
- if (ENABLE_DOWNLOAD_APP_UX_V2.get()
- && mShouldAnimateScaleAndAlpha && mInternalStateProgress == 0 && progress > 0) {
- Animator iconScaleAnimator = mIconScale.animateToValue(SMALL_SCALE);
+ if (ENABLE_DOWNLOAD_APP_UX_V2.get() && progress > 0 && mInternalStateProgress == 0) {
+ // Progress is changing for the first time, animate the icon scale
+ Animator iconScaleAnimator = mIconScaleMultiplier.animateToValue(1);
iconScaleAnimator.setDuration(SCALE_AND_ALPHA_ANIM_DURATION);
iconScaleAnimator.setInterpolator(EMPHASIZED);
iconScaleAnimator.start();
-
- Animator overlayAlphaAnimator = mOverlayAlpha.animateToValue(0);
- overlayAlphaAnimator.setDuration(SCALE_AND_ALPHA_ANIM_DURATION);
- overlayAlphaAnimator.setInterpolator(EMPHASIZED);
- overlayAlphaAnimator.start();
}
mInternalStateProgress = progress;
if (progress <= 0) {
- mIconScale.updateValue(ENABLE_DOWNLOAD_APP_UX_V2.get() ? 1 : SMALL_SCALE);
- mScaledTrackPath.reset();
- mTrackAlpha = MAX_PAINT_ALPHA;
- } else if (progress < 1) {
- mPathMeasure.getSegment(0, progress * mTrackLength, mScaledProgressPath, true);
- if (ENABLE_DOWNLOAD_APP_UX_V2.get()) {
- mPathMeasure.getSegment(0, mTrackLength, mScaledTrackPath, true);
+ if (!ENABLE_DOWNLOAD_APP_UX_V2.get()) {
+ mScaledTrackPath.reset();
}
-
- if (!ENABLE_DOWNLOAD_APP_UX_V2.get() || !mShouldAnimateScaleAndAlpha) {
- mIconScale.updateValue(SMALL_SCALE);
- }
- mTrackAlpha = MAX_PAINT_ALPHA;
+ mIconScaleMultiplier.updateValue(0);
} else {
- setIsDisabled(mItem.isDisabled());
- mScaledTrackPath.set(mScaledProgressPath);
- float fraction = (progress - 1) / COMPLETE_ANIM_FRACTION;
-
- if (fraction >= 1) {
- // Animation has completed
- mIconScale.updateValue(1);
- mTrackAlpha = 0;
- } else {
- mTrackAlpha = Math.round((1 - fraction) * MAX_PAINT_ALPHA);
- mIconScale.updateValue(SMALL_SCALE + (1 - SMALL_SCALE) * fraction);
+ mPathMeasure.getSegment(
+ 0, Math.min(progress, 1) * mTrackLength, mScaledProgressPath, true);
+ if (progress > 1 && ENABLE_DOWNLOAD_APP_UX_V2.get()) {
+ // map the scale back to original value
+ mIconScaleMultiplier.updateValue(Utilities.mapBoundToRange(
+ progress - 1, 0, COMPLETE_ANIM_FRACTION, 1, 0, EMPHASIZED));
}
}
invalidateSelf();
@@ -393,71 +390,48 @@
}
@Override
- public void run() {
- if (!ENABLE_DOWNLOAD_APP_UX_V2.get() || mInternalStateProgress > 0) {
- return;
- }
- if (!applyPendingIconOverlay()) {
- reschedule();
- }
- }
-
- @Override
public boolean setVisible(boolean visible, boolean restart) {
- boolean result = super.setVisible(visible, restart);
- if (visible) {
- reschedule();
- } else {
- unscheduleSelf(this);
+ if (!visible) {
+ unscheduleSelf(mInvalidateRunnable);
}
- return result;
+ return super.setVisible(visible, restart);
}
private void reschedule() {
- unscheduleSelf(this);
-
+ unscheduleSelf(mInvalidateRunnable);
if (!isVisible()) {
return;
}
-
final long upTime = SystemClock.uptimeMillis();
- scheduleSelf(this, upTime - ((upTime % mRefreshRateMillis)) + mRefreshRateMillis);
+ scheduleSelf(mInvalidateRunnable,
+ upTime - ((upTime % mRefreshRateMillis)) + mRefreshRateMillis);
}
-
/**
- * Apply an overlay on the pending icon with cascading motion based on its position.
- * Returns {@code true} if the icon alpha is updated, so that we re-draw.
+ * Returns a color filter to be used as an overlay on the pending icon with cascading motion
+ * based on its position.
*/
- private boolean applyPendingIconOverlay() {
+ private ColorFilter getOverlayFilter() {
+ if (!ENABLE_DOWNLOAD_APP_UX_V2.get() || mInternalStateProgress > 0) {
+ // If the download has started, we do no need to animate
+ return null;
+ }
long waveMotionDelay = (mItem.cellX * WAVE_MOTION_DELAY_FACTOR_MILLIS)
+ (mItem.cellY * WAVE_MOTION_DELAY_FACTOR_MILLIS);
long time = SystemClock.uptimeMillis();
- float newAlpha = Utilities.mapBoundToRange(
- (float) (time + waveMotionDelay) % ALPHA_DURATION_MILLIS,
+ int alpha = (int) Utilities.mapBoundToRange(
+ (int) ((time + waveMotionDelay) % ALPHA_DURATION_MILLIS),
0,
ALPHA_DURATION_MILLIS,
0,
- MAX_PAINT_ALPHA,
+ OVERLAY_ALPHA_RANGE * 2,
LINEAR);
- if (newAlpha > OVERLAY_ALPHA_RANGE) {
- newAlpha = (OVERLAY_ALPHA_RANGE - (newAlpha % OVERLAY_ALPHA_RANGE));
+ if (alpha > OVERLAY_ALPHA_RANGE) {
+ alpha = (OVERLAY_ALPHA_RANGE - (alpha % OVERLAY_ALPHA_RANGE));
}
-
- boolean invalidate = false;
- if ((int) mOverlayAlpha.value != newAlpha) {
- mOverlayAlpha.updateValue(newAlpha);
- invalidate = true;
- }
- return invalidate;
- }
-
- private void updateOverlayAlpha() {
int overlayColor = mIsDarkMode ? 0 : 255;
- int currArgb =
- Color.argb((int) mOverlayAlpha.value, overlayColor, overlayColor, overlayColor);
- mPaint.setColorFilter(COLOR_FILTER_MAP.computeIfAbsent(currArgb, FILTER_FACTORY));
- invalidateSelf();
+ int currArgb = Color.argb(alpha, overlayColor, overlayColor, overlayColor);
+ return COLOR_FILTER_MAP.computeIfAbsent(currArgb, FILTER_FACTORY);
}
protected static class PreloadIconConstantState extends FastBitmapConstantState {
diff --git a/src/com/android/launcher3/logging/StatsLogManager.java b/src/com/android/launcher3/logging/StatsLogManager.java
index 2159c6b..5f6df27 100644
--- a/src/com/android/launcher3/logging/StatsLogManager.java
+++ b/src/com/android/launcher3/logging/StatsLogManager.java
@@ -558,14 +558,11 @@
+ "result page etc.")
LAUNCHER_ALLAPPS_SCROLLED(985),
- @UiEvent(doc = "User scrolled up on one of the all apps surfaces such as A-Z list, search "
- + "result page etc.")
- LAUNCHER_ALLAPPS_SCROLLED_UP(1229),
+ @UiEvent(doc = "User scrolled up on the all apps personal A-Z list.")
+ LAUNCHER_ALLAPPS_PERSONAL_SCROLLED_UP(1287),
- @UiEvent(doc =
- "User scrolled down on one of the all apps surfaces such as A-Z list, search "
- + "result page etc.")
- LAUNCHER_ALLAPPS_SCROLLED_DOWN(1230),
+ @UiEvent(doc = "User scrolled down on the all apps personal A-Z list.")
+ LAUNCHER_ALLAPPS_PERSONAL_SCROLLED_DOWN(1288),
@UiEvent(doc = "User scrolled on one of the all apps surfaces such as A-Z list, search "
+ "result page etc and we don't know the direction since user came back to "
@@ -626,11 +623,19 @@
@UiEvent(doc = "User has invoked split to left half with a keyboard shortcut.")
LAUNCHER_KEYBOARD_SHORTCUT_SPLIT_LEFT_TOP(1233),
- @UiEvent(doc = "User has collapsed the work FAB button by swiping down")
+ @UiEvent(doc = "User has collapsed the work FAB button by scrolling down in the all apps"
+ + " work A-Z list.")
LAUNCHER_WORK_FAB_BUTTON_COLLAPSE(1276),
- @UiEvent(doc = "User has collapsed the work FAB button by swiping up")
+ @UiEvent(doc = "User has collapsed the work FAB button by scrolling up in the all apps"
+ + " work A-Z list.")
LAUNCHER_WORK_FAB_BUTTON_EXTEND(1277),
+
+ @UiEvent(doc = "User scrolled down on the search result page.")
+ LAUNCHER_ALLAPPS_SEARCH_SCROLLED_DOWN(1285),
+
+ @UiEvent(doc = "User scrolled up on the search result page.")
+ LAUNCHER_ALLAPPS_SEARCH_SCROLLED_UP(1286),
;
// ADD MORE
diff --git a/src/com/android/launcher3/model/BaseLoaderResults.java b/src/com/android/launcher3/model/BaseLauncherBinder.java
similarity index 95%
rename from src/com/android/launcher3/model/BaseLoaderResults.java
rename to src/com/android/launcher3/model/BaseLauncherBinder.java
index 8c6428b..9f8db51 100644
--- a/src/com/android/launcher3/model/BaseLoaderResults.java
+++ b/src/com/android/launcher3/model/BaseLauncherBinder.java
@@ -47,12 +47,11 @@
import java.util.concurrent.Executor;
/**
- * Base Helper class to handle results of {@link com.android.launcher3.model.LoaderTask}.
+ * Binds the results of {@link com.android.launcher3.model.LoaderTask} to the Callbacks objects.
*/
-public abstract class BaseLoaderResults {
+public abstract class BaseLauncherBinder {
- protected static final String TAG = "LoaderResults";
- protected static final int INVALID_SCREEN_ID = -1;
+ protected static final String TAG = "LauncherBinder";
private static final int ITEMS_CHUNK = 6; // batch size for the workspace icons
protected final LooperExecutor mUiExecutor;
@@ -65,7 +64,7 @@
private int mMyBindingId;
- public BaseLoaderResults(LauncherAppState app, BgDataModel dataModel,
+ public BaseLauncherBinder(LauncherAppState app, BgDataModel dataModel,
AllAppsList allAppsList, Callbacks[] callbacksList, LooperExecutor uiExecutor) {
mUiExecutor = uiExecutor;
mApp = app;
@@ -101,8 +100,14 @@
}
}
+ /**
+ * BindDeepShortcuts is abstract because it is a no-op for the go launcher.
+ */
public abstract void bindDeepShortcuts();
+ /**
+ * Binds the all apps results from LoaderTask to the callbacks UX.
+ */
public void bindAllApps() {
// shallow copy
AppInfo[] apps = mBgAllAppsList.copyData();
@@ -110,6 +115,9 @@
executeCallbacksTask(c -> c.bindAllApplications(apps, flags), mUiExecutor);
}
+ /**
+ * bindWidgets is abstract because it is a no-op for the go launcher.
+ */
public abstract void bindWidgets();
/**
@@ -160,6 +168,9 @@
});
}
+ /**
+ * Only used in LoaderTask.
+ */
public LooperIdleLock newIdleLock(Object lock) {
LooperIdleLock idleLock = new LooperIdleLock(lock, mUiExecutor.getLooper());
// If we are not binding or if the main looper is already idle, there is no reason to wait
diff --git a/src/com/android/launcher3/model/DeviceGridState.java b/src/com/android/launcher3/model/DeviceGridState.java
index 85d54c0..edc8c1b 100644
--- a/src/com/android/launcher3/model/DeviceGridState.java
+++ b/src/com/android/launcher3/model/DeviceGridState.java
@@ -17,7 +17,10 @@
package com.android.launcher3.model;
import static com.android.launcher3.InvariantDeviceProfile.DeviceType;
-import static com.android.launcher3.InvariantDeviceProfile.TYPE_PHONE;
+import static com.android.launcher3.LauncherPrefs.DB_FILE;
+import static com.android.launcher3.LauncherPrefs.DEVICE_TYPE;
+import static com.android.launcher3.LauncherPrefs.HOTSEAT_COUNT;
+import static com.android.launcher3.LauncherPrefs.WORKSPACE_SIZE;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_GRID_SIZE_2;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_GRID_SIZE_3;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_GRID_SIZE_4;
@@ -25,7 +28,6 @@
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_GRID_SIZE_6;
import android.content.Context;
-import android.content.SharedPreferences;
import android.text.TextUtils;
import com.android.launcher3.InvariantDeviceProfile;
@@ -58,11 +60,11 @@
}
public DeviceGridState(Context context) {
- SharedPreferences prefs = LauncherPrefs.getPrefs(context);
- mGridSizeString = prefs.getString(KEY_WORKSPACE_SIZE, "");
- mNumHotseat = prefs.getInt(KEY_HOTSEAT_COUNT, -1);
- mDeviceType = prefs.getInt(KEY_DEVICE_TYPE, TYPE_PHONE);
- mDbFile = prefs.getString(KEY_DB_FILE, "");
+ LauncherPrefs lp = LauncherPrefs.get(context);
+ mGridSizeString = lp.get(WORKSPACE_SIZE);
+ mNumHotseat = lp.get(HOTSEAT_COUNT);
+ mDeviceType = lp.get(DEVICE_TYPE);
+ mDbFile = lp.get(DB_FILE);
}
/**
@@ -90,12 +92,11 @@
* Stores the device state to shared preferences
*/
public void writeToPrefs(Context context) {
- LauncherPrefs.getPrefs(context).edit()
- .putString(KEY_WORKSPACE_SIZE, mGridSizeString)
- .putInt(KEY_HOTSEAT_COUNT, mNumHotseat)
- .putInt(KEY_DEVICE_TYPE, mDeviceType)
- .putString(KEY_DB_FILE, mDbFile)
- .apply();
+ LauncherPrefs.get(context).put(
+ WORKSPACE_SIZE.to(mGridSizeString),
+ HOTSEAT_COUNT.to(mNumHotseat),
+ DEVICE_TYPE.to(mDeviceType),
+ DB_FILE.to(mDbFile));
}
/**
diff --git a/src/com/android/launcher3/model/LoaderTask.java b/src/com/android/launcher3/model/LoaderTask.java
index 1d6971e..46a6a66 100644
--- a/src/com/android/launcher3/model/LoaderTask.java
+++ b/src/com/android/launcher3/model/LoaderTask.java
@@ -125,7 +125,7 @@
private FirstScreenBroadcast mFirstScreenBroadcast;
- private final LoaderResults mResults;
+ private final LauncherBinder mLauncherBinder;
private final LauncherApps mLauncherApps;
private final UserManager mUserManager;
@@ -145,12 +145,12 @@
private String mDbName;
public LoaderTask(LauncherAppState app, AllAppsList bgAllAppsList, BgDataModel dataModel,
- ModelDelegate modelDelegate, LoaderResults results) {
+ ModelDelegate modelDelegate, LauncherBinder launcherBinder) {
mApp = app;
mBgAllAppsList = bgAllAppsList;
mBgDataModel = dataModel;
mModelDelegate = modelDelegate;
- mResults = results;
+ mLauncherBinder = launcherBinder;
mLauncherApps = mApp.getContext().getSystemService(LauncherApps.class);
mUserManager = mApp.getContext().getSystemService(UserManager.class);
@@ -163,7 +163,7 @@
// Wait until the either we're stopped or the other threads are done.
// This way we don't start loading all apps until the workspace has settled
// down.
- LooperIdleLock idleLock = mResults.newIdleLock(this);
+ LooperIdleLock idleLock = mLauncherBinder.newIdleLock(this);
// Just in case mFlushingWorkerThread changes but we aren't woken up,
// wait no longer than 1sec at a time
while (!mStopped && idleLock.awaitLocked(1000));
@@ -221,7 +221,7 @@
}
verifyNotStopped();
- mResults.bindWorkspace(true /* incrementBindId */);
+ mLauncherBinder.bindWorkspace(true /* incrementBindId */);
logASplit(logger, "bindWorkspace");
mModelDelegate.workspaceLoadComplete();
@@ -245,7 +245,7 @@
logASplit(logger, "loadAllApps");
verifyNotStopped();
- mResults.bindAllApps();
+ mLauncherBinder.bindAllApps();
logASplit(logger, "bindAllApps");
verifyNotStopped();
@@ -271,7 +271,7 @@
logASplit(logger, "loadDeepShortcuts");
verifyNotStopped();
- mResults.bindDeepShortcuts();
+ mLauncherBinder.bindDeepShortcuts();
logASplit(logger, "bindDeepShortcuts");
verifyNotStopped();
@@ -290,7 +290,7 @@
logASplit(logger, "load widgets");
verifyNotStopped();
- mResults.bindWidgets();
+ mLauncherBinder.bindWidgets();
logASplit(logger, "bindWidgets");
verifyNotStopped();
diff --git a/src/com/android/launcher3/pm/InstallSessionHelper.java b/src/com/android/launcher3/pm/InstallSessionHelper.java
index 150bca4..db23566 100644
--- a/src/com/android/launcher3/pm/InstallSessionHelper.java
+++ b/src/com/android/launcher3/pm/InstallSessionHelper.java
@@ -16,8 +16,6 @@
package com.android.launcher3.pm;
-import static com.android.launcher3.LauncherPrefs.getPrefs;
-
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.LauncherApps;
@@ -34,6 +32,7 @@
import androidx.annotation.RequiresApi;
import androidx.annotation.WorkerThread;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.SessionCommitReceiver;
import com.android.launcher3.Utilities;
@@ -65,7 +64,7 @@
// Set<String> of session ids of promise icons that have been added to the home screen
// as FLAG_PROMISE_NEW_INSTALLS.
@NonNull
- protected static final String PROMISE_ICON_IDS = "promise_icon_ids";
+ public static final String PROMISE_ICON_IDS = "promise_icon_ids";
private static final boolean DEBUG = false;
@@ -102,7 +101,7 @@
return mPromiseIconIds;
}
mPromiseIconIds = IntSet.wrap(IntArray.fromConcatString(
- getPrefs(mAppContext).getString(PROMISE_ICON_IDS, "")));
+ LauncherPrefs.get(mAppContext).get(LauncherPrefs.PROMISE_ICON_IDS)));
IntArray existingIds = new IntArray();
for (SessionInfo info : getActiveSessions().values()) {
@@ -146,9 +145,8 @@
}
private void updatePromiseIconPrefs() {
- getPrefs(mAppContext).edit()
- .putString(PROMISE_ICON_IDS, getPromiseIconIds().getArray().toConcatString())
- .apply();
+ LauncherPrefs.get(mAppContext).put(LauncherPrefs.PROMISE_ICON_IDS,
+ getPromiseIconIds().getArray().toConcatString());
}
@Nullable
diff --git a/src/com/android/launcher3/provider/RestoreDbTask.java b/src/com/android/launcher3/provider/RestoreDbTask.java
index 5e97b2d..2a452be 100644
--- a/src/com/android/launcher3/provider/RestoreDbTask.java
+++ b/src/com/android/launcher3/provider/RestoreDbTask.java
@@ -17,13 +17,14 @@
package com.android.launcher3.provider;
import static com.android.launcher3.InvariantDeviceProfile.TYPE_MULTI_DISPLAY;
-import static com.android.launcher3.InvariantDeviceProfile.TYPE_PHONE;
+import static com.android.launcher3.LauncherPrefs.APP_WIDGET_IDS;
+import static com.android.launcher3.LauncherPrefs.OLD_APP_WIDGET_IDS;
+import static com.android.launcher3.LauncherPrefs.RESTORE_DEVICE;
import static com.android.launcher3.provider.LauncherDbUtils.dropTable;
import android.app.backup.BackupManager;
import android.content.ContentValues;
import android.content.Context;
-import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.UserHandle;
@@ -62,13 +63,13 @@
public class RestoreDbTask {
private static final String TAG = "RestoreDbTask";
- private static final String RESTORED_DEVICE_TYPE = "restored_task_pending";
+ public static final String RESTORED_DEVICE_TYPE = "restored_task_pending";
private static final String INFO_COLUMN_NAME = "name";
private static final String INFO_COLUMN_DEFAULT_VALUE = "dflt_value";
- private static final String APPWIDGET_OLD_IDS = "appwidget_old_ids";
- private static final String APPWIDGET_IDS = "appwidget_ids";
+ public static final String APPWIDGET_OLD_IDS = "appwidget_old_ids";
+ public static final String APPWIDGET_IDS = "appwidget_ids";
/**
* Tries to restore the backup DB if needed
@@ -87,7 +88,7 @@
// Set is pending to false irrespective of the result, so that it doesn't get
// executed again.
- LauncherPrefs.getPrefs(context).edit().remove(RESTORED_DEVICE_TYPE).commit();
+ LauncherPrefs.get(context).removeSync(RESTORE_DEVICE);
idp.reinitializeAfterRestore(context);
}
@@ -240,8 +241,7 @@
}
// If restored from a single display backup, remove gaps between screenIds
- if (LauncherPrefs.getPrefs(context).getInt(RESTORED_DEVICE_TYPE, TYPE_PHONE)
- != TYPE_MULTI_DISPLAY) {
+ if (LauncherPrefs.get(context).get(RESTORE_DEVICE) != TYPE_MULTI_DISPLAY) {
removeScreenIdGaps(db);
}
@@ -339,7 +339,7 @@
}
public static boolean isPending(Context context) {
- return LauncherPrefs.getPrefs(context).contains(RESTORED_DEVICE_TYPE);
+ return LauncherPrefs.get(context).has(RESTORE_DEVICE);
}
/**
@@ -347,34 +347,31 @@
*/
public static void setPending(Context context) {
FileLog.d(TAG, "Restore data received through full backup ");
- LauncherPrefs.getPrefs(context).edit()
- .putInt(RESTORED_DEVICE_TYPE, new DeviceGridState(context).getDeviceType())
- .commit();
+ LauncherPrefs.get(context)
+ .putSync(RESTORE_DEVICE.to(new DeviceGridState(context).getDeviceType()));
}
private void restoreAppWidgetIdsIfExists(Context context) {
- SharedPreferences prefs = LauncherPrefs.getPrefs(context);
- if (prefs.contains(APPWIDGET_OLD_IDS) && prefs.contains(APPWIDGET_IDS)) {
+ LauncherPrefs lp = LauncherPrefs.get(context);
+ if (lp.has(APP_WIDGET_IDS, OLD_APP_WIDGET_IDS)) {
LauncherWidgetHolder holder = LauncherWidgetHolder.newInstance(context);
AppWidgetsRestoredReceiver.restoreAppWidgetIds(context,
- IntArray.fromConcatString(prefs.getString(APPWIDGET_OLD_IDS, "")).toArray(),
- IntArray.fromConcatString(prefs.getString(APPWIDGET_IDS, "")).toArray(),
+ IntArray.fromConcatString(lp.get(OLD_APP_WIDGET_IDS)).toArray(),
+ IntArray.fromConcatString(lp.get(APP_WIDGET_IDS)).toArray(),
holder);
holder.destroy();
} else {
FileLog.d(TAG, "No app widget ids to restore.");
}
- prefs.edit().remove(APPWIDGET_OLD_IDS)
- .remove(APPWIDGET_IDS).apply();
+ lp.remove(APP_WIDGET_IDS, OLD_APP_WIDGET_IDS);
}
public static void setRestoredAppWidgetIds(Context context, @NonNull int[] oldIds,
@NonNull int[] newIds) {
- LauncherPrefs.getPrefs(context).edit()
- .putString(APPWIDGET_OLD_IDS, IntArray.wrap(oldIds).toConcatString())
- .putString(APPWIDGET_IDS, IntArray.wrap(newIds).toConcatString())
- .commit();
+ LauncherPrefs.get(context).putSync(
+ OLD_APP_WIDGET_IDS.to(IntArray.wrap(oldIds).toConcatString()),
+ APP_WIDGET_IDS.to(IntArray.wrap(newIds).toConcatString()));
}
}
diff --git a/src/com/android/launcher3/settings/SettingsActivity.java b/src/com/android/launcher3/settings/SettingsActivity.java
index 7ab3013..d2f82c2 100644
--- a/src/com/android/launcher3/settings/SettingsActivity.java
+++ b/src/com/android/launcher3/settings/SettingsActivity.java
@@ -93,7 +93,8 @@
WindowCompat.setDecorFitsSystemWindows(getWindow(), false);
Intent intent = getIntent();
- if (intent.hasExtra(EXTRA_FRAGMENT) || intent.hasExtra(EXTRA_FRAGMENT_ARGS)) {
+ if (intent.hasExtra(EXTRA_FRAGMENT) || intent.hasExtra(EXTRA_FRAGMENT_ARGS)
+ || intent.hasExtra(EXTRA_FRAGMENT_ARG_KEY)) {
getActionBar().setDisplayHomeAsUpEnabled(true);
}
diff --git a/src/com/android/launcher3/statemanager/StateManager.java b/src/com/android/launcher3/statemanager/StateManager.java
index ad1e7f0..34ac8c2 100644
--- a/src/com/android/launcher3/statemanager/StateManager.java
+++ b/src/com/android/launcher3/statemanager/StateManager.java
@@ -28,6 +28,8 @@
import android.os.Handler;
import android.os.Looper;
+import androidx.annotation.FloatRange;
+
import com.android.launcher3.anim.AnimationSuccessListener;
import com.android.launcher3.anim.AnimatorPlaybackController;
import com.android.launcher3.anim.PendingAnimation;
@@ -195,6 +197,21 @@
}
}
+ /** Handles backProgress in predictive back gesture by passing it to state handlers. */
+ public void onBackProgressed(
+ STATE_TYPE toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) {
+ for (StateHandler handler : getStateHandlers()) {
+ handler.onBackProgressed(toState, backProgress);
+ }
+ }
+
+ /** Handles back cancelled event in predictive back gesture by passing it to state handlers. */
+ public void onBackCancelled(STATE_TYPE toState) {
+ for (StateHandler handler : getStateHandlers()) {
+ handler.onBackCancelled(toState);
+ }
+ }
+
private void goToState(
STATE_TYPE state, boolean animated, long delay, AnimatorListener listener) {
animated &= areAnimatorsEnabled();
@@ -586,6 +603,13 @@
*/
void setStateWithAnimation(
STATE_TYPE toState, StateAnimationConfig config, PendingAnimation animation);
+
+ /** Handles backProgress in predictive back gesture for target state. */
+ default void onBackProgressed(
+ STATE_TYPE toState, @FloatRange(from = 0.0, to = 1.0) float backProgress) {};
+
+ /** Handles back cancelled event in predictive back gesture for target state. */
+ default void onBackCancelled(STATE_TYPE toState) {};
}
public interface StateListener<STATE_TYPE> {
diff --git a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
index 8f7a4ec..c499e35 100644
--- a/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
+++ b/src/com/android/launcher3/touch/AbstractStateChangeTouchController.java
@@ -32,7 +32,6 @@
import android.animation.Animator.AnimatorListener;
import android.animation.ValueAnimator;
-import android.util.Log;
import android.view.MotionEvent;
import com.android.launcher3.Launcher;
@@ -292,11 +291,18 @@
? mToState : mFromState;
// snap to top or bottom using the release velocity
} else {
- float successTransitionProgress =
- mLauncher.getDeviceProfile().isTablet
- && (mToState == ALL_APPS || mFromState == ALL_APPS)
- ? TABLET_BOTTOM_SHEET_SUCCESS_TRANSITION_PROGRESS
- : SUCCESS_TRANSITION_PROGRESS;
+ float successTransitionProgress = SUCCESS_TRANSITION_PROGRESS;
+ if (mLauncher.getDeviceProfile().isTablet
+ && (mToState == ALL_APPS || mFromState == ALL_APPS)) {
+ successTransitionProgress = TABLET_BOTTOM_SHEET_SUCCESS_TRANSITION_PROGRESS;
+ } else if (!mLauncher.getDeviceProfile().isTablet
+ && mToState == ALL_APPS && mFromState == NORMAL) {
+ successTransitionProgress = AllAppsSwipeController.ALL_APPS_STATE_TRANSITION_MANUAL;
+ } else if (!mLauncher.getDeviceProfile().isTablet
+ && mToState == NORMAL && mFromState == ALL_APPS) {
+ successTransitionProgress =
+ 1 - AllAppsSwipeController.ALL_APPS_STATE_TRANSITION_MANUAL;
+ }
targetState =
(interpolatedProgress > successTransitionProgress) ? mToState : mFromState;
}
diff --git a/src/com/android/launcher3/touch/AllAppsSwipeController.java b/src/com/android/launcher3/touch/AllAppsSwipeController.java
index 5279dec..bfd0e1b 100644
--- a/src/com/android/launcher3/touch/AllAppsSwipeController.java
+++ b/src/com/android/launcher3/touch/AllAppsSwipeController.java
@@ -17,7 +17,6 @@
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.NORMAL;
-import static com.android.launcher3.anim.Interpolators.DECELERATED_EASE;
import static com.android.launcher3.anim.Interpolators.EMPHASIZED;
import static com.android.launcher3.anim.Interpolators.EMPHASIZED_ACCELERATE;
import static com.android.launcher3.anim.Interpolators.EMPHASIZED_DECELERATE;
@@ -64,11 +63,14 @@
// ---- Custom interpolators for NORMAL -> ALL_APPS on phones only. ----
- private static final float WORKSPACE_MOTION_START_ATOMIC = 0.1667f;
- private static final float ALL_APPS_STATE_TRANSITION_ATOMIC = 0.305f;
- private static final float ALL_APPS_STATE_TRANSITION_MANUAL = 0.4f;
- private static final float ALL_APPS_FADE_END_ATOMIC = 0.4717f;
+ public static final float ALL_APPS_STATE_TRANSITION_ATOMIC = 0.3333f;
+ public static final float ALL_APPS_STATE_TRANSITION_MANUAL = 0.4f;
+ private static final float ALL_APPS_FADE_END_ATOMIC = 0.8333f;
+ private static final float ALL_APPS_FADE_END_MANUAL = 0.8f;
private static final float ALL_APPS_FULL_DEPTH_PROGRESS = 0.5f;
+ private static final float SCRIM_FADE_START_ATOMIC = 0.2642f;
+ private static final float SCRIM_FADE_START_MANUAL = 0.117f;
+ private static final float WORKSPACE_MOTION_START_ATOMIC = 0.1667f;
private static final Interpolator LINEAR_EARLY_MANUAL =
Interpolators.clampToProgress(LINEAR, 0f, ALL_APPS_STATE_TRANSITION_MANUAL);
@@ -98,27 +100,30 @@
public static final Interpolator HOTSEAT_FADE_ATOMIC = STEP_TRANSITION_ATOMIC;
public static final Interpolator HOTSEAT_FADE_MANUAL = STEP_TRANSITION_MANUAL;
- public static final Interpolator HOTSEAT_SCALE_ATOMIC = STEP_TRANSITION_ATOMIC;
- public static final Interpolator HOTSEAT_SCALE_MANUAL = LINEAR_EARLY_MANUAL;
-
- public static final Interpolator HOTSEAT_TRANSLATE_ATOMIC =
+ public static final Interpolator HOTSEAT_SCALE_ATOMIC =
Interpolators.clampToProgress(
EMPHASIZED_ACCELERATE, WORKSPACE_MOTION_START_ATOMIC,
ALL_APPS_STATE_TRANSITION_ATOMIC);
+ public static final Interpolator HOTSEAT_SCALE_MANUAL = LINEAR_EARLY_MANUAL;
+
+ public static final Interpolator HOTSEAT_TRANSLATE_ATOMIC = STEP_TRANSITION_ATOMIC;
public static final Interpolator HOTSEAT_TRANSLATE_MANUAL = STEP_TRANSITION_MANUAL;
public static final Interpolator SCRIM_FADE_ATOMIC =
Interpolators.clampToProgress(
Interpolators.mapToProgress(LINEAR, 0f, 0.8f),
- WORKSPACE_MOTION_START_ATOMIC, ALL_APPS_STATE_TRANSITION_ATOMIC);
- public static final Interpolator SCRIM_FADE_MANUAL = LINEAR_EARLY_MANUAL;
+ SCRIM_FADE_START_ATOMIC, ALL_APPS_STATE_TRANSITION_ATOMIC);
+ public static final Interpolator SCRIM_FADE_MANUAL =
+ Interpolators.clampToProgress(
+ LINEAR, SCRIM_FADE_START_MANUAL, ALL_APPS_STATE_TRANSITION_MANUAL);
public static final Interpolator ALL_APPS_FADE_ATOMIC =
Interpolators.clampToProgress(
- Interpolators.mapToProgress(DECELERATED_EASE, 0.2f, 1f),
+ Interpolators.mapToProgress(EMPHASIZED_DECELERATE, 0.2f, 1f),
ALL_APPS_STATE_TRANSITION_ATOMIC, ALL_APPS_FADE_END_ATOMIC);
public static final Interpolator ALL_APPS_FADE_MANUAL =
- Interpolators.clampToProgress(LINEAR, ALL_APPS_STATE_TRANSITION_MANUAL, 1f);
+ Interpolators.clampToProgress(
+ LINEAR, ALL_APPS_STATE_TRANSITION_MANUAL, ALL_APPS_FADE_END_MANUAL);
public static final Interpolator ALL_APPS_VERTICAL_PROGRESS_ATOMIC =
Interpolators.clampToProgress(
diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java
index b7e0105..64951ca 100644
--- a/src/com/android/launcher3/touch/ItemClickHandler.java
+++ b/src/com/android/launcher3/touch/ItemClickHandler.java
@@ -23,14 +23,14 @@
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_QUIET_USER;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_SAFEMODE;
import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_SUSPENDED;
+import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
+import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.pm.LauncherApps;
import android.content.pm.PackageInstaller.SessionInfo;
-import android.os.Process;
-import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import android.view.View;
@@ -66,6 +66,8 @@
import com.android.launcher3.widget.WidgetManagerHelper;
import java.util.Collections;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
/**
* Class for handling clicks on workspace and all-apps items
@@ -156,32 +158,18 @@
private static void onClickPendingAppItem(View v, Launcher launcher, String packageName,
boolean downloadStarted) {
- if (downloadStarted) {
- // If the download has started, simply direct to the market app.
- startMarketIntentForPackage(v, launcher, packageName);
- return;
- }
- UserHandle user = v.getTag() instanceof ItemInfo
- ? ((ItemInfo) v.getTag()).user : Process.myUserHandle();
- new AlertDialog.Builder(launcher)
- .setTitle(R.string.abandoned_promises_title)
- .setMessage(R.string.abandoned_promise_explanation)
- .setPositiveButton(R.string.abandoned_search,
- (d, i) -> startMarketIntentForPackage(v, launcher, packageName))
- .setNeutralButton(R.string.abandoned_clean_this,
- (d, i) -> launcher.getWorkspace()
- .persistRemoveItemsByMatcher(ItemInfoMatcher.ofPackages(
- Collections.singleton(packageName), user),
- "user explicitly removes the promise app icon"))
- .create().show();
- }
-
- private static void startMarketIntentForPackage(View v, Launcher launcher, String packageName) {
ItemInfo item = (ItemInfo) v.getTag();
+ CompletableFuture<SessionInfo> siFuture;
if (Utilities.ATLEAST_Q) {
- SessionInfo sessionInfo = InstallSessionHelper.INSTANCE.get(launcher)
- .getActiveSessionInfo(item.user, packageName);
- if (sessionInfo != null) {
+ siFuture = CompletableFuture.supplyAsync(() ->
+ InstallSessionHelper.INSTANCE.get(launcher)
+ .getActiveSessionInfo(item.user, packageName),
+ UI_HELPER_EXECUTOR);
+ } else {
+ siFuture = CompletableFuture.completedFuture(null);
+ }
+ Consumer<SessionInfo> marketLaunchAction = sessionInfo -> {
+ if (sessionInfo != null && Utilities.ATLEAST_Q) {
LauncherApps launcherApps = launcher.getSystemService(LauncherApps.class);
try {
launcherApps.startPackageInstallerSessionDetailsActivity(sessionInfo, null,
@@ -191,11 +179,27 @@
Log.e(TAG, "Unable to launch market intent for package=" + packageName, e);
}
}
- }
+ // Fallback to using custom market intent.
+ Intent intent = new PackageManagerHelper(launcher).getMarketIntent(packageName);
+ launcher.startActivitySafely(v, intent, item);
+ };
- // Fallback to using custom market intent.
- Intent intent = new PackageManagerHelper(launcher).getMarketIntent(packageName);
- launcher.startActivitySafely(v, intent, item);
+ if (downloadStarted) {
+ // If the download has started, simply direct to the market app.
+ siFuture.thenAcceptAsync(marketLaunchAction, MAIN_EXECUTOR);
+ return;
+ }
+ new AlertDialog.Builder(launcher)
+ .setTitle(R.string.abandoned_promises_title)
+ .setMessage(R.string.abandoned_promise_explanation)
+ .setPositiveButton(R.string.abandoned_search,
+ (d, i) -> siFuture.thenAcceptAsync(marketLaunchAction, MAIN_EXECUTOR))
+ .setNeutralButton(R.string.abandoned_clean_this,
+ (d, i) -> launcher.getWorkspace()
+ .persistRemoveItemsByMatcher(ItemInfoMatcher.ofPackages(
+ Collections.singleton(packageName), item.user),
+ "user explicitly removes the promise app icon"))
+ .create().show();
}
/**
diff --git a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
index a4cb30a..f73940b 100644
--- a/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
+++ b/src/com/android/launcher3/util/LauncherBindableItemsContainer.java
@@ -50,7 +50,12 @@
Drawable oldIcon = shortcut.getIcon();
boolean oldPromiseState = (oldIcon instanceof PreloadIconDrawable)
&& ((PreloadIconDrawable) oldIcon).hasNotCompleted();
- shortcut.applyFromWorkspaceItem(si, si.isPromise() != oldPromiseState);
+ shortcut.applyFromWorkspaceItem(
+ si,
+ si.isPromise() != oldPromiseState
+ && oldIcon instanceof PreloadIconDrawable
+ ? (PreloadIconDrawable) oldIcon
+ : null);
} else if (info instanceof FolderInfo && v instanceof FolderIcon) {
((FolderIcon) v).updatePreviewItems(updates::contains);
}
@@ -74,7 +79,7 @@
ItemOperator op = (info, v) -> {
if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView
&& updates.contains(info)) {
- ((BubbleTextView) v).applyLoadingState(false /* promiseStateChanged */);
+ ((BubbleTextView) v).applyLoadingState(null);
} else if (v instanceof PendingAppWidgetHostView
&& info instanceof LauncherAppWidgetInfo
&& updates.contains(info)) {
diff --git a/src/com/android/launcher3/util/LockedUserState.kt b/src/com/android/launcher3/util/LockedUserState.kt
new file mode 100644
index 0000000..7b49583
--- /dev/null
+++ b/src/com/android/launcher3/util/LockedUserState.kt
@@ -0,0 +1,57 @@
+package com.android.launcher3.util
+
+import android.content.Context
+import android.content.Intent
+import android.os.Process
+import android.os.UserManager
+import androidx.annotation.VisibleForTesting
+
+class LockedUserState(private val mContext: Context) : SafeCloseable {
+ var isUserUnlocked: Boolean
+ private set
+ private val mUserUnlockedActions: RunnableList = RunnableList()
+
+ @VisibleForTesting
+ val mUserUnlockedReceiver = SimpleBroadcastReceiver {
+ if (Intent.ACTION_USER_UNLOCKED == it.action) {
+ isUserUnlocked = true
+ notifyUserUnlocked()
+ }
+ }
+
+ init {
+ isUserUnlocked =
+ mContext
+ .getSystemService(UserManager::class.java)!!
+ .isUserUnlocked(Process.myUserHandle())
+ if (isUserUnlocked) {
+ notifyUserUnlocked()
+ } else {
+ mUserUnlockedReceiver.register(mContext, Intent.ACTION_USER_UNLOCKED)
+ }
+ }
+
+ private fun notifyUserUnlocked() {
+ mUserUnlockedActions.executeAllAndDestroy()
+ mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
+ }
+
+ /** Stops the receiver from listening for ACTION_USER_UNLOCK broadcasts. */
+ override fun close() {
+ mUserUnlockedReceiver.unregisterReceiverSafely(mContext)
+ }
+
+ /**
+ * Adds a `Runnable` to be executed when a user is unlocked. If the user is already unlocked,
+ * this runnable will run immediately because RunnableList will already have been destroyed.
+ */
+ fun runOnUserUnlocked(action: Runnable) {
+ mUserUnlockedActions.add(action)
+ }
+
+ companion object {
+ @VisibleForTesting val INSTANCE = MainThreadInitializedObject { LockedUserState(it) }
+
+ @JvmStatic fun get(context: Context): LockedUserState = INSTANCE.get(context)
+ }
+}
diff --git a/src/com/android/launcher3/util/Themes.java b/src/com/android/launcher3/util/Themes.java
index 585bea9..5526839 100644
--- a/src/com/android/launcher3/util/Themes.java
+++ b/src/com/android/launcher3/util/Themes.java
@@ -19,6 +19,8 @@
import static android.app.WallpaperColors.HINT_SUPPORTS_DARK_TEXT;
import static android.app.WallpaperColors.HINT_SUPPORTS_DARK_THEME;
+import static com.android.launcher3.LauncherPrefs.THEMED_ICONS;
+
import android.app.WallpaperColors;
import android.app.WallpaperManager;
import android.content.Context;
@@ -74,7 +76,7 @@
* Returns true if workspace icon theming is enabled
*/
public static boolean isThemedIconEnabled(Context context) {
- return LauncherPrefs.getPrefs(context).getBoolean(KEY_THEMED_ICONS, false);
+ return LauncherPrefs.get(context).get(THEMED_ICONS);
}
public static String getDefaultBodyFont(Context context) {
diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java
index 4c0bfde..870ff12 100644
--- a/src/com/android/launcher3/views/ScrimView.java
+++ b/src/com/android/launcher3/views/ScrimView.java
@@ -46,6 +46,7 @@
private int mBackgroundColor;
private boolean mIsVisible = true;
private boolean mLastDispatchedOpaqueness;
+ private float mHeaderScale = 1f;
public ScrimView(Context context, AttributeSet attrs) {
super(context, attrs);
@@ -91,7 +92,16 @@
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mDrawingController != null) {
- mDrawingController.drawOnScrim(canvas);
+ mDrawingController.drawOnScrimWithScale(canvas, mHeaderScale);
+ }
+ }
+
+ /** Set scrim header's scale and bottom offset. */
+ public void setScrimHeaderScale(float scale) {
+ boolean hasChanged = mHeaderScale != scale;
+ mHeaderScale = scale;
+ if (hasChanged) {
+ invalidate();
}
}
@@ -176,6 +186,6 @@
/**
* Called inside ScrimView#OnDraw
*/
- void drawOnScrim(Canvas canvas);
+ void drawOnScrimWithScale(Canvas canvas, float scale);
}
}
diff --git a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java b/src_shortcuts_overrides/com/android/launcher3/model/LauncherBinder.java
similarity index 88%
rename from src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
rename to src_shortcuts_overrides/com/android/launcher3/model/LauncherBinder.java
index abce2a2..e1a5f24 100644
--- a/src_shortcuts_overrides/com/android/launcher3/model/LoaderResults.java
+++ b/src_shortcuts_overrides/com/android/launcher3/model/LauncherBinder.java
@@ -27,11 +27,11 @@
import java.util.List;
/**
- * Helper class to handle results of {@link com.android.launcher3.model.LoaderTask}.
+ * Binds the results of {@link com.android.launcher3.model.LoaderTask} to the Callbacks objects.
*/
-public class LoaderResults extends BaseLoaderResults {
+public class LauncherBinder extends BaseLauncherBinder {
- public LoaderResults(LauncherAppState app, BgDataModel dataModel,
+ public LauncherBinder(LauncherAppState app, BgDataModel dataModel,
AllAppsList allAppsList, Callbacks[] callbacks) {
super(app, dataModel, allAppsList, callbacks, MAIN_EXECUTOR);
}
diff --git a/tests/src/com/android/launcher3/LauncherPrefsTest.kt b/tests/src/com/android/launcher3/LauncherPrefsTest.kt
new file mode 100644
index 0000000..151abf1
--- /dev/null
+++ b/tests/src/com/android/launcher3/LauncherPrefsTest.kt
@@ -0,0 +1,148 @@
+package com.android.launcher3
+
+import android.content.SharedPreferences.OnSharedPreferenceChangeListener
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private val TEST_BOOLEAN_ITEM = LauncherPrefs.nonRestorableItem("1", false)
+private val TEST_STRING_ITEM = LauncherPrefs.nonRestorableItem("2", "( ͡❛ ͜ʖ ͡❛)")
+private val TEST_INT_ITEM = LauncherPrefs.nonRestorableItem("3", -1)
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LauncherPrefsTest {
+
+ private val launcherPrefs by lazy {
+ LauncherPrefs.get(InstrumentationRegistry.getInstrumentation().targetContext).apply {
+ remove(TEST_BOOLEAN_ITEM, TEST_STRING_ITEM, TEST_INT_ITEM)
+ }
+ }
+
+ @Test
+ fun has_keyMissingFromLauncherPrefs_returnsFalse() {
+ assertThat(launcherPrefs.has(TEST_BOOLEAN_ITEM)).isFalse()
+ }
+
+ @Test
+ fun has_keyPresentInLauncherPrefs_returnsTrue() {
+ with(launcherPrefs) {
+ putSync(TEST_BOOLEAN_ITEM.to(TEST_BOOLEAN_ITEM.defaultValue))
+ assertThat(has(TEST_BOOLEAN_ITEM)).isTrue()
+ remove(TEST_BOOLEAN_ITEM)
+ }
+ }
+
+ @Test
+ fun addListener_listeningForStringItemUpdates_isCorrectlyNotifiedOfUpdates() {
+ val latch = CountDownLatch(1)
+ val listener = OnSharedPreferenceChangeListener { _, _ -> latch.countDown() }
+
+ with(launcherPrefs) {
+ putSync(TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue))
+ addListener(listener, TEST_STRING_ITEM)
+ putSync(TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue + "abc"))
+
+ assertThat(latch.await(2, TimeUnit.SECONDS)).isTrue()
+ remove(TEST_STRING_ITEM)
+ }
+ }
+
+ @Test
+ fun removeListener_previouslyListeningForStringItemUpdates_isNoLongerNotifiedOfUpdates() {
+ val latch = CountDownLatch(1)
+ val listener = OnSharedPreferenceChangeListener { _, _ -> latch.countDown() }
+
+ with(launcherPrefs) {
+ addListener(listener, TEST_STRING_ITEM)
+ removeListener(listener, TEST_STRING_ITEM)
+ putSync(TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue + "hello."))
+
+ // latch will be still be 1 (and await will return false) if the listener was not called
+ assertThat(latch.await(2, TimeUnit.SECONDS)).isFalse()
+ remove(TEST_STRING_ITEM)
+ }
+ }
+
+ @Test
+ fun addListenerAndRemoveListener_forMultipleItems_bothWorkProperly() {
+ var latch = CountDownLatch(3)
+ val listener = OnSharedPreferenceChangeListener { _, _ -> latch.countDown() }
+
+ with(launcherPrefs) {
+ addListener(listener, TEST_INT_ITEM, TEST_STRING_ITEM, TEST_BOOLEAN_ITEM)
+ putSync(
+ TEST_INT_ITEM.to(TEST_INT_ITEM.defaultValue + 123),
+ TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue + "abc"),
+ TEST_BOOLEAN_ITEM.to(!TEST_BOOLEAN_ITEM.defaultValue)
+ )
+ assertThat(latch.await(2, TimeUnit.SECONDS)).isTrue()
+
+ removeListener(listener, TEST_INT_ITEM, TEST_STRING_ITEM, TEST_BOOLEAN_ITEM)
+ latch = CountDownLatch(1)
+ putSync(
+ TEST_INT_ITEM.to(TEST_INT_ITEM.defaultValue),
+ TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue),
+ TEST_BOOLEAN_ITEM.to(TEST_BOOLEAN_ITEM.defaultValue)
+ )
+ remove(TEST_INT_ITEM, TEST_STRING_ITEM, TEST_BOOLEAN_ITEM)
+
+ assertThat(latch.await(2, TimeUnit.SECONDS)).isFalse()
+ }
+ }
+
+ @Test
+ fun get_booleanItemNotInLauncherprefs_returnsDefaultValue() {
+ assertThat(launcherPrefs.get(TEST_BOOLEAN_ITEM)).isEqualTo(TEST_BOOLEAN_ITEM.defaultValue)
+ }
+
+ @Test
+ fun get_stringItemNotInLauncherPrefs_returnsDefaultValue() {
+ assertThat(launcherPrefs.get(TEST_STRING_ITEM)).isEqualTo(TEST_STRING_ITEM.defaultValue)
+ }
+
+ @Test
+ fun get_intItemNotInLauncherprefs_returnsDefaultValue() {
+ assertThat(launcherPrefs.get(TEST_INT_ITEM)).isEqualTo(TEST_INT_ITEM.defaultValue)
+ }
+
+ @Test
+ fun put_storesItemInLauncherPrefs_successfully() {
+ val notDefaultValue = !TEST_BOOLEAN_ITEM.defaultValue
+
+ with(launcherPrefs) {
+ putSync(TEST_BOOLEAN_ITEM.to(notDefaultValue))
+ assertThat(get(TEST_BOOLEAN_ITEM)).isEqualTo(notDefaultValue)
+ remove(TEST_BOOLEAN_ITEM)
+ }
+ }
+
+ @Test
+ fun put_storesListOfItemsInLauncherPrefs_successfully() {
+ with(launcherPrefs) {
+ putSync(
+ TEST_STRING_ITEM.to(TEST_STRING_ITEM.defaultValue),
+ TEST_INT_ITEM.to(TEST_INT_ITEM.defaultValue),
+ TEST_BOOLEAN_ITEM.to(TEST_BOOLEAN_ITEM.defaultValue)
+ )
+ assertThat(has(TEST_BOOLEAN_ITEM, TEST_INT_ITEM, TEST_STRING_ITEM)).isTrue()
+ remove(TEST_STRING_ITEM, TEST_INT_ITEM, TEST_BOOLEAN_ITEM)
+ }
+ }
+
+ @Test
+ fun remove_deletesItemFromLauncherPrefs_successfully() {
+ val notDefaultValue = !TEST_BOOLEAN_ITEM.defaultValue
+
+ with(launcherPrefs) {
+ putSync(TEST_BOOLEAN_ITEM.to(notDefaultValue))
+ remove(TEST_BOOLEAN_ITEM)
+ assertThat(get(TEST_BOOLEAN_ITEM)).isEqualTo(TEST_BOOLEAN_ITEM.defaultValue)
+ }
+ }
+}
diff --git a/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
index 843f011..7b38ed6 100644
--- a/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
+++ b/tests/src/com/android/launcher3/celllayout/ReorderWidgets.java
@@ -132,10 +132,10 @@
(CellLayoutLayoutParams) callView.getLayoutParams();
// is icon
if (callView instanceof DoubleShadowBubbleTextView) {
- board.addIcon(params.cellX, params.cellY);
+ board.addIcon(params.getCellX(), params.getCellY());
} else {
// is widget
- board.addWidget(params.cellX, params.cellY, params.cellHSpan,
+ board.addWidget(params.getCellX(), params.getCellY(), params.cellHSpan,
params.cellVSpan, (char) ('A' + widgetCount));
widgetCount++;
}
diff --git a/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt b/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
index dcc8ec7..a85fa3a 100644
--- a/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
+++ b/tests/src/com/android/launcher3/model/GridSizeMigrationUtilTest.kt
@@ -24,7 +24,8 @@
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.launcher3.InvariantDeviceProfile
-import com.android.launcher3.LauncherFiles
+import com.android.launcher3.LauncherPrefs
+import com.android.launcher3.LauncherPrefs.Companion.WORKSPACE_SIZE
import com.android.launcher3.LauncherSettings.Favorites.*
import com.android.launcher3.config.FeatureFlags
import com.android.launcher3.model.GridSizeMigrationUtil.DbReader
@@ -754,11 +755,7 @@
.edit()
.putBoolean(FeatureFlags.ENABLE_NEW_MIGRATION_LOGIC.key, true)
.commit()
- context
- .getSharedPreferences(LauncherFiles.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
- .edit()
- .putString(DeviceGridState.KEY_WORKSPACE_SIZE, srcGridSize)
- .commit()
+ LauncherPrefs.get(context).putSync(WORKSPACE_SIZE.to(srcGridSize))
FeatureFlags.initialize(context)
}
diff --git a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
index 0db719e..9669010 100644
--- a/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
+++ b/tests/src/com/android/launcher3/ui/widget/RequestPinItemTest.java
@@ -147,7 +147,8 @@
// Set callback
PendingIntent callback = PendingIntent.getBroadcast(mTargetContext, 0,
- new Intent(mCallbackAction), FLAG_ONE_SHOT | FLAG_MUTABLE);
+ new Intent(mCallbackAction).setPackage(mTargetContext.getPackageName()),
+ FLAG_ONE_SHOT | FLAG_MUTABLE);
mTargetContext.sendBroadcast(RequestPinItemActivity.getCommandIntent(
RequestPinItemActivity.class, "setCallback").putExtra(
RequestPinItemActivity.EXTRA_PARAM + "0", callback));
diff --git a/tests/src/com/android/launcher3/util/LauncherModelHelper.java b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
index 93bf312..caec301 100644
--- a/tests/src/com/android/launcher3/util/LauncherModelHelper.java
+++ b/tests/src/com/android/launcher3/util/LauncherModelHelper.java
@@ -55,6 +55,7 @@
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.LauncherModel;
import com.android.launcher3.LauncherModel.ModelUpdateTask;
+import com.android.launcher3.LauncherPrefs;
import com.android.launcher3.LauncherProvider;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.model.AllAppsList;
@@ -506,7 +507,7 @@
SanboxModelContext() {
super(ApplicationProvider.getApplicationContext(),
- UserCache.INSTANCE, InstallSessionHelper.INSTANCE,
+ UserCache.INSTANCE, InstallSessionHelper.INSTANCE, LauncherPrefs.INSTANCE,
LauncherAppState.INSTANCE, InvariantDeviceProfile.INSTANCE,
DisplayController.INSTANCE, CustomWidgetManager.INSTANCE,
SettingsCache.INSTANCE, PluginManagerWrapper.INSTANCE,
diff --git a/tests/src/com/android/launcher3/util/LockedUserStateTest.kt b/tests/src/com/android/launcher3/util/LockedUserStateTest.kt
new file mode 100644
index 0000000..84156e7
--- /dev/null
+++ b/tests/src/com/android/launcher3/util/LockedUserStateTest.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.util
+
+import android.content.Context
+import android.content.Intent
+import android.os.Process
+import android.os.UserManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.verifyZeroInteractions
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+/** Unit tests for {@link LockedUserUtil} */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class LockedUserStateTest {
+
+ @Mock lateinit var userManager: UserManager
+ @Mock lateinit var context: Context
+
+ @Before
+ fun setup() {
+ MockitoAnnotations.initMocks(this)
+ `when`(context.getSystemService(UserManager::class.java)).thenReturn(userManager)
+ }
+
+ @Test
+ fun runOnUserUnlocked_runs_action_immediately_if_already_unlocked() {
+ `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
+ LockedUserState.INSTANCE.initializeForTesting(LockedUserState(context))
+ val action: Runnable = mock()
+
+ LockedUserState.get(context).runOnUserUnlocked(action)
+ verify(action).run()
+ }
+
+ @Test
+ fun runOnUserUnlocked_waits_to_run_action_until_user_is_unlocked() {
+ `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
+ LockedUserState.INSTANCE.initializeForTesting(LockedUserState(context))
+ val action: Runnable = mock()
+
+ LockedUserState.get(context).runOnUserUnlocked(action)
+ verifyZeroInteractions(action)
+
+ LockedUserState.get(context)
+ .mUserUnlockedReceiver
+ .onReceive(context, Intent(Intent.ACTION_USER_UNLOCKED))
+
+ verify(action).run()
+ }
+
+ @Test
+ fun isUserUnlocked_returns_true_when_user_is_unlocked() {
+ `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(true)
+ LockedUserState.INSTANCE.initializeForTesting(LockedUserState(context))
+ assertThat(LockedUserState.get(context).isUserUnlocked).isTrue()
+ }
+
+ @Test
+ fun isUserUnlocked_returns_false_when_user_is_locked() {
+ `when`(userManager.isUserUnlocked(Process.myUserHandle())).thenReturn(false)
+ LockedUserState.INSTANCE.initializeForTesting(LockedUserState(context))
+ assertThat(LockedUserState.get(context).isUserUnlocked).isFalse()
+ }
+}