Snap for 11343021 from 5a1c88629b to 24Q2-release

Change-Id: Ieb779ad2a63827731a0668b71f1e0656e16ee93c
This commit is contained in:
Android Build Coastguard Worker
2024-01-23 00:20:57 +00:00
22 changed files with 614 additions and 33 deletions
+1
View File
@@ -175,6 +175,7 @@ android_library {
"androidx.preference_preference",
"androidx.slice_slice-view",
"androidx.cardview_cardview",
"androidx.window_window",
"com.google.android.material_material",
"iconloader_base",
"view_capture",
+4
View File
@@ -184,5 +184,9 @@
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />
<property
android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
android:value="true" />
</application>
</manifest>
+14
View File
@@ -84,6 +84,13 @@ flag {
bug: "315055849"
}
flag {
name: "enable_two_pane_launcher_settings"
namespace: "launcher"
description: "Enables two panel settings when on large enough displays"
bug: "204463748"
}
flag {
name: "enable_shortcut_dont_suggest_app"
namespace: "launcher"
@@ -126,3 +133,10 @@ flag {
description: "Enables asnc inflation of workspace icons"
bug: "318539160"
}
flag {
name: "enable_unfold_state_animation"
namespace: "launcher"
description: "Tie unfold animation with state animation"
bug: "297057373"
}
@@ -72,6 +72,8 @@ public class PredictionRowView<T extends Context & ActivityContext>
private boolean mPredictionsEnabled = false;
private boolean mPredictionUiUpdatePaused = false;
public PredictionRowView(@NonNull Context context) {
this(context, null);
}
@@ -193,7 +195,18 @@ public class PredictionRowView<T extends Context & ActivityContext>
applyPredictionApps();
}
/** Pause the prediction row UI update */
public void setPredictionUiUpdatePaused(boolean predictionUiUpdatePaused) {
mPredictionUiUpdatePaused = predictionUiUpdatePaused;
if (!mPredictionUiUpdatePaused) {
applyPredictionApps();
}
}
private void applyPredictionApps() {
if (mPredictionUiUpdatePaused) {
return;
}
if (getChildCount() != mNumPredictedAppsPerRow) {
while (getChildCount() > mNumPredictedAppsPerRow) {
removeViewAt(0);
@@ -22,6 +22,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL;
import static com.android.launcher3.BaseActivity.EVENT_DESTROYED;
import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
import static com.android.launcher3.LauncherState.OVERVIEW;
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
import static com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate;
@@ -405,8 +406,12 @@ public class TaskbarManager {
*/
private UnfoldTransitionProgressProvider getUnfoldTransitionProgressProviderForActivity(
StatefulActivity activity) {
if (activity instanceof QuickstepLauncher) {
return ((QuickstepLauncher) activity).getUnfoldTransitionProgressProvider();
if (!enableUnfoldStateAnimation()) {
if (activity instanceof QuickstepLauncher ql) {
return ql.getUnfoldTransitionProgressProvider();
}
} else {
return SystemUiProxy.INSTANCE.get(mContext).getUnfoldTransitionProvider();
}
return null;
}
@@ -51,6 +51,7 @@ import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.folder.FolderIcon;
import com.android.launcher3.folder.PreviewBackground;
import com.android.launcher3.icons.ThemedIconDrawable;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
@@ -618,9 +619,11 @@ public class TaskbarView extends FrameLayout implements FolderIcon.FolderIconPar
super.onDraw(canvas);
if (mLeaveBehindFolderIcon != null) {
canvas.save();
canvas.translate(mLeaveBehindFolderIcon.getLeft(), mLeaveBehindFolderIcon.getTop());
mLeaveBehindFolderIcon.getFolderBackground().drawLeaveBehind(canvas,
mFolderLeaveBehindColor);
canvas.translate(
mLeaveBehindFolderIcon.getLeft() + mLeaveBehindFolderIcon.getTranslationX(),
mLeaveBehindFolderIcon.getTop());
PreviewBackground previewBackground = mLeaveBehindFolderIcon.getFolderBackground();
previewBackground.drawLeaveBehind(canvas, mFolderLeaveBehindColor);
canvas.restore();
}
}
@@ -21,6 +21,7 @@ import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEAS
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
import static com.android.app.animation.Interpolators.EMPHASIZED;
import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO;
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
import static com.android.launcher3.LauncherSettings.Animation.DEFAULT_NO_ICON;
@@ -170,6 +171,8 @@ import com.android.quickstep.util.SplitSelectStateController;
import com.android.quickstep.util.SplitToWorkspaceController;
import com.android.quickstep.util.SplitWithKeyboardShortcutController;
import com.android.quickstep.util.TISBindHelper;
import com.android.quickstep.util.unfold.LauncherUnfoldTransitionController;
import com.android.quickstep.util.unfold.ProxyUnfoldTransitionProvider;
import com.android.quickstep.views.FloatingTaskView;
import com.android.quickstep.views.OverviewActionsView;
import com.android.quickstep.views.RecentsView;
@@ -367,11 +370,19 @@ public class QuickstepLauncher extends Launcher {
mHotseatPredictionController.setPauseUIUpdate(getTaskbarUIController() == null);
Log.d("b/318394698", "startActivitySafely being run, getTaskbarUIController is: "
+ getTaskbarUIController());
PredictionRowView<?> predictionRowView =
getAppsView().getFloatingHeaderView().findFixedRowByType(PredictionRowView.class);
// Pause the prediction row updates until the transition (if it exists) ends.
predictionRowView.setPredictionUiUpdatePaused(true);
RunnableList result = super.startActivitySafely(v, intent, item);
if (result == null) {
mHotseatPredictionController.setPauseUIUpdate(false);
predictionRowView.setPredictionUiUpdatePaused(false);
} else {
result.add(() -> mHotseatPredictionController.setPauseUIUpdate(false));
result.add(() -> {
mHotseatPredictionController.setPauseUIUpdate(false);
predictionRowView.setPredictionUiUpdatePaused(false);
});
}
return result;
}
@@ -468,7 +479,7 @@ public class QuickstepLauncher extends Launcher {
@Override
public void bindExtraContainerItems(FixedContainerItems item) {
Log.d(TAG, "Bind extra container items");
Log.d(TAG, "Bind extra container items. ContainerId = " + item.containerId);
if (item.containerId == Favorites.CONTAINER_PREDICTION) {
mAllAppsPredictions = item;
PredictionRowView<?> predictionRowView =
@@ -957,9 +968,17 @@ public class QuickstepLauncher extends Launcher {
}
private void initUnfoldTransitionProgressProvider() {
final UnfoldTransitionConfig config = new ResourceUnfoldTransitionConfig();
if (config.isEnabled()) {
initRemotelyCalculatedUnfoldAnimation(config);
if (!enableUnfoldStateAnimation()) {
final UnfoldTransitionConfig config = new ResourceUnfoldTransitionConfig();
if (config.isEnabled()) {
initRemotelyCalculatedUnfoldAnimation(config);
}
} else {
ProxyUnfoldTransitionProvider provider =
SystemUiProxy.INSTANCE.get(this).getUnfoldTransitionProvider();
if (provider != null) {
new LauncherUnfoldTransitionController(this, provider);
}
}
}
@@ -17,6 +17,7 @@ package com.android.quickstep;
import static android.app.ActivityManager.RECENT_IGNORE_UNAVAILABLE;
import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.launcher3.util.SplitConfigurationOptions.StagePosition;
@@ -67,6 +68,7 @@ import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
import com.android.quickstep.util.ActiveGestureLog;
import com.android.quickstep.util.AssistUtils;
import com.android.quickstep.util.unfold.ProxyUnfoldTransitionProvider;
import com.android.systemui.shared.recents.ISystemUiProxy;
import com.android.systemui.shared.recents.model.ThumbnailData;
import com.android.systemui.shared.system.RecentsAnimationControllerCompat;
@@ -74,6 +76,7 @@ import com.android.systemui.shared.system.RecentsAnimationListener;
import com.android.systemui.shared.system.smartspace.ILauncherUnlockAnimationController;
import com.android.systemui.shared.system.smartspace.ISysuiUnlockAnimationController;
import com.android.systemui.shared.system.smartspace.SmartspaceState;
import com.android.systemui.unfold.config.ResourceUnfoldTransitionConfig;
import com.android.systemui.unfold.progress.IUnfoldAnimation;
import com.android.systemui.unfold.progress.IUnfoldTransitionListener;
import com.android.wm.shell.back.IBackAnimation;
@@ -177,7 +180,10 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle {
*/
private final PendingIntent mRecentsPendingIntent;
public SystemUiProxy(Context context) {
@Nullable
private final ProxyUnfoldTransitionProvider mUnfoldTransitionProvider;
private SystemUiProxy(Context context) {
mContext = context;
mAsyncHandler = new Handler(UI_HELPER_EXECUTOR.getLooper(), this::handleMessageAsync);
final Intent baseIntent = new Intent().setPackage(mContext.getPackageName());
@@ -187,6 +193,10 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle {
mRecentsPendingIntent = PendingIntent.getActivity(mContext, 0, baseIntent,
PendingIntent.FLAG_MUTABLE | PendingIntent.FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT
| Intent.FILL_IN_COMPONENT, options.toBundle());
mUnfoldTransitionProvider =
(enableUnfoldStateAnimation() && new ResourceUnfoldTransitionConfig().isEnabled())
? new ProxyUnfoldTransitionProvider() : null;
}
@Override
@@ -251,7 +261,7 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle {
mRecentTasks = recentTasks;
mBackAnimation = backAnimation;
mDesktopMode = desktopMode;
mUnfoldAnimation = unfoldAnimation;
mUnfoldAnimation = enableUnfoldStateAnimation() ? null : unfoldAnimation;
mDragAndDrop = dragAndDrop;
linkToDeath();
// re-attach the listeners once missing due to setProxy has not been initialized yet.
@@ -272,6 +282,19 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle {
setAssistantOverridesRequested(
AssistUtils.newInstance(mContext).getSysUiAssistOverrideInvocationTypes());
mStateChangeCallbacks.forEach(Runnable::run);
if (mUnfoldTransitionProvider != null) {
if (unfoldAnimation != null) {
try {
unfoldAnimation.setListener(mUnfoldTransitionProvider);
mUnfoldTransitionProvider.setActive(true);
} catch (RemoteException e) {
// Ignore
}
} else {
mUnfoldTransitionProvider.setActive(false);
}
}
}
/**
@@ -1451,6 +1474,11 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle {
}
}
@Nullable
public ProxyUnfoldTransitionProvider getUnfoldTransitionProvider() {
return mUnfoldTransitionProvider;
}
//
// Recents
//
@@ -191,8 +191,7 @@ public class TaskIconCache implements DisplayInfoChangeListener {
entry.contentDescription = getBadgedContentDescription(
activityInfo, task.key.userId, task.taskDescription);
if (enableOverviewIconMenu()) {
entry.title = Utilities.trim(
activityInfo.applicationInfo.loadLabel(mContext.getPackageManager()));
entry.title = Utilities.trim(activityInfo.loadLabel(mContext.getPackageManager()));
}
}
@@ -77,7 +77,6 @@ import android.view.Choreographer;
import android.view.InputDevice;
import android.view.InputEvent;
import android.view.MotionEvent;
import android.view.SurfaceControl;
import android.view.accessibility.AccessibilityManager;
import androidx.annotation.BinderThread;
@@ -303,15 +302,6 @@ public class TouchInteractionService extends Service {
});
}
@Override
public void onNavigationBarSurface(SurfaceControl surface) {
// TODO: implement
if (surface != null) {
surface.release();
surface = null;
}
}
@BinderThread
public void onSystemUiStateChanged(int stateFlags) {
MAIN_EXECUTOR.execute(() -> executeForTouchInteractionService(tis -> {
@@ -16,6 +16,7 @@
package com.android.quickstep.util;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.quickstep.views.DesktopTaskView.isDesktopModeSupported;
@@ -43,6 +44,7 @@ import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.icons.BitmapInfo;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.PackageItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.quickstep.views.FloatingTaskView;
@@ -123,7 +125,12 @@ public class SplitToWorkspaceController {
intent = appInfo.intent;
user = appInfo.user;
bitmapInfo = appInfo.bitmap;
} else if (tag instanceof FolderInfo fi && fi.itemType == ITEM_TYPE_APP_PAIR) {
// Prompt the user to select something else by wiggling the instructions view
mController.getSplitInstructionsView().goBoing();
return true;
} else {
// Use Launcher's default click handler
return false;
}
@@ -0,0 +1,129 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.util.unfold
import android.app.Activity
import android.os.Trace
import android.view.Surface
import com.android.launcher3.Alarm
import com.android.launcher3.DeviceProfile
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener
import com.android.launcher3.Launcher
import com.android.launcher3.anim.PendingAnimation
import com.android.launcher3.config.FeatureFlags
import com.android.launcher3.util.ActivityLifecycleCallbacksAdapter
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
/** Controls animations that are happening during unfolding foldable devices */
class LauncherUnfoldTransitionController(
private val launcher: Launcher,
private val progressProvider: ProxyUnfoldTransitionProvider
) : OnDeviceProfileChangeListener, ActivityLifecycleCallbacksAdapter, TransitionProgressListener {
private var isTablet: Boolean? = null
private var hasUnfoldTransitionStarted = false
private val timeoutAlarm =
Alarm().apply {
setOnAlarmListener {
onTransitionFinished()
Trace.endAsyncSection("$TAG#startedPreemptively", 0)
}
}
init {
launcher.addOnDeviceProfileChangeListener(this)
launcher.registerActivityLifecycleCallbacks(this)
}
override fun onActivityPaused(activity: Activity) {
progressProvider.removeCallback(this)
}
override fun onActivityResumed(activity: Activity) {
progressProvider.addCallback(this)
}
override fun onDeviceProfileChanged(dp: DeviceProfile) {
if (!FeatureFlags.PREEMPTIVE_UNFOLD_ANIMATION_START.get()) {
return
}
if (isTablet != null && dp.isTablet != isTablet) {
// We should preemptively start the animation only if:
// - We changed to the unfolded screen
// - SystemUI IPC connection is alive, so we won't end up in a situation that we won't
// receive transition progress events from SystemUI later because there was no
// IPC connection established (e.g. because of SystemUI crash)
// - SystemUI has not already sent unfold animation progress events. This might happen
// if Launcher was not open during unfold, in this case we receive the configuration
// change only after we went back to home screen and we don't want to start the
// animation in this case.
if (dp.isTablet && progressProvider.isActive && !hasUnfoldTransitionStarted) {
// Preemptively start the unfold animation to make sure that we have drawn
// the first frame of the animation before the screen gets unblocked
onTransitionStarted()
Trace.beginAsyncSection("$TAG#startedPreemptively", 0)
timeoutAlarm.setAlarm(PREEMPTIVE_UNFOLD_TIMEOUT_MS)
}
if (!dp.isTablet) {
// Reset unfold transition status when folded
hasUnfoldTransitionStarted = false
}
}
isTablet = dp.isTablet
}
override fun onTransitionStarted() {
hasUnfoldTransitionStarted = true
launcher.animationCoordinator.setAnimation(
provider = this,
factory = this::onPrepareUnfoldAnimation,
duration =
1000L // The expected duration for the animation. Then only comes to play if we have
// to run the animation ourselves in case sysui misses the end signal
)
timeoutAlarm.cancelAlarm()
}
override fun onTransitionProgress(progress: Float) {
hasUnfoldTransitionStarted = true
launcher.animationCoordinator.getPlaybackController(this)?.setPlayFraction(progress)
}
override fun onTransitionFinished() {
// Run the animation to end the animation in case it is not already at end progress. It
// will scale the duration to the remaining progress
launcher.animationCoordinator.getPlaybackController(this)?.start()
timeoutAlarm.cancelAlarm()
}
private fun onPrepareUnfoldAnimation(anim: PendingAnimation) {
val dp = launcher.deviceProfile
val rotation = dp.displayInfo.rotation
val isVertical = rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180
UnfoldAnimationBuilder.buildUnfoldAnimation(
launcher,
isVertical,
dp.displayInfo.currentSize,
anim
)
}
companion object {
private const val TAG = "LauncherUnfoldTransitionController"
}
}
@@ -0,0 +1,97 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.util.unfold
import androidx.annotation.AnyThread
import androidx.annotation.FloatRange
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
import com.android.systemui.unfold.UnfoldTransitionProgressProvider
import com.android.systemui.unfold.UnfoldTransitionProgressProvider.TransitionProgressListener
import com.android.systemui.unfold.progress.IUnfoldTransitionListener
import com.android.systemui.unfold.progress.UnfoldRemoteFilter
/** Receives unfold events from remote senders (System UI). */
class ProxyUnfoldTransitionProvider :
UnfoldTransitionProgressProvider, IUnfoldTransitionListener.Stub() {
private val listeners: MutableSet<TransitionProgressListener> = mutableSetOf()
private val delegate = UnfoldRemoteFilter(ProcessedProgressListener())
private var transitionStarted = false
var isActive = false
set(value) {
field = value
if (!value) {
// Finish any active transition
onTransitionFinished()
}
}
@AnyThread
override fun onTransitionStarted() {
MAIN_EXECUTOR.execute(delegate::onTransitionStarted)
}
@AnyThread
override fun onTransitionProgress(progress: Float) {
MAIN_EXECUTOR.execute { delegate.onTransitionProgress(progress) }
}
@AnyThread
override fun onTransitionFinished() {
MAIN_EXECUTOR.execute(delegate::onTransitionFinished)
}
override fun addCallback(listener: TransitionProgressListener) {
listeners += listener
if (transitionStarted) {
// Update the listener in case there was is an active transition
listener.onTransitionStarted()
}
}
override fun removeCallback(listener: TransitionProgressListener) {
listeners -= listener
if (transitionStarted) {
// Finish the transition if it was already running
listener.onTransitionFinished()
}
}
override fun destroy() {
listeners.clear()
}
private inner class ProcessedProgressListener : TransitionProgressListener {
override fun onTransitionStarted() {
if (!transitionStarted) {
transitionStarted = true
listeners.forEach(TransitionProgressListener::onTransitionStarted)
}
}
override fun onTransitionProgress(@FloatRange(from = 0.0, to = 1.0) progress: Float) {
listeners.forEach { it.onTransitionProgress(progress) }
}
override fun onTransitionFinished() {
if (transitionStarted) {
transitionStarted = false
listeners.forEach(TransitionProgressListener::onTransitionFinished)
}
}
}
}
@@ -0,0 +1,167 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep.util.unfold
import android.graphics.Point
import android.view.ViewGroup
import com.android.app.animation.Interpolators.LINEAR
import com.android.app.animation.Interpolators.clampToProgress
import com.android.launcher3.CellLayout
import com.android.launcher3.Launcher
import com.android.launcher3.LauncherAnimUtils.HOTSEAT_SCALE_PROPERTY_FACTORY
import com.android.launcher3.LauncherAnimUtils.SCALE_INDEX_UNFOLD_ANIMATION
import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_X
import com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y
import com.android.launcher3.LauncherAnimUtils.WORKSPACE_SCALE_PROPERTY_FACTORY
import com.android.launcher3.Workspace
import com.android.launcher3.anim.PendingAnimation
import com.android.launcher3.util.HorizontalInsettableView
private typealias ViewGroupAction = (ViewGroup, Boolean) -> Unit
object UnfoldAnimationBuilder {
private val CLIP_CHILDREN: ViewGroupAction = ViewGroup::setClipChildren
private val CLIP_TO_PADDING: ViewGroupAction = ViewGroup::setClipToPadding
data class RestoreInfo(val action: ViewGroupAction, var target: ViewGroup, var value: Boolean)
// Percentage of the width of the quick search bar that will be reduced
// from the both sides of the bar when progress is 0
private const val MAX_WIDTH_INSET_FRACTION = 0.04f
// Scale factor for the whole workspace and hotseat
private const val SCALE_LAUNCHER_FROM = 0.92f
// Translation factor for all the items on the homescreen
private const val TRANSLATION_PERCENTAGE = 0.08f
private fun setClipChildren(
target: ViewGroup,
value: Boolean,
restoreList: MutableList<RestoreInfo>
) {
val originalValue = target.clipChildren
if (originalValue != value) {
target.clipChildren = value
restoreList.add(RestoreInfo(CLIP_CHILDREN, target, originalValue))
}
}
private fun setClipToPadding(
target: ViewGroup,
value: Boolean,
restoreList: MutableList<RestoreInfo>
) {
val originalValue = target.clipToPadding
if (originalValue != value) {
target.clipToPadding = value
restoreList.add(RestoreInfo(CLIP_TO_PADDING, target, originalValue))
}
}
private fun addChildrenAnimation(
itemsContainer: ViewGroup,
isVerticalFold: Boolean,
screenSize: Point,
anim: PendingAnimation
) {
val tempLocation = IntArray(2)
for (i in 0 until itemsContainer.childCount) {
val child = itemsContainer.getChildAt(i)
child.getLocationOnScreen(tempLocation)
if (isVerticalFold) {
val viewCenterX = tempLocation[0] + child.width / 2
val distanceFromScreenCenterToViewCenter = screenSize.x / 2 - viewCenterX
anim.addFloat(
child,
VIEW_TRANSLATE_X,
distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE,
0f,
LINEAR
)
} else {
val viewCenterY = tempLocation[1] + child.height / 2
val distanceFromScreenCenterToViewCenter = screenSize.y / 2 - viewCenterY
anim.addFloat(
child,
VIEW_TRANSLATE_Y,
distanceFromScreenCenterToViewCenter * TRANSLATION_PERCENTAGE,
0f,
LINEAR
)
}
}
}
/**
* Builds an animation for the unfold experience and adds it to the provided PendingAnimation
*/
fun buildUnfoldAnimation(
launcher: Launcher,
isVerticalFold: Boolean,
screenSize: Point,
anim: PendingAnimation
) {
val restoreList = ArrayList<RestoreInfo>()
val registerViews: (CellLayout) -> Unit = { cellLayout ->
setClipChildren(cellLayout, false, restoreList)
setClipToPadding(cellLayout, false, restoreList)
addChildrenAnimation(cellLayout.shortcutsAndWidgets, isVerticalFold, screenSize, anim)
}
val workspace: Workspace<*> = launcher.workspace
val hotseat = launcher.hotseat
// Animation icons from workspace for all orientations
workspace.forEachVisiblePage { registerViews(it as CellLayout) }
setClipChildren(workspace, false, restoreList)
setClipToPadding(workspace, true, restoreList)
// Workspace scale
launcher.workspace.setPivotToScaleWithSelf(launcher.hotseat)
val interpolator = clampToProgress(LINEAR, 0f, 1f)
anim.addFloat(
workspace,
WORKSPACE_SCALE_PROPERTY_FACTORY[SCALE_INDEX_UNFOLD_ANIMATION],
SCALE_LAUNCHER_FROM,
1f,
interpolator
)
anim.addFloat(
hotseat,
HOTSEAT_SCALE_PROPERTY_FACTORY[SCALE_INDEX_UNFOLD_ANIMATION],
SCALE_LAUNCHER_FROM,
1f,
interpolator
)
if (isVerticalFold) {
if (hotseat.qsb is HorizontalInsettableView) {
anim.addFloat(
hotseat.qsb as HorizontalInsettableView,
HorizontalInsettableView.HORIZONTAL_INSETS,
MAX_WIDTH_INSET_FRACTION,
0f,
LINEAR
)
}
registerViews(hotseat)
}
anim.addEndListener { restoreList.forEach { it.action(it.target, it.value) } }
}
}
@@ -16,6 +16,8 @@
package com.android.quickstep.views;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.os.Bundle;
import android.util.AttributeSet;
@@ -26,9 +28,15 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.dynamicanimation.animation.DynamicAnimation;
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.dynamicanimation.animation.SpringForce;
import com.android.app.animation.Interpolators;
import com.android.launcher3.LauncherState;
import com.android.launcher3.R;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.statemanager.StatefulActivity;
@@ -40,7 +48,11 @@ import com.android.launcher3.statemanager.StatefulActivity;
* Appears and disappears concurrently with a FloatingTaskView.
*/
public class SplitInstructionsView extends LinearLayout {
private static final int BOUNCE_DURATION = 250;
private static final float BOUNCE_HEIGHT = 20;
private final StatefulActivity mLauncher;
public boolean mIsCurrentlyAnimating = false;
public static final FloatProperty<SplitInstructionsView> UNFOLD =
new FloatProperty<>("SplitInstructionsUnfold") {
@@ -55,6 +67,19 @@ public class SplitInstructionsView extends LinearLayout {
}
};
public static final FloatProperty<SplitInstructionsView> TRANSLATE_Y =
new FloatProperty<>("SplitInstructionsTranslateY") {
@Override
public void setValue(SplitInstructionsView splitInstructionsView, float v) {
splitInstructionsView.setTranslationY(v);
}
@Override
public Float get(SplitInstructionsView splitInstructionsView) {
return splitInstructionsView.getTranslationY();
}
};
public SplitInstructionsView(Context context) {
this(context, null);
}
@@ -143,4 +168,42 @@ public class SplitInstructionsView extends LinearLayout {
getMeasuredWidth()
);
}
/**
* Draws attention to the split instructions view by bouncing it up and down.
*/
public void goBoing() {
if (mIsCurrentlyAnimating) {
return;
}
float restingY = getTranslationY();
float bounceToY = restingY - Utilities.dpToPx(BOUNCE_HEIGHT);
PendingAnimation anim = new PendingAnimation(BOUNCE_DURATION);
// Animate the view lifting up to a higher position
anim.addFloat(this, TRANSLATE_Y, restingY, bounceToY, Interpolators.STANDARD);
anim.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {
mIsCurrentlyAnimating = true;
}
@Override
public void onAnimationEnd(Animator animation) {
// Create a low stiffness, medium bounce spring centering at the rest position
SpringForce spring = new SpringForce(restingY)
.setDampingRatio(SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
.setStiffness(SpringForce.STIFFNESS_LOW);
// Animate the view getting pulled back to rest position by the spring
SpringAnimation springAnim = new SpringAnimation(SplitInstructionsView.this,
DynamicAnimation.TRANSLATION_Y).setSpring(spring).setStartValue(bounceToY);
springAnim.addEndListener((a, b, c, d) -> mIsCurrentlyAnimating = false);
springAnim.start();
}
});
anim.buildAnim().start();
}
}
@@ -45,24 +45,22 @@
android:background="?attr/widgetPickerPrimarySurfaceColor"
android:clipToPadding="false"
android:elevation="0.1dp"
android:paddingBottom="8dp"
android:paddingBottom="16dp"
android:paddingHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
launcher:layout_sticky="true">
<include layout="@layout/widgets_search_bar" />
</FrameLayout>
<LinearLayout
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/suggestions_header"
android:layout_marginTop="8dp"
android:layout_marginHorizontal="@dimen/widget_list_horizontal_margin_two_pane"
android:paddingBottom="16dp"
android:orientation="horizontal"
android:background="?attr/widgetPickerPrimarySurfaceColor"
launcher:layout_sticky="true">
</LinearLayout>
</FrameLayout>
</com.android.launcher3.views.StickyHeaderLayout>
</FrameLayout>
</merge>
+3
View File
@@ -252,6 +252,9 @@
<!-- Used for custom widgets -->
<array name="custom_widget_providers"/>
<!-- Embed parameters -->
<dimen name="activity_split_ratio" format="float">0.5</dimen>
<integer name="min_width_split">720</integer>
<!-- Skip "Install to private" long-press shortcut packages name -->
<string-array name="skip_private_profile_shortcut_packages" translatable="false">
+18
View File
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources
xmlns:window="http://schemas.android.com/apk/res-auto">
<!-- Automatically split the following activity pairs. -->
<SplitPairRule
window:splitRatio="@dimen/activity_split_ratio"
window:splitLayoutDirection="locale"
window:splitMinWidthDp="@integer/min_width_split"
window:splitMaxAspectRatioInPortrait="alwaysAllow"
window:finishPrimaryWithSecondary="never"
window:finishSecondaryWithPrimary="always"
window:clearTop="false">
<SplitPairFilter
window:primaryActivityName="com.android.launcher3.settings.SettingsActivity"
window:secondaryActivityName="com.android.launcher3.settings.SettingsActivity"/>
</SplitPairRule>
</resources>
+6
View File
@@ -152,6 +152,7 @@ import androidx.annotation.RequiresApi;
import androidx.annotation.StringRes;
import androidx.annotation.UiThread;
import androidx.annotation.VisibleForTesting;
import androidx.window.embedding.RuleController;
import com.android.launcher3.DropTarget.DragObject;
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
@@ -580,6 +581,11 @@ public class Launcher extends StatefulActivity<LauncherState>
}
setTitle(R.string.home_screen);
mStartupLatencyLogger.logEnd(LAUNCHER_LATENCY_STARTUP_ACTIVITY_ON_CREATE);
if (com.android.launcher3.Flags.enableTwoPaneLauncherSettings()) {
RuleController.getInstance(this).setRules(
RuleController.parseRules(this, R.xml.split_configuration));
}
}
protected ModelCallbacks createModelCallbacks() {
@@ -353,9 +353,17 @@ public class PackageUpdatedTask extends BaseModelUpdateTask {
}
if (!removedPackages.isEmpty() || !removedComponents.isEmpty()) {
Predicate<ItemInfo> removeMatch = ItemInfoMatcher.ofPackages(removedPackages, mUser)
.or(ItemInfoMatcher.ofComponents(removedComponents, mUser))
.and(ItemInfoMatcher.ofItemIds(forceKeepShortcuts).negate());
// This predicate is used to mark an ItemInfo for removal if its package or component
// is marked for removal.
Predicate<ItemInfo> removeAppMatch =
ItemInfoMatcher.ofPackages(removedPackages, mUser)
.or(ItemInfoMatcher.ofComponents(removedComponents, mUser))
.and(ItemInfoMatcher.ofItemIds(forceKeepShortcuts).negate());
// This predicate is used to mark an app pair for removal if it contains an app marked
// for removal.
Predicate<ItemInfo> removeAppPairMatch =
ItemInfoMatcher.forAppPairMatch(removeAppMatch);
Predicate<ItemInfo> removeMatch = removeAppMatch.or(removeAppPairMatch);
deleteAndBindComponentsRemoved(removeMatch,
"removed because the corresponding package or component is removed. "
+ "mOp=" + mOp + " removedPackages=" + removedPackages.stream().collect(
@@ -69,6 +69,15 @@ public abstract class ItemInfoMatcher {
.anyMatch(childOperator);
}
/**
* Returns a matcher for items within app pairs.
*/
public static Predicate<ItemInfo> forAppPairMatch(Predicate<ItemInfo> childOperator) {
Predicate<ItemInfo> isAppPair = info ->
info instanceof FolderInfo fi && fi.itemType == Favorites.ITEM_TYPE_APP_PAIR;
return isAppPair.and(forFolderMatch(childOperator));
}
/**
* Returns a matcher for items with provided ids
*/
@@ -56,7 +56,7 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet {
private static final int MAXIMUM_WIDTH_LEFT_PANE_FOLDABLE_DP = 395;
private static final String SUGGESTIONS_PACKAGE_NAME = "widgets_list_suggestions_entry";
private LinearLayout mSuggestedWidgetsContainer;
private FrameLayout mSuggestedWidgetsContainer;
private WidgetsListHeader mSuggestedWidgetsHeader;
private LinearLayout mRightPane;