Snap for 11343021 from 5a1c88629b to 24Q2-release
Change-Id: Ieb779ad2a63827731a0668b71f1e0656e16ee93c
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+129
@@ -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>
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user