diff --git a/Android.bp b/Android.bp index 7e6a565f46..1b6ffe42b7 100644 --- a/Android.bp +++ b/Android.bp @@ -16,6 +16,8 @@ package { default_applicable_licenses: ["packages_apps_Launcher3_license"], } +min_launcher3_sdk_version = "26" + // Added automatically by a large-scale-change // See: http://go/android-license-faq license { @@ -92,7 +94,7 @@ java_library { srcs: ["src_plugins/**/*.java"], sdk_version: "current", - min_sdk_version: "28", + min_sdk_version: min_launcher3_sdk_version, } // Library with all the dependencies for building Launcher3 @@ -114,7 +116,7 @@ android_library { ], manifest: "AndroidManifest-common.xml", sdk_version: "current", - min_sdk_version: "26", + min_sdk_version: min_launcher3_sdk_version, lint: { baseline_filename: "lint-baseline-res-lib.xml", }, @@ -128,7 +130,7 @@ android_library { srcs: ["src_build_config/**/*.java"], static_libs: ["Launcher3ResLib"], sdk_version: "current", - min_sdk_version: "26", + min_sdk_version: min_launcher3_sdk_version, manifest: "AndroidManifest-common.xml", lint: { baseline_filename: "lint-baseline-common-deps-lib.xml", @@ -160,8 +162,8 @@ android_app { }, sdk_version: "current", - min_sdk_version: "26", - target_sdk_version: "29", + min_sdk_version: min_launcher3_sdk_version, + target_sdk_version: "current", privileged: true, system_ext_specific: true, @@ -195,7 +197,7 @@ android_library { "SystemUI-statsd", ], manifest: "quickstep/AndroidManifest.xml", - min_sdk_version: "28", + min_sdk_version: "current", } @@ -244,7 +246,7 @@ android_library { "go/AndroidManifest.xml", "AndroidManifest-common.xml", ], - min_sdk_version: "29", + min_sdk_version: "current", lint: { baseline_filename: "lint-baseline-go-res-lib.xml", }, diff --git a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java index 907767551c..117b8e6b92 100644 --- a/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java +++ b/go/quickstep/src/com/android/quickstep/TaskOverlayFactoryGo.java @@ -27,6 +27,7 @@ import android.content.Intent; import android.graphics.Matrix; import android.net.Uri; import android.os.SystemClock; +import android.os.UserManager; import android.provider.Settings; import android.text.TextUtils; @@ -96,7 +97,10 @@ public final class TaskOverlayFactoryGo extends TaskOverlayFactory { } getActionsView().updateDisabledFlags(DISABLED_ROTATED, rotated); - boolean isAllowedByPolicy = mThumbnailView.isRealSnapshot(); + // Disable Overview Actions for Work Profile apps + boolean isManagedProfileTask = + UserManager.get(mApplicationContext).isManagedProfile(task.key.userId); + boolean isAllowedByPolicy = mThumbnailView.isRealSnapshot() && !isManagedProfileTask; getActionsView().setCallbacks(new OverlayUICallbacksGoImpl(isAllowedByPolicy, task)); mTaskPackageName = task.key.getPackageName(); @@ -127,8 +131,7 @@ public final class TaskOverlayFactoryGo extends TaskOverlayFactory { /** * Creates and sends an Intent corresponding to the button that was clicked */ - @VisibleForTesting - public void sendNIUIntent(String actionType) { + private void sendNIUIntent(String actionType) { Intent intent = createNIUIntent(actionType); // Only add and send the image if the appropriate permissions are held if (mAssistPermissionsEnabled) { diff --git a/quickstep/res/layout/taskbar.xml b/quickstep/res/layout/taskbar.xml index 240fe556da..c4362214cb 100644 --- a/quickstep/res/layout/taskbar.xml +++ b/quickstep/res/layout/taskbar.xml @@ -22,9 +22,29 @@ + android:gravity="center" + android:forceHasOverlappingRendering="false" + android:layout_gravity="bottom" > + + + + + + - - - - diff --git a/quickstep/res/values/dimens.xml b/quickstep/res/values/dimens.xml index 735cb24f82..50453ace16 100644 --- a/quickstep/res/values/dimens.xml +++ b/quickstep/res/values/dimens.xml @@ -149,4 +149,5 @@ 8dp 16dp + 16dp diff --git a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java index e777ee7354..0b41f15b42 100644 --- a/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/BaseQuickstepLauncher.java @@ -20,7 +20,7 @@ import static com.android.launcher3.AbstractFloatingView.TYPE_HIDE_BACK_BUTTON; import static com.android.launcher3.LauncherState.FLAG_HIDE_BACK_BUTTON; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.NO_OFFSET; -import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN; +import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID; import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR; import static com.android.quickstep.SysUINavigationMode.Mode.TWO_BUTTONS; import static com.android.quickstep.util.NavigationModeFeatureFlag.LIVE_TILE; @@ -30,7 +30,6 @@ import android.animation.AnimatorSet; import android.animation.ValueAnimator; import android.app.ActivityOptions; import android.content.ComponentName; -import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.ServiceConnection; @@ -51,13 +50,11 @@ import com.android.launcher3.proxy.StartActivityParams; import com.android.launcher3.statehandlers.BackButtonAlphaHandler; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.StateManager.StateHandler; -import com.android.launcher3.taskbar.TaskbarActivityContext; -import com.android.launcher3.taskbar.TaskbarController; +import com.android.launcher3.taskbar.LauncherTaskbarUIController; +import com.android.launcher3.taskbar.TaskbarManager; import com.android.launcher3.taskbar.TaskbarStateHandler; -import com.android.launcher3.taskbar.TaskbarView; import com.android.launcher3.uioverrides.RecentsViewStateController; import com.android.launcher3.util.ActivityOptionsWrapper; -import com.android.launcher3.util.DisplayController; import com.android.launcher3.util.ObjectWrapper; import com.android.launcher3.util.UiThreadHelper; import com.android.quickstep.RecentsModel; @@ -67,6 +64,7 @@ import com.android.quickstep.SysUINavigationMode.NavigationModeChangeListener; import com.android.quickstep.SystemUiProxy; import com.android.quickstep.TaskUtils; import com.android.quickstep.TouchInteractionService; +import com.android.quickstep.TouchInteractionService.TISBinder; import com.android.quickstep.util.RemoteAnimationProvider; import com.android.quickstep.util.RemoteFadeOutAnimationListener; import com.android.quickstep.util.SplitSelectStateController; @@ -88,8 +86,6 @@ public abstract class BaseQuickstepLauncher extends Launcher private DepthController mDepthController = new DepthController(this); private QuickstepTransitionManager mAppTransitionManager; - private ServiceConnection mTisBinderConnection; - protected TouchInteractionService.TISBinder mTisBinder; /** * Reusable command for applying the back button alpha on the background thread. @@ -100,8 +96,20 @@ public abstract class BaseQuickstepLauncher extends Launcher private OverviewActionsView mActionsView; - private @Nullable TaskbarController mTaskbarController; + private @Nullable TaskbarManager mTaskbarManager; + private @Nullable LauncherTaskbarUIController mTaskbarUIController; + private final ServiceConnection mTisBinderConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName componentName, IBinder iBinder) { + mTaskbarManager = ((TISBinder) iBinder).getTaskbarManager(); + mTaskbarManager.setLauncher(BaseQuickstepLauncher.this); + } + + @Override + public void onServiceDisconnected(ComponentName componentName) { } + }; private final TaskbarStateHandler mTaskbarStateHandler = new TaskbarStateHandler(this); + // Will be updated when dragging from taskbar. private @Nullable DragOptions mNextWorkspaceDragOptions = null; private SplitPlaceholderView mSplitPlaceholderView; @@ -111,24 +119,6 @@ public abstract class BaseQuickstepLauncher extends Launcher super.onCreate(savedInstanceState); SysUINavigationMode.INSTANCE.get(this).addModeChangeListener(this); addMultiWindowModeChangedListener(mDepthController); - setupTouchInteractionServiceBinder(); - } - - private void setupTouchInteractionServiceBinder() { - Intent intent = new Intent(this, TouchInteractionService.class); - mTisBinderConnection = new ServiceConnection() { - @Override - public void onServiceConnected(ComponentName componentName, IBinder binder) { - mTisBinder = ((TouchInteractionService.TISBinder) binder); - mTisBinder.setTaskbarOverviewProxyDelegate(mTaskbarController); - } - - @Override - public void onServiceDisconnected(ComponentName componentName) { - mTisBinder = null; - } - }; - bindService(intent, mTisBinderConnection, 0); } @Override @@ -136,15 +126,12 @@ public abstract class BaseQuickstepLauncher extends Launcher mAppTransitionManager.onActivityDestroyed(); SysUINavigationMode.INSTANCE.get(this).removeModeChangeListener(this); - if (mTaskbarController != null) { - mTaskbarController.cleanup(); - mTaskbarController = null; - if (mTisBinder != null) { - mTisBinder.setTaskbarOverviewProxyDelegate(null); - unbindService(mTisBinderConnection); - } - } + + unbindService(mTisBinderConnection); + if (mTaskbarManager != null) { + mTaskbarManager.setLauncher(null); + } super.onDestroy(); } @@ -271,37 +258,12 @@ public abstract class BaseQuickstepLauncher extends Launcher mAppTransitionManager = new QuickstepTransitionManager(this); mAppTransitionManager.registerRemoteAnimations(); - addTaskbarIfNecessary(); - addOnDeviceProfileChangeListener(newDp -> addTaskbarIfNecessary()); + bindService(new Intent(this, TouchInteractionService.class), mTisBinderConnection, 0); + } - @Override - public void onDisplayInfoChanged(Context context, DisplayController.Info info, - int flags) { - super.onDisplayInfoChanged(context, info, flags); - if ((flags & CHANGE_ACTIVE_SCREEN) != 0) { - addTaskbarIfNecessary(); - } - } - - private void addTaskbarIfNecessary() { - if (mTaskbarController != null) { - mTaskbarController.cleanup(); - if (mTisBinder != null) { - mTisBinder.setTaskbarOverviewProxyDelegate(null); - } - mTaskbarController = null; - } - if (mDeviceProfile.isTaskbarPresent) { - TaskbarView taskbarViewOnHome = (TaskbarView) mHotseat.getTaskbarView(); - TaskbarActivityContext taskbarActivityContext = new TaskbarActivityContext(this); - mTaskbarController = new TaskbarController(this, - taskbarActivityContext.getTaskbarContainerView(), taskbarViewOnHome); - mTaskbarController.init(); - if (mTisBinder != null) { - mTisBinder.setTaskbarOverviewProxyDelegate(mTaskbarController); - } - } + public void setTaskbarUIController(LauncherTaskbarUIController taskbarUIController) { + mTaskbarUIController = taskbarUIController; } public T getActionsView() { @@ -331,8 +293,8 @@ public abstract class BaseQuickstepLauncher extends Launcher return mDepthController; } - public @Nullable TaskbarController getTaskbarController() { - return mTaskbarController; + public @Nullable LauncherTaskbarUIController getTaskbarUIController() { + return mTaskbarUIController; } public TaskbarStateHandler getTaskbarStateHandler() { @@ -340,14 +302,9 @@ public abstract class BaseQuickstepLauncher extends Launcher } @Override - public boolean isViewInTaskbar(View v) { - return mTaskbarController != null && mTaskbarController.isViewInTaskbar(v); - } - public boolean supportsAdaptiveIconAnimation(View clickedView) { return mAppTransitionManager.hasControlRemoteAppTransitionPermission() - && FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get() - && !isViewInTaskbar(clickedView); + && FeatureFlags.ADAPTIVE_ICON_WINDOW_ANIM.get(); } @Override @@ -393,8 +350,8 @@ public abstract class BaseQuickstepLauncher extends Launcher @Override public float getNormalTaskbarScale() { - if (mTaskbarController != null) { - return mTaskbarController.getTaskbarScaleOnHome(); + if (mTaskbarUIController != null) { + return mTaskbarUIController.getTaskbarScaleOnHome(); } return super.getNormalTaskbarScale(); } @@ -416,8 +373,8 @@ public abstract class BaseQuickstepLauncher extends Launcher } if ((changeBits & ACTIVITY_STATE_RESUMED) != 0) { - if (mTaskbarController != null) { - mTaskbarController.onLauncherResumedOrPaused(hasBeenResumed()); + if (mTaskbarUIController != null) { + mTaskbarUIController.onLauncherResumedOrPaused(hasBeenResumed()); } } @@ -499,6 +456,8 @@ public abstract class BaseQuickstepLauncher extends Launcher // Also allow swiping to folders break; } + // Reset any existing launch cookies associated with the cookie + opts.setLaunchCookie(ObjectWrapper.wrap(NO_MATCHING_ID)); return; } switch (info.itemType) { @@ -509,6 +468,8 @@ public abstract class BaseQuickstepLauncher extends Launcher // Fall through and continue if it's an app, shortcut, or widget break; default: + // Reset any existing launch cookies associated with the cookie + opts.setLaunchCookie(ObjectWrapper.wrap(NO_MATCHING_ID)); return; } opts.setLaunchCookie(ObjectWrapper.wrap(new Integer(info.id))); diff --git a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java index 1b8fcb3c67..80754a00f4 100644 --- a/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java +++ b/quickstep/src/com/android/launcher3/QuickstepTransitionManager.java @@ -1252,7 +1252,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener final boolean launchingFromWidget = mV instanceof LauncherAppWidgetHostView; final boolean launchingFromRecents = isLaunchingFromRecents(mV, appTargets); - final boolean launchingFromTaskbar = mLauncher.isViewInTaskbar(mV); if (launchingFromWidget) { composeWidgetLaunchAnimator(anim, (LauncherAppWidgetHostView) mV, appTargets, wallpaperTargets, nonAppTargets); @@ -1263,8 +1262,6 @@ public class QuickstepTransitionManager implements OnDeviceProfileChangeListener launcherClosing); addCujInstrumentation( anim, InteractionJankMonitorWrapper.CUJ_APP_LAUNCH_FROM_RECENTS); - } else if (launchingFromTaskbar) { - // TODO } else { composeIconLaunchAnimator(anim, mV, appTargets, wallpaperTargets, nonAppTargets, launcherClosing); diff --git a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java index 5dcf84c249..85e5ab0a9b 100644 --- a/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java +++ b/quickstep/src/com/android/launcher3/hybridhotseat/HotseatPredictionController.java @@ -263,8 +263,8 @@ public class HotseatPredictionController implements DragController.DragListener, removeOutlineDrawings(); } - if (mLauncher.getTaskbarController() != null) { - mLauncher.getTaskbarController().onHotseatUpdated(); + if (mLauncher.getTaskbarUIController() != null) { + mLauncher.getTaskbarUIController().onHotseatUpdated(); } } diff --git a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java index 59d0afa47f..e608885491 100644 --- a/quickstep/src/com/android/launcher3/statehandlers/DepthController.java +++ b/quickstep/src/com/android/launcher3/statehandlers/DepthController.java @@ -217,17 +217,11 @@ public class DepthController implements StateHandler, } if (supportsBlur) { - final int blur; - if (mLauncher.isInState(LauncherState.ALL_APPS) && mDepth == 1) { - // All apps has a solid background. We don't need to draw blurs after it's fully - // visible. This will take us out of GPU composition, saving battery and increasing - // performance. - blur = 0; - } else { - blur = (int) (mDepth * mMaxBlurRadius); - } + boolean isOpaque = mLauncher.getScrimView().isFullyOpaque(); + int blur = isOpaque ? 0 : (int) (mDepth * mMaxBlurRadius); new TransactionCompat() .setBackgroundBlurRadius(mSurface, blur) + .setOpaque(mSurface, isOpaque) .apply(); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java b/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java index 0d4130d1bc..540f748313 100644 --- a/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java +++ b/quickstep/src/com/android/launcher3/taskbar/ButtonProvider.java @@ -16,12 +16,17 @@ package com.android.launcher3.taskbar; +import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK; +import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME; +import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH; +import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS; + import android.annotation.DrawableRes; -import android.content.Context; import android.view.View; import android.widget.ImageView; import com.android.launcher3.R; +import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton; /** * Creates Buttons for Taskbar for 3 button nav. @@ -29,47 +34,46 @@ import com.android.launcher3.R; */ public class ButtonProvider { - private int mMarginLeftRight; - private final Context mContext; + private final int mMarginLeftRight; + private final TaskbarActivityContext mContext; - public ButtonProvider(Context context) { + public ButtonProvider(TaskbarActivityContext context) { mContext = context; - } - - public void setMarginLeftRight(int margin) { - mMarginLeftRight = margin; + mMarginLeftRight = context.getResources() + .getDimensionPixelSize(R.dimen.taskbar_icon_spacing); } public View getBack() { // Back button - return getButtonForDrawable(R.drawable.ic_sysbar_back); + return getButtonForDrawable(R.drawable.ic_sysbar_back, BUTTON_BACK); } public View getDown() { // Ime down button - return getButtonForDrawable(R.drawable.ic_sysbar_back); + return getButtonForDrawable(R.drawable.ic_sysbar_back, BUTTON_BACK); } public View getHome() { // Home button - return getButtonForDrawable(R.drawable.ic_sysbar_home); + return getButtonForDrawable(R.drawable.ic_sysbar_home, BUTTON_HOME); } public View getRecents() { // Recents button - return getButtonForDrawable(R.drawable.ic_sysbar_recent); + return getButtonForDrawable(R.drawable.ic_sysbar_recent, BUTTON_RECENTS); } public View getImeSwitcher() { // IME Switcher Button - return getButtonForDrawable(R.drawable.ic_ime_switcher); + return getButtonForDrawable(R.drawable.ic_ime_switcher, BUTTON_IME_SWITCH); } - private View getButtonForDrawable(@DrawableRes int drawableId) { + private View getButtonForDrawable(@DrawableRes int drawableId, @TaskbarButton int buttonType) { ImageView buttonView = new ImageView(mContext); buttonView.setImageResource(drawableId); buttonView.setBackgroundResource(R.drawable.taskbar_icon_click_feedback_roundrect); buttonView.setPadding(mMarginLeftRight, 0, mMarginLeftRight, 0); + buttonView.setOnClickListener(view -> mContext.onNavigationButtonClick(buttonType)); return buttonView; } diff --git a/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java b/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java index bb3669beed..287caab44b 100644 --- a/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/ImeBarView.java @@ -16,9 +16,6 @@ package com.android.launcher3.taskbar; -import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK; -import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_IME_SWITCH; - import android.content.Context; import android.util.AttributeSet; import android.view.View; @@ -29,7 +26,6 @@ import com.android.launcher3.views.ActivityContext; public class ImeBarView extends RelativeLayout { private ButtonProvider mButtonProvider; - private TaskbarController.TaskbarViewCallbacks mControllerCallbacks; private View mImeView; public ImeBarView(Context context) { @@ -44,12 +40,9 @@ public class ImeBarView extends RelativeLayout { super(context, attrs, defStyleAttr); } - public void construct(ButtonProvider buttonProvider) { + public void init(ButtonProvider buttonProvider) { mButtonProvider = buttonProvider; - } - public void init(TaskbarController.TaskbarViewCallbacks taskbarCallbacks) { - mControllerCallbacks = taskbarCallbacks; ActivityContext context = getActivityContext(); RelativeLayout.LayoutParams imeParams = new RelativeLayout.LayoutParams( context.getDeviceProfile().iconSizePx, @@ -64,24 +57,16 @@ public class ImeBarView extends RelativeLayout { // Down Arrow View downView = mButtonProvider.getDown(); - downView.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick( - BUTTON_BACK)); downView.setLayoutParams(downParams); downView.setRotation(-90); addView(downView); // IME switcher button mImeView = mButtonProvider.getImeSwitcher(); - mImeView.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick( - BUTTON_IME_SWITCH)); mImeView.setLayoutParams(imeParams); addView(mImeView); } - public void cleanup() { - removeAllViews(); - } - public void setImeSwitcherVisibility(boolean show) { mImeView.setVisibility(show ? VISIBLE : GONE); } diff --git a/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java new file mode 100644 index 0000000000..67264194b2 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/LauncherTaskbarUIController.java @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.taskbar; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.graphics.Rect; +import android.graphics.RectF; +import android.view.MotionEvent; + +import androidx.annotation.Nullable; + +import com.android.launcher3.BaseQuickstepLauncher; +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.LauncherState; +import com.android.launcher3.QuickstepTransitionManager; +import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.anim.PendingAnimation; +import com.android.launcher3.states.StateAnimationConfig; + + +/** + * A data source which integrates with a Launcher instance + * TODO: Rename to have Launcher prefix + */ + +public class LauncherTaskbarUIController extends TaskbarUIController { + + private final BaseQuickstepLauncher mLauncher; + private final TaskbarStateHandler mTaskbarStateHandler; + private final TaskbarAnimationController mTaskbarAnimationController; + private final TaskbarHotseatController mHotseatController; + + private final TaskbarActivityContext mContext; + final TaskbarContainerView mTaskbarContainerView; + final TaskbarView mTaskbarView; + + private @Nullable Animator mAnimator; + private boolean mIsAnimatingToLauncher; + + public LauncherTaskbarUIController( + BaseQuickstepLauncher launcher, TaskbarActivityContext context) { + mContext = context; + mTaskbarContainerView = context.getDragLayer(); + mTaskbarView = mTaskbarContainerView.findViewById(R.id.taskbar_view); + + mLauncher = launcher; + mTaskbarStateHandler = mLauncher.getTaskbarStateHandler(); + mTaskbarAnimationController = new TaskbarAnimationController(mLauncher, + createTaskbarAnimationControllerCallbacks()); + mHotseatController = new TaskbarHotseatController( + mLauncher, mTaskbarView::updateHotseatItems); + } + + @Override + protected void onCreate() { + mTaskbarStateHandler.setAnimationController(mTaskbarAnimationController); + mTaskbarAnimationController.init(); + mHotseatController.init(); + setTaskbarViewVisible(!mLauncher.hasBeenResumed()); + alignRealHotseatWithTaskbar(); + mLauncher.setTaskbarUIController(this); + } + + @Override + protected void onDestroy() { + if (mAnimator != null) { + // End this first, in case it relies on properties that are about to be cleaned up. + mAnimator.end(); + } + mTaskbarStateHandler.setAnimationController(null); + mTaskbarAnimationController.cleanup(); + mHotseatController.cleanup(); + setTaskbarViewVisible(true); + mLauncher.getHotseat().setIconsAlpha(1f); + mLauncher.setTaskbarUIController(null); + } + + @Override + protected boolean isTaskbarTouchable() { + return !mIsAnimatingToLauncher; + } + + private TaskbarAnimationControllerCallbacks createTaskbarAnimationControllerCallbacks() { + return new TaskbarAnimationControllerCallbacks() { + @Override + public void updateTaskbarBackgroundAlpha(float alpha) { + mTaskbarContainerView.setTaskbarBackgroundAlpha(alpha); + } + + @Override + public void updateTaskbarVisibilityAlpha(float alpha) { + mTaskbarView.setAlpha(alpha); + } + + @Override + public void updateImeBarVisibilityAlpha(float alpha) { + mTaskbarContainerView.updateImeBarVisibilityAlpha(alpha); + } + + @Override + public void updateTaskbarScale(float scale) { + mTaskbarView.setScaleX(scale); + mTaskbarView.setScaleY(scale); + } + + @Override + public void updateTaskbarTranslationY(float translationY) { + if (translationY < 0) { + // Resize to accommodate the max translation we'll reach. + mContext.setTaskbarWindowHeight(mContext.getDeviceProfile().taskbarSize + + mLauncher.getHotseat().getTaskbarOffsetY()); + } else { + mContext.setTaskbarWindowHeight(mContext.getDeviceProfile().taskbarSize); + } + mTaskbarView.setTranslationY(translationY); + } + }; + } + + /** + * Should be called from onResume() and onPause(), and animates the Taskbar accordingly. + */ + public void onLauncherResumedOrPaused(boolean isResumed) { + long duration = QuickstepTransitionManager.CONTENT_ALPHA_DURATION; + if (mAnimator != null) { + mAnimator.cancel(); + } + if (isResumed) { + mAnimator = createAnimToLauncher(null, duration); + } else { + mAnimator = createAnimToApp(duration); + } + mAnimator.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mAnimator = null; + } + }); + mAnimator.start(); + } + + /** + * Create Taskbar animation when going from an app to Launcher. + * @param toState If known, the state we will end up in when reaching Launcher. + */ + public Animator createAnimToLauncher(@Nullable LauncherState toState, long duration) { + PendingAnimation anim = new PendingAnimation(duration); + anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(0, duration)); + if (toState != null) { + mTaskbarStateHandler.setStateWithAnimation(toState, new StateAnimationConfig(), anim); + } + + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mIsAnimatingToLauncher = true; + mTaskbarView.setHolesAllowedInLayout(true); + mTaskbarView.updateHotseatItemsVisibility(); + } + + @Override + public void onAnimationEnd(Animator animation) { + mIsAnimatingToLauncher = false; + setTaskbarViewVisible(false); + } + }); + + return anim.buildAnim(); + } + + private Animator createAnimToApp(long duration) { + PendingAnimation anim = new PendingAnimation(duration); + anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(1, duration)); + anim.addListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mTaskbarView.updateHotseatItemsVisibility(); + setTaskbarViewVisible(true); + } + + @Override + public void onAnimationEnd(Animator animation) { + mTaskbarView.setHolesAllowedInLayout(false); + } + }); + return anim.buildAnim(); + } + + @Override + protected void onImeVisible(TaskbarContainerView containerView, boolean isVisible) { + mTaskbarAnimationController.animateToVisibilityForIme(isVisible ? 0 : 1); + } + + /** + * Should be called when one or more items in the Hotseat have changed. + */ + public void onHotseatUpdated() { + mHotseatController.onHotseatUpdated(); + } + + /** + * @param ev MotionEvent in screen coordinates. + * @return Whether any Taskbar item could handle the given MotionEvent if given the chance. + */ + public boolean isEventOverAnyTaskbarItem(MotionEvent ev) { + return mTaskbarView.isEventOverAnyItem(ev); + } + + public boolean isDraggingItem() { + return mTaskbarView.isDraggingItem(); + } + + /** + * Pads the Hotseat to line up exactly with Taskbar's copy of the Hotseat. + */ + @Override + public void alignRealHotseatWithTaskbar() { + Rect hotseatBounds = new Rect(); + DeviceProfile grid = mLauncher.getDeviceProfile(); + int hotseatHeight = grid.workspacePadding.bottom + grid.taskbarSize; + int taskbarOffset = mLauncher.getHotseat().getTaskbarOffsetY(); + int hotseatTopDiff = hotseatHeight - grid.taskbarSize - taskbarOffset; + int hotseatBottomDiff = taskbarOffset; + + RectF hotseatBoundsF = mTaskbarView.getHotseatBounds(); + Utilities.scaleRectFAboutPivot(hotseatBoundsF, getTaskbarScaleOnHome(), + mTaskbarView.getPivotX(), mTaskbarView.getPivotY()); + hotseatBoundsF.round(hotseatBounds); + mLauncher.getHotseat().setPadding(hotseatBounds.left, + hotseatBounds.top + hotseatTopDiff, + mTaskbarView.getWidth() - hotseatBounds.right, + mTaskbarView.getHeight() - hotseatBounds.bottom + hotseatBottomDiff); + } + + /** + * Returns the ratio of the taskbar icon size on home vs in an app. + */ + public float getTaskbarScaleOnHome() { + DeviceProfile inAppDp = mContext.getDeviceProfile(); + DeviceProfile onHomeDp = mLauncher.getDeviceProfile(); + return (float) onHomeDp.cellWidthPx / inAppDp.cellWidthPx; + } + + void setTaskbarViewVisible(boolean isVisible) { + mTaskbarView.setIconsVisibility(isVisible); + mLauncher.getHotseat().setIconsAlpha(isVisible ? 0f : 1f); + } + + /** + * Contains methods that TaskbarAnimationController can call to interface with + * TaskbarController. + */ + protected interface TaskbarAnimationControllerCallbacks { + void updateTaskbarBackgroundAlpha(float alpha); + void updateTaskbarVisibilityAlpha(float alpha); + void updateImeBarVisibilityAlpha(float alpha); + void updateTaskbarScale(float scale); + void updateTaskbarTranslationY(float translationY); + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index 3af51d575d..70f278874a 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -15,56 +15,164 @@ */ package com.android.launcher3.taskbar; -import android.content.ContextWrapper; +import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + +import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT; +import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR; + +import android.app.ActivityOptions; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.content.pm.LauncherApps; +import android.graphics.PixelFormat; import android.graphics.Point; import android.graphics.Rect; import android.graphics.drawable.Drawable; +import android.os.Process; +import android.os.SystemProperties; +import android.util.Log; +import android.view.ContextThemeWrapper; +import android.view.Display; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; +import android.view.WindowManager; +import android.widget.Toast; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.android.launcher3.BaseQuickstepLauncher; +import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.DeviceProfile; import com.android.launcher3.DragSource; import com.android.launcher3.DropTarget; +import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.R; import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.dragndrop.DragView; import com.android.launcher3.dragndrop.DraggableView; +import com.android.launcher3.folder.Folder; +import com.android.launcher3.folder.FolderIcon; +import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; +import com.android.launcher3.model.data.WorkspaceItemInfo; +import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton; +import com.android.launcher3.touch.ItemClickHandler; +import com.android.launcher3.util.PackageManagerHelper; +import com.android.launcher3.util.Themes; +import com.android.launcher3.util.TraceHelper; import com.android.launcher3.views.ActivityContext; -import com.android.launcher3.views.BaseDragLayer; +import com.android.quickstep.SysUINavigationMode; +import com.android.quickstep.SysUINavigationMode.Mode; +import com.android.systemui.shared.recents.model.Task; +import com.android.systemui.shared.system.ActivityManagerWrapper; +import com.android.systemui.shared.system.WindowManagerWrapper; /** * The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements * that are used by both Launcher and Taskbar (such as Folder) to reference a generic * ActivityContext and BaseDragLayer instead of the Launcher activity and its DragLayer. */ -public class TaskbarActivityContext extends ContextWrapper implements ActivityContext { +public class TaskbarActivityContext extends ContextThemeWrapper implements ActivityContext { + + private static final boolean ENABLE_THREE_BUTTON_TASKBAR = + SystemProperties.getBoolean("persist.debug.taskbar_three_button", false); + private static final String TAG = "TaskbarActivityContext"; + + private static final String WINDOW_TITLE = "Taskbar"; private final DeviceProfile mDeviceProfile; private final LayoutInflater mLayoutInflater; private final TaskbarContainerView mTaskbarContainerView; + private final TaskbarIconController mIconController; private final MyDragController mDragController; - public TaskbarActivityContext(BaseQuickstepLauncher launcher) { - super(launcher); - mDeviceProfile = launcher.getDeviceProfile().copy(this); + private final WindowManager mWindowManager; + private WindowManager.LayoutParams mWindowLayoutParams; + + private final SysUINavigationMode.Mode mNavMode; + private final TaskbarNavButtonController mNavButtonController; + + private final boolean mIsSafeModeEnabled; + + @NonNull + private TaskbarUIController mUIController = TaskbarUIController.DEFAULT; + + private final View.OnClickListener mOnTaskbarIconClickListener; + private final View.OnLongClickListener mOnTaskbarIconLongClickListener; + + public TaskbarActivityContext(Context windowContext, DeviceProfile dp, + TaskbarNavButtonController buttonController) { + super(windowContext, Themes.getActivityThemeRes(windowContext)); + mDeviceProfile = dp; + mNavButtonController = buttonController; + mNavMode = SysUINavigationMode.getMode(windowContext); + mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode", + () -> getPackageManager().isSafeMode()); + + mOnTaskbarIconLongClickListener = + new TaskbarDragController(this)::startSystemDragOnLongClick; + mOnTaskbarIconClickListener = this::onTaskbarIconClicked; + float taskbarIconSize = getResources().getDimension(R.dimen.taskbar_icon_size); float iconScale = taskbarIconSize / mDeviceProfile.iconSizePx; mDeviceProfile.updateIconSize(iconScale, getResources()); mLayoutInflater = LayoutInflater.from(this).cloneInContext(this); - mTaskbarContainerView = (TaskbarContainerView) mLayoutInflater .inflate(R.layout.taskbar, null, false); + mIconController = new TaskbarIconController(this, mTaskbarContainerView); mDragController = new MyDragController(this); + + Display display = windowContext.getDisplay(); + Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY + ? windowContext.getApplicationContext() + : windowContext.getApplicationContext().createDisplayContext(display); + mWindowManager = c.getSystemService(WindowManager.class); } - public TaskbarContainerView getTaskbarContainerView() { - return mTaskbarContainerView; + public void init() { + mWindowLayoutParams = new WindowManager.LayoutParams( + MATCH_PARENT, + mDeviceProfile.taskbarSize, + TYPE_APPLICATION_OVERLAY, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + mWindowLayoutParams.setTitle(WINDOW_TITLE); + mWindowLayoutParams.packageName = getPackageName(); + mWindowLayoutParams.gravity = Gravity.BOTTOM; + mWindowLayoutParams.setFitInsetsTypes(0); + mWindowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; + mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; + mWindowLayoutParams.setSystemApplicationOverlay(true); + + WindowManagerWrapper wmWrapper = WindowManagerWrapper.getInstance(); + wmWrapper.setProvidesInsetsTypes( + mWindowLayoutParams, + new int[] { ITYPE_EXTRA_NAVIGATION_BAR, ITYPE_BOTTOM_TAPPABLE_ELEMENT } + ); + + mIconController.init(mOnTaskbarIconClickListener, mOnTaskbarIconLongClickListener); + mWindowManager.addView(mTaskbarContainerView, mWindowLayoutParams); + } + + /** + * Updates the TaskbarContainer height (pass deviceProfile.taskbarSize to reset). + */ + public void setTaskbarWindowHeight(int height) { + if (mWindowLayoutParams.height == height) { + return; + } + mWindowLayoutParams.height = height; + mWindowManager.updateViewLayout(mTaskbarContainerView, mWindowLayoutParams); + } + + public boolean canShowNavButtons() { + return ENABLE_THREE_BUTTON_TASKBAR && mNavMode == Mode.THREE_BUTTONS; } @Override @@ -73,7 +181,7 @@ public class TaskbarActivityContext extends ContextWrapper implements ActivityCo } @Override - public BaseDragLayer getDragLayer() { + public TaskbarContainerView getDragLayer() { return mTaskbarContainerView; } @@ -92,6 +200,103 @@ public class TaskbarActivityContext extends ContextWrapper implements ActivityCo return mDragController; } + /** + * Sets a new data-source for this taskbar instance + */ + public void setUIController(@NonNull TaskbarUIController uiController) { + mUIController.onDestroy(); + mUIController = uiController; + mIconController.setUIController(mUIController); + mUIController.onCreate(); + } + + /** + * Called when this instance of taskbar is no longer needed + */ + public void onDestroy() { + setUIController(TaskbarUIController.DEFAULT); + mIconController.onDestroy(); + mWindowManager.removeViewImmediate(mTaskbarContainerView); + } + + void onNavigationButtonClick(@TaskbarButton int buttonType) { + mNavButtonController.onButtonClick(buttonType); + } + + public TaskbarIconController getIconController() { + return mIconController; + } + + /** + * Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size. + */ + protected void setTaskbarWindowFullscreen(boolean fullscreen) { + setTaskbarWindowHeight(fullscreen ? MATCH_PARENT : getDeviceProfile().taskbarSize); + } + + protected void onTaskbarIconClicked(View view) { + Object tag = view.getTag(); + if (tag instanceof Task) { + Task task = (Task) tag; + ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key, + ActivityOptions.makeBasic()); + } else if (tag instanceof FolderInfo) { + FolderIcon folderIcon = (FolderIcon) view; + Folder folder = folderIcon.getFolder(); + setTaskbarWindowFullscreen(true); + + getDragLayer().post(() -> { + folder.animateOpen(); + + folder.iterateOverItems((itemInfo, itemView) -> { + itemView.setOnClickListener(mOnTaskbarIconClickListener); + itemView.setOnLongClickListener(mOnTaskbarIconLongClickListener); + // To play haptic when dragging, like other Taskbar items do. + itemView.setHapticFeedbackEnabled(true); + return false; + }); + }); + } else if (tag instanceof WorkspaceItemInfo) { + WorkspaceItemInfo info = (WorkspaceItemInfo) tag; + if (info.isDisabled()) { + ItemClickHandler.handleDisabledItemClicked(info, this); + } else { + Intent intent = new Intent(info.getIntent()) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + try { + if (mIsSafeModeEnabled && !PackageManagerHelper.isSystemApp(this, intent)) { + Toast.makeText(this, R.string.safemode_shortcut_error, + Toast.LENGTH_SHORT).show(); + } else if (info.isPromise()) { + intent = new PackageManagerHelper(this) + .getMarketIntent(info.getTargetPackage()) + .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + + } else if (info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) { + String id = info.getDeepShortcutId(); + String packageName = intent.getPackage(); + getSystemService(LauncherApps.class) + .startShortcut(packageName, id, null, null, info.user); + } else if (info.user.equals(Process.myUserHandle())) { + startActivity(intent); + } else { + getSystemService(LauncherApps.class).startMainActivity( + intent.getComponent(), info.user, intent.getSourceBounds(), null); + } + } catch (NullPointerException | ActivityNotFoundException | SecurityException e) { + Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT) + .show(); + Log.e(TAG, "Unable to launch. tag=" + info + " intent=" + intent, e); + } + } + } else { + Log.e(TAG, "Unknown type clicked: " + tag); + } + + AbstractFloatingView.closeAllOpenViews(this); + } + private static class MyDragController extends DragController { MyDragController(TaskbarActivityContext activity) { super(activity); @@ -106,7 +311,8 @@ public class TaskbarActivityContext extends ContextWrapper implements ActivityCo } @Override - protected void exitDrag() { } + protected void exitDrag() { + } @Override protected DropTarget getDefaultDropTarget(int[] dropCoordinates) { diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java index 29f6935f8a..e20ddf88ce 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarAnimationController.java @@ -21,6 +21,7 @@ import android.animation.Animator; import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.Utilities; +import com.android.launcher3.taskbar.LauncherTaskbarUIController.TaskbarAnimationControllerCallbacks; import com.android.quickstep.AnimatedFloat; import com.android.quickstep.SystemUiProxy; import com.android.systemui.shared.system.QuickStepContract; @@ -34,7 +35,7 @@ public class TaskbarAnimationController { private static final long IME_VISIBILITY_ALPHA_DURATION = 120; private final BaseQuickstepLauncher mLauncher; - private final TaskbarController.TaskbarAnimationControllerCallbacks mTaskbarCallbacks; + private final TaskbarAnimationControllerCallbacks mTaskbarCallbacks; // Background alpha. private final AnimatedFloat mTaskbarBackgroundAlpha = new AnimatedFloat( @@ -55,7 +56,7 @@ public class TaskbarAnimationController { this::updateTranslationY); public TaskbarAnimationController(BaseQuickstepLauncher launcher, - TaskbarController.TaskbarAnimationControllerCallbacks taskbarCallbacks) { + TaskbarAnimationControllerCallbacks taskbarCallbacks) { mLauncher = launcher; mTaskbarCallbacks = taskbarCallbacks; } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java index 621bba7e85..5034791f0e 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarContainerView.java @@ -15,9 +15,6 @@ */ package com.android.launcher3.taskbar; -import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_FRAME; -import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION; - import android.content.Context; import android.graphics.Canvas; import android.graphics.Paint; @@ -32,22 +29,21 @@ import com.android.launcher3.R; import com.android.launcher3.util.TouchController; import com.android.launcher3.views.BaseDragLayer; import com.android.systemui.shared.system.ViewTreeObserverWrapper; +import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo; +import com.android.systemui.shared.system.ViewTreeObserverWrapper.OnComputeInsetsListener; /** * Top-level ViewGroup that hosts the TaskbarView as well as Views created by it such as Folder. */ public class TaskbarContainerView extends BaseDragLayer { - private final int[] mTempLoc = new int[2]; private final int mFolderMargin; private final Paint mTaskbarBackgroundPaint; - // Initialized in TaskbarController constructor. - private TaskbarController.TaskbarContainerViewCallbacks mControllerCallbacks; - - // Initialized in init. + private TaskbarIconController.Callbacks mControllerCallbacks; private TaskbarView mTaskbarView; - private ViewTreeObserverWrapper.OnComputeInsetsListener mTaskbarInsetsComputer; + + private final OnComputeInsetsListener mTaskbarInsetsComputer = this::onComputeTaskbarInsets; public TaskbarContainerView(@NonNull Context context) { this(context, null); @@ -68,15 +64,6 @@ public class TaskbarContainerView extends BaseDragLayer mFolderMargin = getResources().getDimensionPixelSize(R.dimen.taskbar_folder_margin); mTaskbarBackgroundPaint = new Paint(); mTaskbarBackgroundPaint.setColor(getResources().getColor(R.color.taskbar_background)); - } - - protected void construct(TaskbarController.TaskbarContainerViewCallbacks callbacks) { - mControllerCallbacks = callbacks; - } - - protected void init(TaskbarView taskbarView) { - mTaskbarView = taskbarView; - mTaskbarInsetsComputer = createTaskbarInsetsComputer(); recreateControllers(); } @@ -85,46 +72,24 @@ public class TaskbarContainerView extends BaseDragLayer mControllers = new TouchController[0]; } - private ViewTreeObserverWrapper.OnComputeInsetsListener createTaskbarInsetsComputer() { - return insetsInfo -> { - if (mControllerCallbacks.isTaskbarTouchable()) { - // Accept touches anywhere in our bounds. - insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME); - } else { - // Let touches pass through us. - insetsInfo.touchableRegion.setEmpty(); - insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION); - } - - // TaskbarContainerView provides insets to other apps based on contentInsets. These - // insets should stay consistent even if we expand TaskbarContainerView's bounds, e.g. - // to show a floating view like Folder. Thus, we set the contentInsets to be where - // mTaskbarView is, since its position never changes and insets rather than overlays. - int[] loc = mTempLoc; - float scale = mTaskbarView.getScaleX(); - float translationY = mTaskbarView.getTranslationY(); - mTaskbarView.setScaleX(1); - mTaskbarView.setScaleY(1); - mTaskbarView.setTranslationY(0); - mTaskbarView.getLocationInWindow(loc); - mTaskbarView.setScaleX(scale); - mTaskbarView.setScaleY(scale); - mTaskbarView.setTranslationY(translationY); - insetsInfo.contentInsets.left = loc[0]; - insetsInfo.contentInsets.top = loc[1]; - insetsInfo.contentInsets.right = getWidth() - (loc[0] + mTaskbarView.getWidth()); - insetsInfo.contentInsets.bottom = getHeight() - (loc[1] + mTaskbarView.getHeight()); - }; + public void init(TaskbarIconController.Callbacks callbacks, TaskbarView taskbarView) { + mControllerCallbacks = callbacks; + mTaskbarView = taskbarView; } - protected void cleanup() { + private void onComputeTaskbarInsets(InsetsInfo insetsInfo) { + if (mControllerCallbacks != null) { + mControllerCallbacks.updateInsetsTouchability(insetsInfo); + } + } + + protected void onDestroy() { ViewTreeObserverWrapper.removeOnComputeInsetsListener(mTaskbarInsetsComputer); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); - ViewTreeObserverWrapper.addOnComputeInsetsListener(getViewTreeObserver(), mTaskbarInsetsComputer); } @@ -133,7 +98,7 @@ public class TaskbarContainerView extends BaseDragLayer protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - cleanup(); + onDestroy(); } @Override @@ -143,10 +108,25 @@ public class TaskbarContainerView extends BaseDragLayer return true; } + public void updateImeBarVisibilityAlpha(float alpha) { + if (mControllerCallbacks != null) { + mControllerCallbacks.updateImeBarVisibilityAlpha(alpha); + } + } + @Override public void onViewRemoved(View child) { super.onViewRemoved(child); - mControllerCallbacks.onViewRemoved(); + if (mControllerCallbacks != null) { + mControllerCallbacks.onContainerViewRemoved(); + } + } + + @Override + protected void dispatchDraw(Canvas canvas) { + canvas.drawRect(0, canvas.getHeight() - mTaskbarView.getHeight(), canvas.getWidth(), + canvas.getHeight(), mTaskbarBackgroundPaint); + super.dispatchDraw(canvas); } /** @@ -158,16 +138,6 @@ public class TaskbarContainerView extends BaseDragLayer return boundingBox; } - protected TaskbarActivityContext getTaskbarActivityContext() { - return mActivity; - } - - @Override - protected void dispatchDraw(Canvas canvas) { - canvas.drawRect(0, canvas.getHeight() - mTaskbarView.getHeight(), canvas.getWidth(), - canvas.getHeight(), mTaskbarBackgroundPaint); - super.dispatchDraw(canvas); - } /** * Sets the alpha of the background color behind all the Taskbar contents. diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java deleted file mode 100644 index 6084e10d85..0000000000 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarController.java +++ /dev/null @@ -1,596 +0,0 @@ -/* - * Copyright (C) 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.android.launcher3.taskbar; - -import static android.view.View.GONE; -import static android.view.View.INVISIBLE; -import static android.view.View.VISIBLE; -import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; -import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; - -import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT; -import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.app.ActivityOptions; -import android.graphics.PixelFormat; -import android.graphics.Point; -import android.graphics.Rect; -import android.inputmethodservice.InputMethodService; -import android.view.Gravity; -import android.view.MotionEvent; -import android.view.View; -import android.view.WindowManager; - -import androidx.annotation.Nullable; - -import com.android.launcher3.AbstractFloatingView; -import com.android.launcher3.BaseQuickstepLauncher; -import com.android.launcher3.DeviceProfile; -import com.android.launcher3.LauncherState; -import com.android.launcher3.QuickstepTransitionManager; -import com.android.launcher3.R; -import com.android.launcher3.anim.AlphaUpdateListener; -import com.android.launcher3.anim.PendingAnimation; -import com.android.launcher3.folder.Folder; -import com.android.launcher3.folder.FolderIcon; -import com.android.launcher3.model.data.FolderInfo; -import com.android.launcher3.model.data.ItemInfo; -import com.android.launcher3.states.StateAnimationConfig; -import com.android.launcher3.taskbar.TaskbarNavButtonController.TaskbarButton; -import com.android.launcher3.touch.ItemClickHandler; -import com.android.launcher3.views.ActivityContext; -import com.android.quickstep.AnimatedFloat; -import com.android.quickstep.SysUINavigationMode; -import com.android.quickstep.TouchInteractionService.TaskbarOverviewProxyDelegate; -import com.android.systemui.shared.recents.model.Task; -import com.android.systemui.shared.system.ActivityManagerWrapper; -import com.android.systemui.shared.system.WindowManagerWrapper; - -/** - * Interfaces with Launcher/WindowManager/SystemUI to determine what to show in TaskbarView. - */ -public class TaskbarController implements TaskbarOverviewProxyDelegate { - - private static final String WINDOW_TITLE = "Taskbar"; - - private final TaskbarContainerView mTaskbarContainerView; - private final TaskbarView mTaskbarViewInApp; - private final TaskbarView mTaskbarViewOnHome; - private final ImeBarView mImeBarView; - - private final BaseQuickstepLauncher mLauncher; - private final WindowManager mWindowManager; - // Layout width and height of the Taskbar in the default state. - private final Point mTaskbarSize; - private final TaskbarStateHandler mTaskbarStateHandler; - private final TaskbarAnimationController mTaskbarAnimationController; - private final TaskbarHotseatController mHotseatController; - private final TaskbarDragController mDragController; - private final TaskbarNavButtonController mNavButtonController; - - // Initialized in init(). - private WindowManager.LayoutParams mWindowLayoutParams; - private SysUINavigationMode.Mode mNavMode = SysUINavigationMode.Mode.NO_BUTTON; - private final SysUINavigationMode.NavigationModeChangeListener mNavigationModeChangeListener = - this::onNavModeChanged; - - private @Nullable Animator mAnimator; - private boolean mIsAnimatingToLauncher; - - public TaskbarController(BaseQuickstepLauncher launcher, - TaskbarContainerView taskbarContainerView, TaskbarView taskbarViewOnHome) { - mLauncher = launcher; - mTaskbarContainerView = taskbarContainerView; - mTaskbarContainerView.construct(createTaskbarContainerViewCallbacks()); - ButtonProvider buttonProvider = new ButtonProvider(launcher); - mTaskbarViewInApp = mTaskbarContainerView.findViewById(R.id.taskbar_view); - mTaskbarViewInApp.construct(createTaskbarViewCallbacks(), buttonProvider); - mTaskbarViewOnHome = taskbarViewOnHome; - mTaskbarViewOnHome.construct(createTaskbarViewCallbacks(), buttonProvider); - mImeBarView = mTaskbarContainerView.findViewById(R.id.ime_bar_view); - mImeBarView.construct(buttonProvider); - mNavButtonController = new TaskbarNavButtonController(launcher); - mWindowManager = mLauncher.getWindowManager(); - mTaskbarSize = new Point(MATCH_PARENT, mLauncher.getDeviceProfile().taskbarSize); - mTaskbarStateHandler = mLauncher.getTaskbarStateHandler(); - mTaskbarAnimationController = new TaskbarAnimationController(mLauncher, - createTaskbarAnimationControllerCallbacks()); - mHotseatController = new TaskbarHotseatController(mLauncher, - createTaskbarHotseatControllerCallbacks()); - mDragController = new TaskbarDragController(mLauncher); - } - - private TaskbarAnimationControllerCallbacks createTaskbarAnimationControllerCallbacks() { - return new TaskbarAnimationControllerCallbacks() { - @Override - public void updateTaskbarBackgroundAlpha(float alpha) { - mTaskbarContainerView.setTaskbarBackgroundAlpha(alpha); - } - - @Override - public void updateTaskbarVisibilityAlpha(float alpha) { - mTaskbarViewInApp.setAlpha(alpha); - mTaskbarViewOnHome.setAlpha(alpha); - } - - @Override - public void updateImeBarVisibilityAlpha(float alpha) { - if (mNavMode != SysUINavigationMode.Mode.THREE_BUTTONS) { - // TODO Remove sysui IME bar for gesture nav as well - return; - } - mImeBarView.setAlpha(alpha); - mImeBarView.setVisibility(alpha == 0 ? GONE : VISIBLE); - } - - @Override - public void updateTaskbarScale(float scale) { - mTaskbarViewInApp.setScaleX(scale); - mTaskbarViewInApp.setScaleY(scale); - } - - @Override - public void updateTaskbarTranslationY(float translationY) { - if (translationY < 0) { - // Resize to accommodate the max translation we'll reach. - setTaskbarWindowHeight(mTaskbarSize.y - + mLauncher.getHotseat().getTaskbarOffsetY()); - } else { - setTaskbarWindowHeight(mTaskbarSize.y); - } - mTaskbarViewInApp.setTranslationY(translationY); - } - }; - } - - private TaskbarContainerViewCallbacks createTaskbarContainerViewCallbacks() { - return new TaskbarContainerViewCallbacks() { - @Override - public void onViewRemoved() { - // Ensure no other children present (like Folders, etc) - for (int i = 0; i < mTaskbarContainerView.getChildCount(); i++) { - View v = mTaskbarContainerView.getChildAt(i); - if (!((v instanceof TaskbarView) || (v instanceof ImeBarView))){ - return; - } - } - setTaskbarWindowFullscreen(false); - } - - @Override - public boolean isTaskbarTouchable() { - return mTaskbarContainerView.getAlpha() > AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD - && (mTaskbarViewInApp.getVisibility() == VISIBLE - || mImeBarView.getVisibility() == VISIBLE) - && !mIsAnimatingToLauncher; - } - }; - } - - private TaskbarViewCallbacks createTaskbarViewCallbacks() { - return new TaskbarViewCallbacks() { - @Override - public View.OnClickListener getItemOnClickListener() { - return view -> { - Object tag = view.getTag(); - if (tag instanceof Task) { - Task task = (Task) tag; - ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key, - ActivityOptions.makeBasic()); - } else if (tag instanceof FolderInfo) { - FolderIcon folderIcon = (FolderIcon) view; - Folder folder = folderIcon.getFolder(); - - setTaskbarWindowFullscreen(true); - - mTaskbarContainerView.post(() -> { - folder.animateOpen(); - - folder.iterateOverItems((itemInfo, itemView) -> { - itemView.setOnClickListener(getItemOnClickListener()); - itemView.setOnLongClickListener(getItemOnLongClickListener()); - // To play haptic when dragging, like other Taskbar items do. - itemView.setHapticFeedbackEnabled(true); - return false; - }); - }); - } else { - ItemClickHandler.INSTANCE.onClick(view); - } - - AbstractFloatingView.closeAllOpenViews( - mTaskbarContainerView.getTaskbarActivityContext()); - }; - } - - @Override - public View.OnLongClickListener getItemOnLongClickListener() { - return mDragController::startSystemDragOnLongClick; - } - - @Override - public int getEmptyHotseatViewVisibility(TaskbarView taskbarView) { - // When on the home screen, we want the empty hotseat views to take up their full - // space so that the others line up with the home screen hotseat. - boolean isOnHomeScreen = taskbarView == mTaskbarViewOnHome - || mLauncher.hasBeenResumed() || mIsAnimatingToLauncher; - return isOnHomeScreen ? INVISIBLE : GONE; - } - - @Override - public float getNonIconScale(TaskbarView taskbarView) { - return taskbarView == mTaskbarViewOnHome ? getTaskbarScaleOnHome() : 1f; - } - - @Override - public void onItemPositionsChanged(TaskbarView taskbarView) { - if (taskbarView == mTaskbarViewOnHome) { - alignRealHotseatWithTaskbar(); - } - } - - @Override - public void onNavigationButtonClick(@TaskbarButton int buttonType) { - mNavButtonController.onButtonClick(buttonType); - } - }; - } - - private TaskbarHotseatControllerCallbacks createTaskbarHotseatControllerCallbacks() { - return new TaskbarHotseatControllerCallbacks() { - @Override - public void updateHotseatItems(ItemInfo[] hotseatItemInfos) { - mTaskbarViewInApp.updateHotseatItems(hotseatItemInfos); - } - }; - } - - /** - * Initializes the Taskbar, including adding it to the screen. - */ - public void init() { - mNavMode = SysUINavigationMode.INSTANCE.get(mLauncher) - .addModeChangeListener(mNavigationModeChangeListener); - mTaskbarViewInApp.init(mHotseatController.getNumHotseatIcons(), mNavMode); - mTaskbarViewOnHome.init(mHotseatController.getNumHotseatIcons(), mNavMode); - mTaskbarContainerView.init(mTaskbarViewInApp); - mImeBarView.init(createTaskbarViewCallbacks()); - addToWindowManager(); - mTaskbarStateHandler.setTaskbarCallbacks(createTaskbarStateHandlerCallbacks()); - mTaskbarAnimationController.init(); - mHotseatController.init(); - - setWhichTaskbarViewIsVisible(mLauncher.hasBeenResumed() - ? mTaskbarViewOnHome - : mTaskbarViewInApp); - } - - private TaskbarStateHandlerCallbacks createTaskbarStateHandlerCallbacks() { - return new TaskbarStateHandlerCallbacks() { - @Override - public AnimatedFloat getAlphaTarget() { - return mTaskbarAnimationController.getTaskbarVisibilityForLauncherState(); - } - - @Override - public AnimatedFloat getScaleTarget() { - return mTaskbarAnimationController.getTaskbarScaleForLauncherState(); - } - - @Override - public AnimatedFloat getTranslationYTarget() { - return mTaskbarAnimationController.getTaskbarTranslationYForLauncherState(); - } - }; - } - - /** - * Removes the Taskbar from the screen, and removes any obsolete listeners etc. - */ - public void cleanup() { - if (mAnimator != null) { - // End this first, in case it relies on properties that are about to be cleaned up. - mAnimator.end(); - } - - mTaskbarViewInApp.cleanup(); - mTaskbarViewOnHome.cleanup(); - mTaskbarContainerView.cleanup(); - mImeBarView.cleanup(); - removeFromWindowManager(); - mTaskbarStateHandler.setTaskbarCallbacks(null); - mTaskbarAnimationController.cleanup(); - mHotseatController.cleanup(); - - setWhichTaskbarViewIsVisible(null); - SysUINavigationMode.INSTANCE.get(mLauncher) - .removeModeChangeListener(mNavigationModeChangeListener); - } - - private void removeFromWindowManager() { - mWindowManager.removeViewImmediate(mTaskbarContainerView); - } - - private void addToWindowManager() { - final int gravity = Gravity.BOTTOM; - - mWindowLayoutParams = new WindowManager.LayoutParams( - mTaskbarSize.x, - mTaskbarSize.y, - TYPE_APPLICATION_OVERLAY, - WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, - PixelFormat.TRANSLUCENT); - mWindowLayoutParams.setTitle(WINDOW_TITLE); - mWindowLayoutParams.packageName = mLauncher.getPackageName(); - mWindowLayoutParams.gravity = gravity; - mWindowLayoutParams.setFitInsetsTypes(0); - mWindowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; - mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; - mWindowLayoutParams.setSystemApplicationOverlay(true); - - WindowManagerWrapper wmWrapper = WindowManagerWrapper.getInstance(); - wmWrapper.setProvidesInsetsTypes( - mWindowLayoutParams, - new int[] { ITYPE_EXTRA_NAVIGATION_BAR, ITYPE_BOTTOM_TAPPABLE_ELEMENT } - ); - - TaskbarContainerView.LayoutParams taskbarLayoutParams = - new TaskbarContainerView.LayoutParams(mTaskbarSize.x, mTaskbarSize.y); - taskbarLayoutParams.gravity = gravity; - mTaskbarViewInApp.setLayoutParams(taskbarLayoutParams); - - mWindowManager.addView(mTaskbarContainerView, mWindowLayoutParams); - } - - private void onNavModeChanged(SysUINavigationMode.Mode newMode) { - mNavMode = newMode; - cleanup(); - init(); - } - - /** - * Should be called from onResume() and onPause(), and animates the Taskbar accordingly. - */ - public void onLauncherResumedOrPaused(boolean isResumed) { - long duration = QuickstepTransitionManager.CONTENT_ALPHA_DURATION; - if (mAnimator != null) { - mAnimator.cancel(); - } - if (isResumed) { - mAnimator = createAnimToLauncher(null, duration); - } else { - mAnimator = createAnimToApp(duration); - } - mAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - mAnimator = null; - } - }); - mAnimator.start(); - } - - /** - * Create Taskbar animation when going from an app to Launcher. - * @param toState If known, the state we will end up in when reaching Launcher. - */ - public Animator createAnimToLauncher(@Nullable LauncherState toState, long duration) { - PendingAnimation anim = new PendingAnimation(duration); - anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(0, duration)); - if (toState != null) { - mTaskbarStateHandler.setStateWithAnimation(toState, new StateAnimationConfig(), anim); - } - - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - mIsAnimatingToLauncher = true; - mTaskbarViewInApp.updateHotseatItemsVisibility(); - } - - @Override - public void onAnimationEnd(Animator animation) { - mIsAnimatingToLauncher = false; - setWhichTaskbarViewIsVisible(mTaskbarViewOnHome); - } - }); - - return anim.buildAnim(); - } - - private Animator createAnimToApp(long duration) { - PendingAnimation anim = new PendingAnimation(duration); - anim.add(mTaskbarAnimationController.createAnimToBackgroundAlpha(1, duration)); - anim.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - mTaskbarViewInApp.updateHotseatItemsVisibility(); - setWhichTaskbarViewIsVisible(mTaskbarViewInApp); - } - - @Override - public void onAnimationEnd(Animator animation) { - } - }); - return anim.buildAnim(); - } - - /** - * Should be called when the IME visibility changes, so we can hide/show Taskbar accordingly. - */ - public void setIsImeVisible(boolean isImeVisible) { - mTaskbarAnimationController.animateToVisibilityForIme(isImeVisible ? 0 : 1); - blockTaskbarTouchesForIme(isImeVisible); - } - - /** - * When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from - * instantiating at all, which is what's responsible for sending sysui state flags over. - * - * @param vis IME visibility flag - * @param backDisposition Used to determine back button behavior for software keyboard - * See BACK_DISPOSITION_* constants in {@link InputMethodService} - */ - public void updateImeStatus(int displayId, int vis, int backDisposition, - boolean showImeSwitcher) { - if (displayId != mTaskbarContainerView.getContext().getDisplayId() || - mNavMode != SysUINavigationMode.Mode.THREE_BUTTONS) { - return; - } - - boolean imeVisible = (vis & InputMethodService.IME_VISIBLE) != 0; - mTaskbarAnimationController.animateToVisibilityForIme(imeVisible ? 0 : 1); - mImeBarView.setImeSwitcherVisibility(showImeSwitcher); - blockTaskbarTouchesForIme(imeVisible); - } - - /** - * Should be called when one or more items in the Hotseat have changed. - */ - public void onHotseatUpdated() { - mHotseatController.onHotseatUpdated(); - } - - /** - * @param ev MotionEvent in screen coordinates. - * @return Whether any Taskbar item could handle the given MotionEvent if given the chance. - */ - public boolean isEventOverAnyTaskbarItem(MotionEvent ev) { - return mTaskbarViewInApp.isEventOverAnyItem(ev); - } - - public boolean isDraggingItem() { - return mTaskbarViewInApp.isDraggingItem() || mTaskbarViewOnHome.isDraggingItem(); - } - - /** - * @return Whether the given View is in the same window as Taskbar. - */ - public boolean isViewInTaskbar(View v) { - return mTaskbarContainerView.isAttachedToWindow() - && mTaskbarContainerView.getWindowId().equals(v.getWindowId()); - } - - /** - * Pads the Hotseat to line up exactly with Taskbar's copy of the Hotseat. - */ - public void alignRealHotseatWithTaskbar() { - Rect hotseatBounds = new Rect(); - DeviceProfile grid = mLauncher.getDeviceProfile(); - int hotseatHeight = grid.workspacePadding.bottom + grid.taskbarSize; - int taskbarOffset = mLauncher.getHotseat().getTaskbarOffsetY(); - int hotseatTopDiff = hotseatHeight - grid.taskbarSize - taskbarOffset; - int hotseatBottomDiff = taskbarOffset; - - mTaskbarViewOnHome.getHotseatBounds().roundOut(hotseatBounds); - mLauncher.getHotseat().setPadding(hotseatBounds.left, - hotseatBounds.top + hotseatTopDiff, - mTaskbarViewOnHome.getWidth() - hotseatBounds.right, - mTaskbarViewOnHome.getHeight() - hotseatBounds.bottom + hotseatBottomDiff); - } - - private void setWhichTaskbarViewIsVisible(@Nullable TaskbarView visibleTaskbar) { - mTaskbarViewInApp.setVisibility(visibleTaskbar == mTaskbarViewInApp - ? VISIBLE : INVISIBLE); - mTaskbarViewOnHome.setVisibility(visibleTaskbar == mTaskbarViewOnHome - ? VISIBLE : INVISIBLE); - mLauncher.getHotseat().setIconsAlpha(visibleTaskbar != mTaskbarViewInApp ? 1f : 0f); - } - - private void blockTaskbarTouchesForIme(boolean block) { - mTaskbarViewOnHome.setTouchesEnabled(!block); - mTaskbarViewInApp.setTouchesEnabled(!block); - } - - /** - * Returns the ratio of the taskbar icon size on home vs in an app. - */ - public float getTaskbarScaleOnHome() { - DeviceProfile inAppDp = mTaskbarContainerView.getTaskbarActivityContext() - .getDeviceProfile(); - DeviceProfile onHomeDp = ActivityContext.lookupContext(mTaskbarViewOnHome.getContext()) - .getDeviceProfile(); - return (float) onHomeDp.cellWidthPx / inAppDp.cellWidthPx; - } - - /** - * Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size. - */ - private void setTaskbarWindowFullscreen(boolean fullscreen) { - setTaskbarWindowHeight(fullscreen ? MATCH_PARENT : mTaskbarSize.y); - } - - /** - * Updates the TaskbarContainer height (pass mTaskbarSize.y to reset). - */ - private void setTaskbarWindowHeight(int height) { - mWindowLayoutParams.width = mTaskbarSize.x; - mWindowLayoutParams.height = height; - mWindowManager.updateViewLayout(mTaskbarContainerView, mWindowLayoutParams); - } - - /** - * Contains methods that TaskbarStateHandler can call to interface with TaskbarController. - */ - protected interface TaskbarStateHandlerCallbacks { - AnimatedFloat getAlphaTarget(); - AnimatedFloat getScaleTarget(); - AnimatedFloat getTranslationYTarget(); - } - - /** - * Contains methods that TaskbarAnimationController can call to interface with - * TaskbarController. - */ - protected interface TaskbarAnimationControllerCallbacks { - void updateTaskbarBackgroundAlpha(float alpha); - void updateTaskbarVisibilityAlpha(float alpha); - void updateImeBarVisibilityAlpha(float alpha); - void updateTaskbarScale(float scale); - void updateTaskbarTranslationY(float translationY); - } - - /** - * Contains methods that TaskbarContainerView can call to interface with TaskbarController. - */ - protected interface TaskbarContainerViewCallbacks { - void onViewRemoved(); - boolean isTaskbarTouchable(); - } - - /** - * Contains methods that TaskbarView can call to interface with TaskbarController. - */ - protected interface TaskbarViewCallbacks { - View.OnClickListener getItemOnClickListener(); - View.OnLongClickListener getItemOnLongClickListener(); - int getEmptyHotseatViewVisibility(TaskbarView taskbarView); - /** Returns how much to scale non-icon elements such as spacing and dividers. */ - float getNonIconScale(TaskbarView taskbarView); - void onItemPositionsChanged(TaskbarView taskbarView); - void onNavigationButtonClick(@TaskbarButton int buttonType); - } - - /** - * Contains methods that TaskbarHotseatController can call to interface with TaskbarController. - */ - protected interface TaskbarHotseatControllerCallbacks { - void updateHotseatItems(ItemInfo[] hotseatItemInfos); - } -} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java index 5eb34cb360..ee44927ce4 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarDragController.java @@ -20,6 +20,7 @@ import static android.view.View.VISIBLE; import android.content.ClipData; import android.content.ClipDescription; +import android.content.Context; import android.content.Intent; import android.content.pm.LauncherApps; import android.content.res.Resources; @@ -29,7 +30,6 @@ import android.os.UserHandle; import android.view.DragEvent; import android.view.View; -import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.BubbleTextView; import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; @@ -43,12 +43,12 @@ import com.android.systemui.shared.system.LauncherAppsCompat; */ public class TaskbarDragController { - private final BaseQuickstepLauncher mLauncher; + private final Context mContext; private final int mDragIconSize; - public TaskbarDragController(BaseQuickstepLauncher launcher) { - mLauncher = launcher; - Resources resources = mLauncher.getResources(); + public TaskbarDragController(Context context) { + mContext = context; + Resources resources = mContext.getResources(); mDragIconSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_drag_icon_size); } @@ -63,7 +63,6 @@ public class TaskbarDragController { } BubbleTextView btv = (BubbleTextView) view; - View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view) { @Override public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) { @@ -87,7 +86,7 @@ public class TaskbarDragController { Intent intent = null; if (tag instanceof WorkspaceItemInfo) { WorkspaceItemInfo item = (WorkspaceItemInfo) tag; - LauncherApps launcherApps = mLauncher.getSystemService(LauncherApps.class); + LauncherApps launcherApps = mContext.getSystemService(LauncherApps.class); clipDescription = new ClipDescription(item.title, new String[] { item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java index 68829cd0fb..91cf7efab5 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarHotseatController.java @@ -26,6 +26,8 @@ import com.android.launcher3.dragndrop.DragController; import com.android.launcher3.dragndrop.DragOptions; import com.android.launcher3.model.data.ItemInfo; +import java.util.function.Consumer; + /** * Works with TaskbarController to update the TaskbarView's Hotseat items. */ @@ -33,13 +35,12 @@ public class TaskbarHotseatController { private final BaseQuickstepLauncher mLauncher; private final Hotseat mHotseat; - private final TaskbarController.TaskbarHotseatControllerCallbacks mTaskbarCallbacks; + private final Consumer mTaskbarCallbacks; private final int mNumHotseatIcons; private final DragController.DragListener mDragListener = new DragController.DragListener() { @Override - public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { - } + public void onDragStart(DropTarget.DragObject dragObject, DragOptions options) { } @Override public void onDragEnd() { @@ -47,8 +48,8 @@ public class TaskbarHotseatController { } }; - public TaskbarHotseatController(BaseQuickstepLauncher launcher, - TaskbarController.TaskbarHotseatControllerCallbacks taskbarCallbacks) { + public TaskbarHotseatController( + BaseQuickstepLauncher launcher, Consumer taskbarCallbacks) { mLauncher = launcher; mHotseat = mLauncher.getHotseat(); mTaskbarCallbacks = taskbarCallbacks; @@ -85,10 +86,6 @@ public class TaskbarHotseatController { } } - mTaskbarCallbacks.updateHotseatItems(hotseatItemInfos); - } - - protected int getNumHotseatIcons() { - return mNumHotseatIcons; + mTaskbarCallbacks.accept(hotseatItemInfos); } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java new file mode 100644 index 0000000000..2a37915fcf --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarIconController.java @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.taskbar; + +import static android.view.View.GONE; +import static android.view.View.VISIBLE; + +import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_FRAME; +import static com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo.TOUCHABLE_INSETS_REGION; + +import android.graphics.Rect; +import android.inputmethodservice.InputMethodService; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.View.OnLongClickListener; + +import androidx.annotation.NonNull; + +import com.android.launcher3.R; +import com.android.launcher3.anim.AlphaUpdateListener; +import com.android.systemui.shared.system.ViewTreeObserverWrapper.InsetsInfo; + +/** + * Controller for taskbar icon UI + */ +public class TaskbarIconController { + + private final Rect mTempRect = new Rect(); + + private final TaskbarActivityContext mActivity; + private final TaskbarContainerView mContainerView; + + private final TaskbarView mTaskbarView; + private final ImeBarView mImeBarView; + + @NonNull + private TaskbarUIController mUIController = TaskbarUIController.DEFAULT; + + TaskbarIconController(TaskbarActivityContext activity, TaskbarContainerView containerView) { + mActivity = activity; + mContainerView = containerView; + mTaskbarView = mContainerView.findViewById(R.id.taskbar_view); + mImeBarView = mContainerView.findViewById(R.id.ime_bar_view); + } + + public void init(OnClickListener clickListener, OnLongClickListener longClickListener) { + mContainerView.addOnLayoutChangeListener((v, a, b, c, d, e, f, g, h) -> + mUIController.alignRealHotseatWithTaskbar()); + + ButtonProvider buttonProvider = new ButtonProvider(mActivity); + mImeBarView.init(buttonProvider); + mTaskbarView.construct(clickListener, longClickListener, buttonProvider); + mTaskbarView.getLayoutParams().height = mActivity.getDeviceProfile().taskbarSize; + + mContainerView.init(new Callbacks(), mTaskbarView); + } + + public void onDestroy() { + mContainerView.onDestroy(); + } + + public void setUIController(@NonNull TaskbarUIController uiController) { + mUIController = uiController; + } + + /** + * When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from + * instantiating at all, which is what's responsible for sending sysui state flags over. + * + * @param vis IME visibility flag + */ + public void updateImeStatus(int displayId, int vis, boolean showImeSwitcher) { + if (displayId != mActivity.getDisplayId() || !mActivity.canShowNavButtons()) { + return; + } + + mImeBarView.setImeSwitcherVisibility(showImeSwitcher); + setImeIsVisible((vis & InputMethodService.IME_VISIBLE) != 0); + } + + /** + * Should be called when the IME visibility changes, so we can hide/show Taskbar accordingly. + */ + public void setImeIsVisible(boolean isImeVisible) { + mTaskbarView.setTouchesEnabled(!isImeVisible); + mUIController.onImeVisible(mContainerView, isImeVisible); + } + + /** + * Callbacks for {@link TaskbarContainerView} to interact with the icon controller + */ + public class Callbacks { + + /** + * Called to update the touchable insets + */ + public void updateInsetsTouchability(InsetsInfo insetsInfo) { + insetsInfo.touchableRegion.setEmpty(); + if (mContainerView.getAlpha() < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) { + // Let touches pass through us. + insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION); + } else if (mImeBarView.getVisibility() == VISIBLE) { + insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME); + } else if (!mUIController.isTaskbarTouchable()) { + // Let touches pass through us. + insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION); + } else if (mTaskbarView.areIconsVisible()) { + // Buttons are visible, take over the full taskbar area + insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME); + } else { + if (mTaskbarView.mSystemButtonContainer.getVisibility() == VISIBLE) { + mContainerView.getDescendantRectRelativeToSelf( + mTaskbarView.mSystemButtonContainer, mTempRect); + insetsInfo.touchableRegion.set(mTempRect); + } + insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION); + } + + // TaskbarContainerView provides insets to other apps based on contentInsets. These + // insets should stay consistent even if we expand TaskbarContainerView's bounds, e.g. + // to show a floating view like Folder. Thus, we set the contentInsets to be where + // mTaskbarView is, since its position never changes and insets rather than overlays. + insetsInfo.contentInsets.left = mTaskbarView.getLeft(); + insetsInfo.contentInsets.top = mTaskbarView.getTop(); + insetsInfo.contentInsets.right = mContainerView.getWidth() - mTaskbarView.getRight(); + insetsInfo.contentInsets.bottom = mContainerView.getHeight() - mTaskbarView.getBottom(); + } + + public void onContainerViewRemoved() { + int count = mContainerView.getChildCount(); + // Ensure no other children present (like Folders, etc) + for (int i = 0; i < count; i++) { + View v = mContainerView.getChildAt(i); + if (!((v instanceof TaskbarView) || (v instanceof ImeBarView))) { + return; + } + } + mActivity.setTaskbarWindowFullscreen(false); + } + + public void updateImeBarVisibilityAlpha(float alpha) { + if (!mActivity.canShowNavButtons()) { + // TODO Remove sysui IME bar for gesture nav as well + return; + } + mImeBarView.setAlpha(alpha); + mImeBarView.setVisibility(alpha == 0 ? GONE : VISIBLE); + } + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java new file mode 100644 index 0000000000..263aa1142d --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarManager.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.launcher3.taskbar; + +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; + +import static com.android.launcher3.util.DisplayController.CHANGE_ACTIVE_SCREEN; +import static com.android.launcher3.util.DisplayController.CHANGE_DENSITY; +import static com.android.launcher3.util.DisplayController.CHANGE_SUPPORTED_BOUNDS; +import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING; + +import android.content.Context; +import android.hardware.display.DisplayManager; +import android.inputmethodservice.InputMethodService; +import android.view.Display; + +import androidx.annotation.Nullable; + +import com.android.launcher3.BaseQuickstepLauncher; +import com.android.launcher3.DeviceProfile; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.util.DisplayController; +import com.android.launcher3.util.DisplayController.Info; +import com.android.quickstep.SysUINavigationMode; +import com.android.quickstep.SysUINavigationMode.Mode; +import com.android.quickstep.TouchInteractionService; + +/** + * Class to manager taskbar lifecycle + */ +public class TaskbarManager implements DisplayController.DisplayInfoChangeListener, + SysUINavigationMode.NavigationModeChangeListener { + + private final Context mContext; + private final DisplayController mDisplayController; + private final SysUINavigationMode mSysUINavigationMode; + private final TaskbarNavButtonController mNavButtonController; + + private TaskbarActivityContext mTaskbarActivityContext; + private BaseQuickstepLauncher mLauncher; + + private static final int CHANGE_FLAGS = + CHANGE_ACTIVE_SCREEN | CHANGE_DENSITY | CHANGE_SUPPORTED_BOUNDS; + + private boolean mUserUnlocked = false; + + public TaskbarManager(TouchInteractionService service) { + mDisplayController = DisplayController.INSTANCE.get(service); + mSysUINavigationMode = SysUINavigationMode.INSTANCE.get(service); + Display display = + service.getSystemService(DisplayManager.class).getDisplay(DEFAULT_DISPLAY); + mContext = service.createWindowContext(display, TYPE_APPLICATION_OVERLAY, null); + mNavButtonController = new TaskbarNavButtonController(service); + + mDisplayController.addChangeListener(this); + mSysUINavigationMode.addModeChangeListener(this); + recreateTaskbar(); + } + + @Override + public void onNavigationModeChanged(Mode newMode) { + recreateTaskbar(); + } + + @Override + public void onDisplayInfoChanged(Context context, Info info, int flags) { + if ((flags & CHANGE_FLAGS) != 0) { + recreateTaskbar(); + } + } + + private void destroyExistingTaskbar() { + if (mTaskbarActivityContext != null) { + mTaskbarActivityContext.onDestroy(); + mTaskbarActivityContext = null; + } + } + + /** + * Called when the user is unlocked + */ + public void onUserUnlocked() { + mUserUnlocked = true; + recreateTaskbar(); + } + + /** + * Sets or clears a launcher to act as taskbar callback + */ + public void setLauncher(@Nullable BaseQuickstepLauncher launcher) { + mLauncher = launcher; + if (mTaskbarActivityContext != null) { + mTaskbarActivityContext.setUIController(mLauncher == null + ? TaskbarUIController.DEFAULT + : new LauncherTaskbarUIController(launcher, mTaskbarActivityContext)); + } + } + + private void recreateTaskbar() { + destroyExistingTaskbar(); + if (!FeatureFlags.ENABLE_TASKBAR.get()) { + return; + } + if (!mUserUnlocked) { + return; + } + DeviceProfile dp = LauncherAppState.getIDP(mContext).getDeviceProfile(mContext); + if (!dp.isTaskbarPresent) { + return; + } + mTaskbarActivityContext = new TaskbarActivityContext( + mContext, dp.copy(mContext), mNavButtonController); + mTaskbarActivityContext.init(); + if (mLauncher != null) { + mTaskbarActivityContext.setUIController( + new LauncherTaskbarUIController(mLauncher, mTaskbarActivityContext)); + } + } + + /** + * See {@link com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags} + * @param systemUiStateFlags The latest SystemUiStateFlags + */ + public void onSystemUiFlagsChanged(int systemUiStateFlags) { + boolean isImeVisible = (systemUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0; + if (mTaskbarActivityContext != null) { + mTaskbarActivityContext.getIconController().setImeIsVisible(isImeVisible); + } + } + + /** + * When in 3 button nav, the above doesn't get called since we prevent sysui nav bar from + * instantiating at all, which is what's responsible for sending sysui state flags over. + * + * @param vis IME visibility flag + * @param backDisposition Used to determine back button behavior for software keyboard + * See BACK_DISPOSITION_* constants in {@link InputMethodService} + */ + public void updateImeStatus(int displayId, int vis, int backDisposition, + boolean showImeSwitcher) { + if (mTaskbarActivityContext != null) { + mTaskbarActivityContext.getIconController() + .updateImeStatus(displayId, vis, showImeSwitcher); + } + } + + /** + * Called when the manager is no longer needed + */ + public void destroy() { + destroyExistingTaskbar(); + mDisplayController.removeChangeListener(this); + mSysUINavigationMode.removeModeChangeListener(this); + } +} diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java index 54e1610cd1..3b5afad691 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarNavButtonController.java @@ -16,7 +16,8 @@ package com.android.launcher3.taskbar; -import android.content.Context; +import static android.view.Display.DEFAULT_DISPLAY; + import android.content.Intent; import android.view.inputmethod.InputMethodManager; @@ -53,11 +54,10 @@ public class TaskbarNavButtonController { static final int BUTTON_RECENTS = BUTTON_HOME << 1; static final int BUTTON_IME_SWITCH = BUTTON_RECENTS << 1; + private final TouchInteractionService mService; - private final Context mContext; - - public TaskbarNavButtonController(Context context) { - mContext = context; + public TaskbarNavButtonController(TouchInteractionService service) { + mService = service; } public void onButtonClick(@TaskbarButton int buttonType) { @@ -78,13 +78,13 @@ public class TaskbarNavButtonController { } private void navigateHome() { - mContext.startActivity(new Intent(Intent.ACTION_MAIN) + mService.startActivity(new Intent(Intent.ACTION_MAIN) .addCategory(Intent.CATEGORY_HOME) .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); } private void navigateToOverview() { - TouchInteractionService.getInstance().getOverviewCommandHelper() + mService.getOverviewCommandHelper() .addCommand(OverviewCommandHelper.TYPE_SHOW); } @@ -93,8 +93,8 @@ public class TaskbarNavButtonController { } private void showIMESwitcher() { - mContext.getSystemService(InputMethodManager.class).showInputMethodPickerFromSystem( - true /* showAuxiliarySubtypes */, mContext.getDisplayId()); + mService.getSystemService(InputMethodManager.class) + .showInputMethodPickerFromSystem(true /* showAuxiliarySubtypes */, + DEFAULT_DISPLAY); } - } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java index 6ea51fa5eb..a701aae088 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarStateHandler.java @@ -24,59 +24,52 @@ import androidx.annotation.Nullable; import com.android.launcher3.BaseQuickstepLauncher; import com.android.launcher3.LauncherState; import com.android.launcher3.anim.PendingAnimation; +import com.android.launcher3.anim.PropertySetter; import com.android.launcher3.statemanager.StateManager; import com.android.launcher3.states.StateAnimationConfig; import com.android.quickstep.AnimatedFloat; /** * StateHandler to animate Taskbar according to Launcher's state machine. Does nothing if Taskbar - * isn't present (i.e. {@link #setTaskbarCallbacks} is never called). + * isn't present (i.e. {@link #setAnimationController} is never called). */ public class TaskbarStateHandler implements StateManager.StateHandler { private final BaseQuickstepLauncher mLauncher; // Contains Taskbar-related methods and fields we should aniamte. If null, don't do anything. - private @Nullable TaskbarController.TaskbarStateHandlerCallbacks mTaskbarCallbacks = null; + private @Nullable TaskbarAnimationController mAnimationController = null; public TaskbarStateHandler(BaseQuickstepLauncher launcher) { mLauncher = launcher; } - public void setTaskbarCallbacks(TaskbarController.TaskbarStateHandlerCallbacks callbacks) { - mTaskbarCallbacks = callbacks; + public void setAnimationController(TaskbarAnimationController callbacks) { + mAnimationController = callbacks; } @Override public void setState(LauncherState state) { - if (mTaskbarCallbacks == null) { - return; - } - - AnimatedFloat alphaTarget = mTaskbarCallbacks.getAlphaTarget(); - AnimatedFloat scaleTarget = mTaskbarCallbacks.getScaleTarget(); - AnimatedFloat translationYTarget = mTaskbarCallbacks.getTranslationYTarget(); - boolean isTaskbarVisible = (state.getVisibleElements(mLauncher) & TASKBAR) != 0; - alphaTarget.updateValue(isTaskbarVisible ? 1f : 0f); - scaleTarget.updateValue(state.getTaskbarScale(mLauncher)); - translationYTarget.updateValue(state.getTaskbarTranslationY(mLauncher)); + setState(state, PropertySetter.NO_ANIM_PROPERTY_SETTER); } @Override public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config, PendingAnimation animation) { - if (mTaskbarCallbacks == null) { + setState(toState, animation); + } + + private void setState(LauncherState toState, PropertySetter setter) { + if (mAnimationController == null) { return; } - AnimatedFloat alphaTarget = mTaskbarCallbacks.getAlphaTarget(); - AnimatedFloat scaleTarget = mTaskbarCallbacks.getScaleTarget(); - AnimatedFloat translationYTarget = mTaskbarCallbacks.getTranslationYTarget(); boolean isTaskbarVisible = (toState.getVisibleElements(mLauncher) & TASKBAR) != 0; - animation.setFloat(alphaTarget, AnimatedFloat.VALUE, isTaskbarVisible ? 1f : 0f, LINEAR); - animation.setFloat(scaleTarget, AnimatedFloat.VALUE, toState.getTaskbarScale(mLauncher), - LINEAR); - animation.setFloat(translationYTarget, AnimatedFloat.VALUE, - toState.getTaskbarTranslationY(mLauncher), ACCEL_DEACCEL); + setter.setFloat(mAnimationController.getTaskbarVisibilityForLauncherState(), + AnimatedFloat.VALUE, isTaskbarVisible ? 1f : 0f, LINEAR); + setter.setFloat(mAnimationController.getTaskbarScaleForLauncherState(), + AnimatedFloat.VALUE, toState.getTaskbarScale(mLauncher), LINEAR); + setter.setFloat(mAnimationController.getTaskbarTranslationYForLauncherState(), + AnimatedFloat.VALUE, toState.getTaskbarTranslationY(mLauncher), ACCEL_DEACCEL); } } diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarUIHelper.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java similarity index 50% rename from src/com/android/launcher3/widget/picker/search/WidgetsSearchBarUIHelper.java rename to quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java index edfdc655ed..e16f5e65b0 100644 --- a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarUIHelper.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarUIController.java @@ -13,15 +13,29 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -package com.android.launcher3.widget.picker.search; +package com.android.launcher3.taskbar; /** - * UI helper for {@link WidgetsSearchBar}. + * Base class for providing different taskbar UI */ -public interface WidgetsSearchBarUIHelper { +public class TaskbarUIController { + + public static final TaskbarUIController DEFAULT = new TaskbarUIController(); + /** - * Clears focus from the search bar. + * Pads the Hotseat to line up exactly with Taskbar's copy of the Hotseat. */ - void clearSearchBarFocus(); + public void alignRealHotseatWithTaskbar() { } + + protected void onCreate() { } + + protected void onDestroy() { } + + protected boolean isTaskbarTouchable() { + return true; + } + + protected void onImeVisible(TaskbarContainerView container, boolean isVisible) { + container.updateImeBarVisibilityAlpha(isVisible ? 1 : 0); + } } diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java index 9e8013e63e..c6573a639c 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarView.java @@ -15,20 +15,14 @@ */ package com.android.launcher3.taskbar; -import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_BACK; -import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_HOME; -import static com.android.launcher3.taskbar.TaskbarNavButtonController.BUTTON_RECENTS; +import static android.view.View.MeasureSpec.EXACTLY; +import static android.view.View.MeasureSpec.makeMeasureSpec; -import android.animation.Animator; -import android.animation.AnimatorSet; -import android.animation.LayoutTransition; -import android.animation.ValueAnimator; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.RectF; -import android.os.SystemProperties; import android.util.AttributeSet; import android.view.DragEvent; import android.view.Gravity; @@ -51,17 +45,12 @@ import com.android.launcher3.model.data.FolderInfo; import com.android.launcher3.model.data.ItemInfo; import com.android.launcher3.model.data.WorkspaceItemInfo; import com.android.launcher3.views.ActivityContext; -import com.android.quickstep.SysUINavigationMode; /** * Hosts the Taskbar content such as Hotseat and Recent Apps. Drawn on top of other apps. */ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconParent, Insettable { - - private static final boolean ENABLE_THREE_BUTTON_TASKBAR = - SystemProperties.getBoolean("persist.debug.taskbar_three_button", false); - private final int mIconTouchSize; private final boolean mIsRtl; private final int mTouchSlop; @@ -69,17 +58,16 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa private final RectF mDelegateSlopBounds = new RectF(); private final int[] mTempOutLocation = new int[2]; - // Initialized in TaskbarController constructor. - private TaskbarController.TaskbarViewCallbacks mControllerCallbacks; - // Scale on elements that aren't icons. - private float mNonIconScale; - private int mItemMarginLeftRight; + private final int mItemMarginLeftRight; - // Initialized in init(). - private LayoutTransition mLayoutTransition; - private int mHotseatStartIndex; - private int mHotseatEndIndex; - private LinearLayout mButtonRegion; + private final TaskbarActivityContext mActivityContext; + + // Initialized in TaskbarController constructor. + private View.OnClickListener mIconClickListener; + private View.OnLongClickListener mIconLongClickListener; + + LinearLayout mSystemButtonContainer; + LinearLayout mHotseatIconsContainer; // Delegate touches to the closest view if within mIconTouchSize. private boolean mDelegateTargeted; @@ -91,10 +79,12 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa // Only non-null when the corresponding Folder is open. private @Nullable FolderIcon mLeaveBehindFolderIcon; - private int mNavButtonStartIndex; /** Provider of buttons added to taskbar in 3 button nav */ private ButtonProvider mButtonProvider; + private boolean mDisableRelayout; + private boolean mAreHolesAllowed; + public TaskbarView(@NonNull Context context) { this(context, null); } @@ -111,80 +101,58 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa public TaskbarView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + mActivityContext = ActivityContext.lookupContext(context); Resources resources = getResources(); mIconTouchSize = resources.getDimensionPixelSize(R.dimen.taskbar_icon_touch_size); + mItemMarginLeftRight = resources.getDimensionPixelSize(R.dimen.taskbar_icon_spacing); + mIsRtl = Utilities.isRtl(resources); mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); } - protected void construct(TaskbarController.TaskbarViewCallbacks taskbarViewCallbacks, - ButtonProvider buttonProvider) { - mControllerCallbacks = taskbarViewCallbacks; - mNonIconScale = mControllerCallbacks.getNonIconScale(this); - mItemMarginLeftRight = getResources().getDimensionPixelSize(R.dimen.taskbar_icon_spacing); - mItemMarginLeftRight = Math.round(mItemMarginLeftRight * mNonIconScale); - mButtonProvider = buttonProvider; - mButtonProvider.setMarginLeftRight(mItemMarginLeftRight); + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + mSystemButtonContainer = findViewById(R.id.system_button_layout); + mHotseatIconsContainer = findViewById(R.id.hotseat_icons_layout); } - protected void init(int numHotseatIcons, SysUINavigationMode.Mode newMode) { - // TODO: check if buttons on left - if (newMode == SysUINavigationMode.Mode.THREE_BUTTONS && ENABLE_THREE_BUTTON_TASKBAR) { - // 3 button - mNavButtonStartIndex = 0; + protected void construct(OnClickListener clickListener, OnLongClickListener longClickListener, + ButtonProvider buttonProvider) { + mIconClickListener = clickListener; + mIconLongClickListener = longClickListener; + mButtonProvider = buttonProvider; + + if (mActivityContext.canShowNavButtons()) { createNavButtons(); } else { - mNavButtonStartIndex = -1; - removeNavButtons(); + mSystemButtonContainer.setVisibility(GONE); } - mHotseatStartIndex = mNavButtonStartIndex + 1; - mHotseatEndIndex = mHotseatStartIndex + numHotseatIcons - 1; + int numHotseatIcons = mActivityContext.getDeviceProfile().numShownHotseatIcons; updateHotseatItems(new ItemInfo[numHotseatIcons]); - - mLayoutTransition = new LayoutTransition(); - addUpdateListenerForAllLayoutTransitions(() -> { - if (getLayoutTransition() == mLayoutTransition) { - mControllerCallbacks.onItemPositionsChanged(this); - } - }); - setLayoutTransition(mLayoutTransition); } - private void addUpdateListenerForAllLayoutTransitions(Runnable onUpdate) { - addUpdateListenerForLayoutTransition(LayoutTransition.CHANGE_APPEARING, onUpdate); - addUpdateListenerForLayoutTransition(LayoutTransition.CHANGE_DISAPPEARING, onUpdate); - addUpdateListenerForLayoutTransition(LayoutTransition.CHANGING, onUpdate); - addUpdateListenerForLayoutTransition(LayoutTransition.APPEARING, onUpdate); - addUpdateListenerForLayoutTransition(LayoutTransition.DISAPPEARING, onUpdate); - } - - private void addUpdateListenerForLayoutTransition(int transitionType, Runnable onUpdate) { - Animator anim = mLayoutTransition.getAnimator(transitionType); - if (anim instanceof ValueAnimator) { - ((ValueAnimator) anim).addUpdateListener(valueAnimator -> onUpdate.run()); - } else { - AnimatorSet animSet = new AnimatorSet(); - ValueAnimator updateAnim = ValueAnimator.ofFloat(0, 1); - updateAnim.addUpdateListener(valueAnimator -> onUpdate.run()); - animSet.playTogether(anim, updateAnim); - mLayoutTransition.setAnimator(transitionType, animSet); + /** + * Enables/disables empty icons in taskbar so that the layout matches with Launcher + */ + public void setHolesAllowedInLayout(boolean areHolesAllowed) { + if (mAreHolesAllowed != areHolesAllowed) { + mAreHolesAllowed = areHolesAllowed; + updateHotseatItemsVisibility(); + // TODO: Add animation } } - protected void cleanup() { - endAllLayoutTransitionAnimators(); - setLayoutTransition(null); - removeAllViews(); - } - - private void endAllLayoutTransitionAnimators() { - mLayoutTransition.getAnimator(LayoutTransition.CHANGE_APPEARING).end(); - mLayoutTransition.getAnimator(LayoutTransition.CHANGE_DISAPPEARING).end(); - mLayoutTransition.getAnimator(LayoutTransition.CHANGING).end(); - mLayoutTransition.getAnimator(LayoutTransition.APPEARING).end(); - mLayoutTransition.getAnimator(LayoutTransition.DISAPPEARING).end(); + private void setHolesAllowedInLayoutNoAnimation(boolean areHolesAllowed) { + if (mAreHolesAllowed != areHolesAllowed) { + mAreHolesAllowed = areHolesAllowed; + updateHotseatItemsVisibility(); + onMeasure(makeMeasureSpec(getMeasuredWidth(), EXACTLY), + makeMeasureSpec(getMeasuredHeight(), EXACTLY)); + onLayout(false, getLeft(), getTop(), getRight(), getBottom()); + } } /** @@ -192,10 +160,9 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa */ protected void updateHotseatItems(ItemInfo[] hotseatItemInfos) { for (int i = 0; i < hotseatItemInfos.length; i++) { - ItemInfo hotseatItemInfo = hotseatItemInfos[!mIsRtl ? i - : hotseatItemInfos.length - i - 1]; - int hotseatIndex = mHotseatStartIndex + i; - View hotseatView = getChildAt(hotseatIndex); + ItemInfo hotseatItemInfo = hotseatItemInfos[ + !mIsRtl ? i : hotseatItemInfos.length - i - 1]; + View hotseatView = mHotseatIconsContainer.getChildAt(i); // Replace any Hotseat views with the appropriate type if it's not already that type. final int expectedLayoutResId; @@ -213,23 +180,23 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa } else { expectedLayoutResId = R.layout.taskbar_app_icon; } - if (hotseatView == null || hotseatView.getSourceLayoutResId() != expectedLayoutResId + if (hotseatView == null + || hotseatView.getSourceLayoutResId() != expectedLayoutResId || needsReinflate) { - removeView(hotseatView); - ActivityContext activityContext = getActivityContext(); + mHotseatIconsContainer.removeView(hotseatView); if (isFolder) { FolderInfo folderInfo = (FolderInfo) hotseatItemInfo; FolderIcon folderIcon = FolderIcon.inflateFolderAndIcon(expectedLayoutResId, - getActivityContext(), this, folderInfo); + mActivityContext, this, folderInfo); folderIcon.setTextVisible(false); hotseatView = folderIcon; } else { hotseatView = inflate(expectedLayoutResId); } - int iconSize = activityContext.getDeviceProfile().iconSizePx; + int iconSize = mActivityContext.getDeviceProfile().iconSizePx; LayoutParams lp = new LayoutParams(iconSize, iconSize); lp.setMargins(mItemMarginLeftRight, 0, mItemMarginLeftRight, 0); - addView(hotseatView, hotseatIndex, lp); + mHotseatIconsContainer.addView(hotseatView, i, lp); } // Apply the Hotseat ItemInfos, or hide the view if there is none for a given index. @@ -237,13 +204,11 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa && hotseatItemInfo instanceof WorkspaceItemInfo) { ((BubbleTextView) hotseatView).applyFromWorkspaceItem( (WorkspaceItemInfo) hotseatItemInfo); - hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener()); - hotseatView.setOnLongClickListener( - mControllerCallbacks.getItemOnLongClickListener()); + hotseatView.setOnClickListener(mIconClickListener); + hotseatView.setOnLongClickListener(mIconLongClickListener); } else if (isFolder) { - hotseatView.setOnClickListener(mControllerCallbacks.getItemOnClickListener()); - hotseatView.setOnLongClickListener( - mControllerCallbacks.getItemOnLongClickListener()); + hotseatView.setOnClickListener(mIconClickListener); + hotseatView.setOnLongClickListener(mIconLongClickListener); } else { hotseatView.setOnClickListener(null); hotseatView.setOnLongClickListener(null); @@ -254,24 +219,14 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa } protected void updateHotseatItemsVisibility() { - for (int i = mHotseatStartIndex; i <= mHotseatEndIndex; i++) { - updateHotseatItemVisibility(getChildAt(i)); + for (int i = mHotseatIconsContainer.getChildCount() - 1; i >= 0; i--) { + updateHotseatItemVisibility(mHotseatIconsContainer.getChildAt(i)); } } private void updateHotseatItemVisibility(View hotseatView) { - if (hotseatView.getTag() != null) { - hotseatView.setVisibility(VISIBLE); - } else { - int oldVisibility = hotseatView.getVisibility(); - int newVisibility = mControllerCallbacks.getEmptyHotseatViewVisibility(this); - hotseatView.setVisibility(newVisibility); - if (oldVisibility == GONE && newVisibility != GONE) { - // By default, the layout transition only runs when going to VISIBLE, - // but we want it to run when going to GONE to INVISIBLE as well. - getLayoutTransition().showChild(this, hotseatView, oldVisibility); - } - } + hotseatView.setVisibility( + hotseatView.getTag() != null ? VISIBLE : (mAreHolesAllowed ? INVISIBLE : GONE)); } @Override @@ -378,49 +333,20 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa return findDelegateView(xInOurCoordinates, yInOurCoorindates) != null; } - private void removeNavButtons() { - if (mButtonRegion != null) { - mButtonRegion.removeAllViews(); - removeView(mButtonRegion); - } // else We've never been in 3 button. Woah Scoob! - } - /** * Add back/home/recents buttons into a single ViewGroup that will be inserted at * {@param navButtonStartIndex} */ private void createNavButtons() { - ActivityContext context = getActivityContext(); - if (mButtonRegion == null) { - mButtonRegion = new LinearLayout(getContext()); - } else { - mButtonRegion.removeAllViews(); - } - mButtonRegion.setVisibility(VISIBLE); - LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams( - context.getDeviceProfile().iconSizePx, - context.getDeviceProfile().iconSizePx + mActivityContext.getDeviceProfile().iconSizePx, + mActivityContext.getDeviceProfile().iconSizePx ); buttonParams.gravity = Gravity.CENTER; - View backButton = mButtonProvider.getBack(); - backButton.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick( - BUTTON_BACK)); - mButtonRegion.addView(backButton, buttonParams); - - // Home button - View homeButton = mButtonProvider.getHome(); - homeButton.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick( - BUTTON_HOME)); - mButtonRegion.addView(homeButton, buttonParams); - - View recentsButton = mButtonProvider.getRecents(); - recentsButton.setOnClickListener(view -> mControllerCallbacks.onNavigationButtonClick( - BUTTON_RECENTS)); - mButtonRegion.addView(recentsButton, buttonParams); - - addView(mButtonRegion, mNavButtonStartIndex); + mSystemButtonContainer.addView(mButtonProvider.getBack(), buttonParams); + mSystemButtonContainer.addView(mButtonProvider.getHome(), buttonParams); + mSystemButtonContainer.addView(mButtonProvider.getRecents(), buttonParams); } @Override @@ -428,7 +354,7 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa switch (event.getAction()) { case DragEvent.ACTION_DRAG_STARTED: mIsDraggingItem = true; - AbstractFloatingView.closeAllOpenViews(getActivityContext()); + AbstractFloatingView.closeAllOpenViews(mActivityContext); return true; case DragEvent.ACTION_DRAG_ENDED: mIsDraggingItem = false; @@ -445,26 +371,26 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa * @return The bounding box of where the hotseat elements are relative to this TaskbarView. */ protected RectF getHotseatBounds() { - View firstHotseatView = null, lastHotseatView = null; - for (int i = mHotseatStartIndex; i <= mHotseatEndIndex; i++) { - View child = getChildAt(i); - if (child.getVisibility() != GONE) { - if (firstHotseatView == null) { - firstHotseatView = child; - } - lastHotseatView = child; - } + RectF result; + mDisableRelayout = true; + boolean wereHolesAllowed = mAreHolesAllowed; + setHolesAllowedInLayoutNoAnimation(true); + result = new RectF( + mHotseatIconsContainer.getLeft(), + mHotseatIconsContainer.getTop(), + mHotseatIconsContainer.getRight(), + mHotseatIconsContainer.getBottom()); + setHolesAllowedInLayoutNoAnimation(wereHolesAllowed); + mDisableRelayout = false; + + return result; + } + + @Override + public void requestLayout() { + if (!mDisableRelayout) { + super.requestLayout(); } - if (firstHotseatView == null || lastHotseatView == null) { - return new RectF(); - } - View leftmostHotseatView = !mIsRtl ? firstHotseatView : lastHotseatView; - View rightmostHotseatView = !mIsRtl ? lastHotseatView : firstHotseatView; - return new RectF( - leftmostHotseatView.getLeft() - mItemMarginLeftRight, - leftmostHotseatView.getTop(), - rightmostHotseatView.getRight() + mItemMarginLeftRight, - rightmostHotseatView.getBottom()); } // FolderIconParent implemented methods. @@ -495,7 +421,7 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa } private View inflate(@LayoutRes int layoutResId) { - return getActivityContext().getLayoutInflater().inflate(layoutResId, this, false); + return mActivityContext.getLayoutInflater().inflate(layoutResId, this, false); } @Override @@ -503,7 +429,11 @@ public class TaskbarView extends LinearLayout implements FolderIcon.FolderIconPa // Ignore, we just implement Insettable to draw behind system insets. } - private T getActivityContext() { - return ActivityContext.lookupContext(getContext()); + public void setIconsVisibility(boolean isVisible) { + mHotseatIconsContainer.setVisibility(isVisible ? VISIBLE : INVISIBLE); + } + + public boolean areIconsVisible() { + return mHotseatIconsContainer.getVisibility() == VISIBLE; } } diff --git a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java index 45bb521336..f0b02b3c47 100644 --- a/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java +++ b/quickstep/src/com/android/launcher3/uioverrides/QuickstepLauncher.java @@ -165,7 +165,7 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { @Override public boolean startActivitySafely(View v, Intent intent, ItemInfo item) { // Only pause is taskbar controller is not present - mHotseatPredictionController.setPauseUIUpdate(getTaskbarController() == null); + mHotseatPredictionController.setPauseUIUpdate(getTaskbarUIController() == null); return super.startActivitySafely(v, intent, item); } @@ -233,9 +233,9 @@ public class QuickstepLauncher extends BaseQuickstepLauncher { @Override public void bindWorkspaceItemsChanged(List updated) { super.bindWorkspaceItemsChanged(updated); - if (getTaskbarController() != null && updated.stream() + if (getTaskbarUIController() != null && updated.stream() .filter(w -> w.container == CONTAINER_HOTSEAT).findFirst().isPresent()) { - getTaskbarController().onHotseatUpdated(); + getTaskbarUIController().onHotseatUpdated(); } } diff --git a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java index e0f430d6cf..005e9b5a7d 100644 --- a/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java +++ b/quickstep/src/com/android/quickstep/AbsSwipeUpHandler.java @@ -750,6 +750,7 @@ public abstract class AbsSwipeUpHandler, setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */); mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED); mGestureStarted = true; + SystemUiProxy.INSTANCE.get(mContext).notifySwipeUpGestureStarted(); } /** diff --git a/quickstep/src/com/android/quickstep/BaseActivityInterface.java b/quickstep/src/com/android/quickstep/BaseActivityInterface.java index f5ddd0e52f..86bf1194bb 100644 --- a/quickstep/src/com/android/quickstep/BaseActivityInterface.java +++ b/quickstep/src/com/android/quickstep/BaseActivityInterface.java @@ -52,7 +52,6 @@ import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.statehandlers.DepthController; import com.android.launcher3.statemanager.BaseState; import com.android.launcher3.statemanager.StatefulActivity; -import com.android.launcher3.taskbar.TaskbarController; import com.android.launcher3.touch.PagedOrientationHandler; import com.android.launcher3.util.WindowBounds; import com.android.launcher3.views.ScrimView; @@ -123,11 +122,6 @@ public abstract class BaseActivityInterface SystemUiProxy.INSTANCE.get(mContext) - .onQuickSwitchToNewTask(rotation)); + .notifyPrioritizedRotation(rotation)); } /** diff --git a/quickstep/src/com/android/quickstep/SystemUiProxy.java b/quickstep/src/com/android/quickstep/SystemUiProxy.java index acf999253f..7ef6a4a4c8 100644 --- a/quickstep/src/com/android/quickstep/SystemUiProxy.java +++ b/quickstep/src/com/android/quickstep/SystemUiProxy.java @@ -341,6 +341,17 @@ public class SystemUiProxy implements ISystemUiProxy, } } + @Override + public void notifySwipeUpGestureStarted() { + if (mSystemUiProxy != null) { + try { + mSystemUiProxy.notifySwipeUpGestureStarted(); + } catch (RemoteException e) { + Log.w(TAG, "Failed call notifySwipeUpGestureStarted", e); + } + } + } + /** * Notifies that swipe-to-home action is finished. */ @@ -350,18 +361,18 @@ public class SystemUiProxy implements ISystemUiProxy, try { mSystemUiProxy.notifySwipeToHomeFinished(); } catch (RemoteException e) { - Log.w(TAG, "Failed call setPinnedStackAnimationType", e); + Log.w(TAG, "Failed call notifySwipeToHomeFinished", e); } } } @Override - public void onQuickSwitchToNewTask(int rotation) { + public void notifyPrioritizedRotation(int rotation) { if (mSystemUiProxy != null) { try { - mSystemUiProxy.onQuickSwitchToNewTask(rotation); + mSystemUiProxy.notifyPrioritizedRotation(rotation); } catch (RemoteException e) { - Log.w(TAG, "Failed call onQuickSwitchToNewTask with arg: " + rotation, e); + Log.w(TAG, "Failed call notifyPrioritizedRotation with arg: " + rotation, e); } } } diff --git a/quickstep/src/com/android/quickstep/TouchInteractionService.java b/quickstep/src/com/android/quickstep/TouchInteractionService.java index edc7a3c8b8..7956fcc562 100644 --- a/quickstep/src/com/android/quickstep/TouchInteractionService.java +++ b/quickstep/src/com/android/quickstep/TouchInteractionService.java @@ -52,7 +52,6 @@ import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.os.Looper; -import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; import android.util.Log; @@ -75,6 +74,7 @@ import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.provider.RestoreDbTask; import com.android.launcher3.statemanager.StatefulActivity; +import com.android.launcher3.taskbar.TaskbarManager; import com.android.launcher3.testing.TestLogging; import com.android.launcher3.testing.TestProtocol; import com.android.launcher3.tracing.LauncherTraceProto; @@ -147,21 +147,9 @@ public class TouchInteractionService extends Service implements PluginListener { - if (mTaskbarOverviewProxyDelegate == null) { - return; - } - mTaskbarOverviewProxyDelegate - .updateImeStatus(displayId, vis, backDisposition, showImeSwitcher); - }); + int backDisposition, boolean showImeSwitcher) { + MAIN_EXECUTOR.execute(() -> mTaskbarManager.updateImeStatus( + displayId, vis, backDisposition, showImeSwitcher)); } - }; - public interface TaskbarOverviewProxyDelegate { - void updateImeStatus(int displayId, int vis, int backDisposition, - boolean showImeSwitcher); + public TaskbarManager getTaskbarManager() { + return mTaskbarManager; + } } private static boolean sConnected = false; - private static TouchInteractionService sInstance; private static boolean sIsInitialized = false; private RotationTouchHelper mRotationTouchHelper; - @Nullable - private TaskbarOverviewProxyDelegate mTaskbarOverviewProxyDelegate; public static boolean isConnected() { return sConnected; } - @Nullable - public static TouchInteractionService getInstance() { - return sInstance; - } public static boolean isInitialized() { return sIsInitialized; @@ -336,9 +308,7 @@ public class TouchInteractionService extends Service implements PluginListener \ No newline at end of file + + + + \ No newline at end of file diff --git a/res/drawable/widgets_bottom_sheet_background.xml b/res/drawable/widgets_bottom_sheet_background.xml index faa414c506..24607678c6 100644 --- a/res/drawable/widgets_bottom_sheet_background.xml +++ b/res/drawable/widgets_bottom_sheet_background.xml @@ -18,8 +18,8 @@ android:shape="rectangle"> diff --git a/res/layout/widget_cell_content.xml b/res/layout/widget_cell_content.xml index 0f6fc6cdb7..b27b50560e 100644 --- a/res/layout/widget_cell_content.xml +++ b/res/layout/widget_cell_content.xml @@ -33,14 +33,6 @@ android:layout_height="match_parent" android:importantForAccessibility="no" android:layout_gravity="fill"/> - - diff --git a/robolectric_tests/src/com/android/launcher3/testing/TestActivity.java b/robolectric_tests/src/com/android/launcher3/testing/TestActivity.java index dbf4b3e6c0..17d0ac1a9f 100644 --- a/robolectric_tests/src/com/android/launcher3/testing/TestActivity.java +++ b/robolectric_tests/src/com/android/launcher3/testing/TestActivity.java @@ -27,7 +27,12 @@ public class TestActivity extends BaseActivity implements ActivityContext { @Override public BaseDragLayer getDragLayer() { - return null; + return new BaseDragLayer(this, /* attrs= */ null, /* alphaChannelCount= */ 1) { + @Override + public void recreateControllers() { + // Do nothing. + } + }; } @Override diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java index 6b5678cb8a..e1214ff39f 100644 --- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java +++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListAdapterTest.java @@ -82,7 +82,7 @@ public final class WidgetsListAdapterTest { mTestProfile.numColumns = 5; mUserHandle = Process.myUserHandle(); mAdapter = new WidgetsListAdapter(mContext, mMockLayoutInflater, mMockWidgetCache, - mIconCache, null, null, null); + mIconCache, null, null); mAdapter.registerAdapterDataObserver(mListener); doAnswer(invocation -> ((ComponentWithLabel) invocation.getArgument(0)) diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java index 12a092dafe..84a03d572c 100644 --- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java +++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListHeaderViewHolderBinderTest.java @@ -105,8 +105,7 @@ public final class WidgetsListHeaderViewHolderBinderTest { mWidgetPreviewLoader, mIconCache, /* iconClickListener= */ view -> {}, - /* iconLongClickListener= */ view -> false, - /* searchBarUIHelper= */ null); + /* iconLongClickListener= */ view -> false); mViewHolderBinder = new WidgetsListHeaderViewHolderBinder( LayoutInflater.from(mTestActivity), mOnHeaderClickListener, widgetsListAdapter); } diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java index e09034132c..075c58db6b 100644 --- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java +++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListSearchHeaderViewHolderBinderTest.java @@ -105,8 +105,7 @@ public final class WidgetsListSearchHeaderViewHolderBinderTest { mWidgetPreviewLoader, mIconCache, /* iconClickListener= */ view -> {}, - /* iconLongClickListener= */ view -> false, - /* searchBarUIHelper= */ null); + /* iconLongClickListener= */ view -> false); mViewHolderBinder = new WidgetsListSearchHeaderViewHolderBinder( LayoutInflater.from(mTestActivity), mOnHeaderClickListener, widgetsListAdapter); } diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java index 0935d1cfc4..0c6e71711d 100644 --- a/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java +++ b/robolectric_tests/src/com/android/launcher3/widget/picker/WidgetsListTableViewHolderBinderTest.java @@ -111,8 +111,7 @@ public final class WidgetsListTableViewHolderBinderTest { mWidgetPreviewLoader, mIconCache, /* iconClickListener= */ view -> {}, - /* iconLongClickListener= */ view -> false, - /* searchBarUIHelper= */ null); + /* iconLongClickListener= */ view -> false); mViewHolderBinder = new WidgetsListTableViewHolderBinder( mContext, LayoutInflater.from(mTestActivity), diff --git a/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java b/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java index 4e6f17c384..a057a84d06 100644 --- a/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java +++ b/robolectric_tests/src/com/android/launcher3/widget/picker/search/WidgetsSearchBarControllerTest.java @@ -19,24 +19,27 @@ package com.android.launcher3.widget.picker.search; import static org.junit.Assert.assertEquals; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; -import android.content.Context; import android.view.View; import android.widget.ImageButton; import com.android.launcher3.ExtendedEditText; import com.android.launcher3.search.SearchAlgorithm; +import com.android.launcher3.testing.TestActivity; import com.android.launcher3.widget.model.WidgetsListBaseEntry; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; +import org.robolectric.android.controller.ActivityController; import java.util.ArrayList; @@ -44,7 +47,9 @@ import java.util.ArrayList; public class WidgetsSearchBarControllerTest { private WidgetsSearchBarController mController; - private Context mContext; + // TODO: Replace ActivityController with ActivityScenario, which is the recommended way for + // activity testing. + private ActivityController mActivityController; private ExtendedEditText mEditText; private ImageButton mCancelButton; @Mock @@ -55,13 +60,20 @@ public class WidgetsSearchBarControllerTest { @Before public void setUp() { MockitoAnnotations.initMocks(this); - mContext = RuntimeEnvironment.application; - mEditText = new ExtendedEditText(mContext); - mCancelButton = new ImageButton(mContext); + mActivityController = Robolectric.buildActivity(TestActivity.class); + TestActivity testActivity = mActivityController.setup().get(); + + mEditText = new ExtendedEditText(testActivity); + mCancelButton = new ImageButton(testActivity); mController = new WidgetsSearchBarController( mSearchAlgorithm, mEditText, mCancelButton, mSearchModeListener); } + @After + public void tearDown() { + mActivityController.destroy(); + } + @Test public void onSearchResult_shouldInformSearchModeListener() { ArrayList entries = new ArrayList<>(); @@ -119,14 +131,18 @@ public class WidgetsSearchBarControllerTest { public void cancelSearch_shouldInformSearchModeListenerToClearResultsAndExitSearch() { mCancelButton.performClick(); - verify(mSearchModeListener).exitSearchMode(); + // 1 time explicitly from the cancel button on click listener. + // Another from the setText("") the cancel button on click listener causing afterTextChange. + verify(mSearchModeListener, times(2)).exitSearchMode(); } @Test public void cancelSearch_shouldCancelSearch() { mCancelButton.performClick(); - verify(mSearchAlgorithm).cancel(true); + // 1 time explicitly from the cancel button on click listener. + // Another from the setText("") the cancel button on click listener causing afterTextChange. + verify(mSearchAlgorithm, times(2)).cancel(true); verifyNoMoreInteractions(mSearchAlgorithm); } diff --git a/src/com/android/launcher3/BubbleTextView.java b/src/com/android/launcher3/BubbleTextView.java index 3d044d68ce..322c6eed29 100644 --- a/src/com/android/launcher3/BubbleTextView.java +++ b/src/com/android/launcher3/BubbleTextView.java @@ -447,10 +447,6 @@ public class BubbleTextView extends TextView implements ItemInfoUpdateReceiver, * @param canvas The canvas to draw to. */ protected void drawDotIfNecessary(Canvas canvas) { - if (mActivity instanceof Launcher && ((Launcher) mActivity).isViewInTaskbar(this)) { - // TODO: support notification dots in Taskbar - return; - } if (!mForceHideDot && (hasDot() || mDotParams.scale > 0)) { getIconBounds(mDotParams.iconBounds); Utilities.scaleRectAboutCenter(mDotParams.iconBounds, diff --git a/src/com/android/launcher3/CellLayout.java b/src/com/android/launcher3/CellLayout.java index 1df9df6763..bfa1769ea8 100644 --- a/src/com/android/launcher3/CellLayout.java +++ b/src/com/android/launcher3/CellLayout.java @@ -1080,9 +1080,10 @@ public class CellLayout extends ViewGroup { cellToRect(targetCell[0], targetCell[1], spanX, spanY, mTempRect); // Now get the rect in drag layer coordinates. - getBoundsForViewInDragLayer(launcher.getDragLayer(), workspace, mTempRect, false, + getBoundsForViewInDragLayer(launcher.getDragLayer(), this, mTempRect, true, mTmpFloatArray, mTempRectF); Utilities.setRect(mTempRectF, mTempRect); + ((LauncherAppWidgetHostView) view).handleDrag(mTempRect, pageId); } } @@ -2594,7 +2595,9 @@ public class CellLayout extends ViewGroup { final int cellWidth = mCellWidth; final int cellHeight = mCellHeight; - final int hStartPadding = getPaddingLeft(); + // We observe a shift of 1 pixel on the x coordinate compared to the actual cell coordinates + final int hStartPadding = getPaddingLeft() + + (int) Math.ceil(getUnusedHorizontalSpace() / 2f); final int vStartPadding = getPaddingTop(); int x = hStartPadding + (cellX * mBorderSpacing) + (cellX * cellWidth); diff --git a/src/com/android/launcher3/Hotseat.java b/src/com/android/launcher3/Hotseat.java index b2a9e75e50..ff380ce1ae 100644 --- a/src/com/android/launcher3/Hotseat.java +++ b/src/com/android/launcher3/Hotseat.java @@ -49,7 +49,6 @@ public class Hotseat extends CellLayout implements Insettable { private final View mQsb; private final int mQsbHeight; - private final View mTaskbarView; private final int mTaskbarViewHeight; public Hotseat(Context context) { @@ -67,10 +66,7 @@ public class Hotseat extends CellLayout implements Insettable { mQsbHeight = mQsb.getLayoutParams().height; addView(mQsb); - mTaskbarView = LayoutInflater.from(context).inflate(R.layout.taskbar_view, this, false); - mTaskbarViewHeight = mTaskbarView.getLayoutParams().height; - // We want taskbar in the back so its background applies to Hotseat as well. - addView(mTaskbarView, 0); + mTaskbarViewHeight = context.getResources().getDimensionPixelSize(R.dimen.taskbar_size); } /** @@ -187,8 +183,6 @@ public class Hotseat extends CellLayout implements Insettable { int width = getShortcutsAndWidgets().getMeasuredWidth(); mQsb.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(mQsbHeight, MeasureSpec.EXACTLY)); - mTaskbarView.measure(MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), - MeasureSpec.makeMeasureSpec(mTaskbarViewHeight, MeasureSpec.EXACTLY)); } @Override @@ -202,13 +196,6 @@ public class Hotseat extends CellLayout implements Insettable { int bottom = b - t - getQsbOffsetY(); int top = bottom - mQsbHeight; mQsb.layout(left, top, right, bottom); - - int taskbarWidth = mTaskbarView.getMeasuredWidth(); - left = (r - l - taskbarWidth) / 2; - right = left + taskbarWidth; - bottom = b - t - getTaskbarOffsetY(); - top = bottom - mTaskbarViewHeight; - mTaskbarView.layout(left, top, right, bottom); } /** @@ -244,10 +231,4 @@ public class Hotseat extends CellLayout implements Insettable { return mQsb; } - /** - * Returns the Taskbar inside hotseat - */ - public View getTaskbarView() { - return mTaskbarView; - } } diff --git a/src/com/android/launcher3/Launcher.java b/src/com/android/launcher3/Launcher.java index cf902160ee..8889e60688 100644 --- a/src/com/android/launcher3/Launcher.java +++ b/src/com/android/launcher3/Launcher.java @@ -1942,13 +1942,6 @@ public class Launcher extends StatefulActivity implements Launche @Override public boolean startActivitySafely(View v, Intent intent, ItemInfo item) { - if (isViewInTaskbar(v)) { - // Start the activity without the hacky workarounds below, which assume the View was - // clicked when Launcher was resumed and will be hidden until Launcher is re-resumed - // (this isn't the case for Taskbar). - return super.startActivitySafely(v, intent, item); - } - if (!hasBeenResumed()) { // Workaround an issue where the WM launch animation is clobbered when finishing the // recents animation into launcher. Defer launching the activity until Launcher is @@ -2860,13 +2853,6 @@ public class Launcher extends StatefulActivity implements Launche .start(); } - /** - * @return Whether the View is in the same window as the Taskbar window. - */ - public boolean isViewInTaskbar(View v) { - return false; - } - public boolean supportsAdaptiveIconAnimation(View clickedView) { return false; } diff --git a/src/com/android/launcher3/LauncherAppState.java b/src/com/android/launcher3/LauncherAppState.java index b6cc6d644d..dabbdd3063 100644 --- a/src/com/android/launcher3/LauncherAppState.java +++ b/src/com/android/launcher3/LauncherAppState.java @@ -81,6 +81,8 @@ public class LauncherAppState { public LauncherAppState(Context context) { this(context, LauncherFiles.APP_ICONS_DB); + Log.v(Launcher.TAG, "LauncherAppState initiated"); + Preconditions.assertUIThread(); mInvariantDeviceProfile.addOnChangeListener(idp -> refreshAndReloadLauncher()); @@ -132,8 +134,6 @@ public class LauncherAppState { } public LauncherAppState(Context context, @Nullable String iconCacheFileName) { - Log.v(Launcher.TAG, "LauncherAppState initiated"); - Preconditions.assertUIThread(); mContext = context; mInvariantDeviceProfile = InvariantDeviceProfile.INSTANCE.get(context); @@ -142,6 +142,7 @@ public class LauncherAppState { iconCacheFileName, mIconProvider); mWidgetCache = new WidgetPreviewLoader(mContext, mIconCache); mModel = new LauncherModel(context, this, mIconCache, new AppFilter(mContext)); + mOnTerminateCallback.add(mIconCache::close); } private void onNotificationSettingsChanged(boolean areNotificationDotsEnabled) { diff --git a/src/com/android/launcher3/Utilities.java b/src/com/android/launcher3/Utilities.java index 7ae729aa0c..2884fba08d 100644 --- a/src/com/android/launcher3/Utilities.java +++ b/src/com/android/launcher3/Utilities.java @@ -324,15 +324,17 @@ public final class Utilities { } public static void scaleRectFAboutCenter(RectF r, float scale) { + scaleRectFAboutPivot(r, scale, r.centerX(), r.centerY()); + } + + public static void scaleRectFAboutPivot(RectF r, float scale, float px, float py) { if (scale != 1.0f) { - float cx = r.centerX(); - float cy = r.centerY(); - r.offset(-cx, -cy); + r.offset(-px, -py); r.left = r.left * scale; r.top = r.top * scale ; r.right = r.right * scale; r.bottom = r.bottom * scale; - r.offset(cx, cy); + r.offset(px, py); } } diff --git a/src/com/android/launcher3/WidgetPreviewLoader.java b/src/com/android/launcher3/WidgetPreviewLoader.java index 75d25d7aa3..8b7a750cc7 100644 --- a/src/com/android/launcher3/WidgetPreviewLoader.java +++ b/src/com/android/launcher3/WidgetPreviewLoader.java @@ -21,9 +21,7 @@ import android.graphics.Color; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; -import android.graphics.Rect; import android.graphics.RectF; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.CancellationSignal; @@ -35,9 +33,7 @@ import android.util.LongSparseArray; import android.util.Pair; import androidx.annotation.Nullable; -import androidx.annotation.UiThread; -import com.android.launcher3.icons.FastBitmapDrawable; import com.android.launcher3.icons.GraphicsUtils; import com.android.launcher3.icons.IconCache; import com.android.launcher3.icons.LauncherIcons; @@ -93,51 +89,6 @@ public class WidgetPreviewLoader { mDb = new CacheDb(context); } - /** - * Returns a drawable that can be used as a badge for the user or null. - */ - @UiThread - public Drawable getBadgeForUser(UserHandle user, int badgeSize) { - if (mMyUser.equals(user)) { - return null; - } - - Bitmap badgeBitmap = getUserBadge(user, badgeSize); - FastBitmapDrawable d = new FastBitmapDrawable(badgeBitmap); - d.setFilterBitmap(true); - d.setBounds(0, 0, badgeBitmap.getWidth(), badgeBitmap.getHeight()); - return d; - } - - private Bitmap getUserBadge(UserHandle user, int badgeSize) { - synchronized (mUserBadges) { - Bitmap badgeBitmap = mUserBadges.get(user); - if (badgeBitmap != null) { - return badgeBitmap; - } - - final Resources res = mContext.getResources(); - badgeBitmap = Bitmap.createBitmap(badgeSize, badgeSize, Bitmap.Config.ARGB_8888); - - Drawable drawable = mContext.getPackageManager().getUserBadgedDrawableForDensity( - new BitmapDrawable(res, badgeBitmap), user, - new Rect(0, 0, badgeSize, badgeSize), - 0); - if (drawable instanceof BitmapDrawable) { - badgeBitmap = ((BitmapDrawable) drawable).getBitmap(); - } else { - badgeBitmap.eraseColor(Color.TRANSPARENT); - Canvas c = new Canvas(badgeBitmap); - drawable.setBounds(0, 0, badgeSize, badgeSize); - drawable.draw(c); - c.setBitmap(null); - } - - mUserBadges.put(user, badgeBitmap); - return badgeBitmap; - } - } - /** * Generates the widget preview on {@link AsyncTask#THREAD_POOL_EXECUTOR}. Must be * called on UI thread diff --git a/src/com/android/launcher3/Workspace.java b/src/com/android/launcher3/Workspace.java index 5ba7623ece..98d80fedbc 100644 --- a/src/com/android/launcher3/Workspace.java +++ b/src/com/android/launcher3/Workspace.java @@ -2953,10 +2953,11 @@ public class Workspace extends PagedView }; final Workspace.ItemOperator packageAndUserAndApp = (ItemInfo info, View view) -> info != null - && info.getTargetComponent() != null - && TextUtils.equals(info.getTargetComponent().getPackageName(), packageName) + && info.itemType == ITEM_TYPE_APPLICATION && info.user.equals(user) - && info.itemType == ITEM_TYPE_APPLICATION; + && info.getTargetComponent() != null + && TextUtils.equals(info.getTargetComponent().getPackageName(), + packageName); final Workspace.ItemOperator packageAndUserAndAppInFolder = (info, view) -> { if (info instanceof FolderInfo) { FolderInfo folderInfo = (FolderInfo) info; @@ -2973,7 +2974,7 @@ public class Workspace extends PagedView cellLayouts.add(getHotseat()); forEachVisiblePage(page -> cellLayouts.add((CellLayout) page)); - // Order: App icons, app in folder. Items in hotseat get returned first. + // Order: Preferred item, App icons in hotseat/workspace, app in folder in hotseat/workspace if (ADAPTIVE_ICON_WINDOW_ANIM.get()) { return getFirstMatch(cellLayouts, preferredItem, preferredItemInFolder, packageAndUserAndApp, packageAndUserAndAppInFolder); @@ -3010,34 +3011,17 @@ public class Workspace extends PagedView } /** + * Finds the first view matching the ordered operators across the given cell layouts by order. * @param cellLayouts List of CellLayouts to scan, in order of preference. * @param operators List of operators, in order starting from best matching operator. - * @return */ View getFirstMatch(Iterable cellLayouts, final ItemOperator... operators) { - // This array is filled with the first match for each operator. - final View[] matches = new View[operators.length]; - // For efficiency, the outer loop should be CellLayout. - for (CellLayout cellLayout : cellLayouts) { - mapOverCellLayout(cellLayout, (info, v) -> { - for (int i = 0; i < operators.length; ++i) { - if (matches[i] == null && operators[i].evaluate(info, v)) { - matches[i] = v; - if (i == 0) { - // We can return since this is the best match possible. - return true; - } - } + for (ItemOperator operator : operators) { + for (CellLayout cellLayout : cellLayouts) { + View match = mapOverCellLayout(cellLayout, operator); + if (match != null) { + return match; } - return false; - }); - if (matches[0] != null) { - break; - } - } - for (View match : matches) { - if (match != null) { - return match; } } return null; @@ -3111,16 +3095,16 @@ public class Workspace extends PagedView */ public void mapOverItems(ItemOperator op) { for (CellLayout layout : getWorkspaceAndHotseatCellLayouts()) { - if (mapOverCellLayout(layout, op)) { + if (mapOverCellLayout(layout, op) != null) { return; } } } - private boolean mapOverCellLayout(CellLayout layout, ItemOperator op) { + private View mapOverCellLayout(CellLayout layout, ItemOperator op) { // TODO(b/128460496) Potential race condition where layout is not yet loaded if (layout == null) { - return false; + return null; } ShortcutAndWidgetContainer container = layout.getShortcutsAndWidgets(); // map over all the shortcuts on the workspace @@ -3128,10 +3112,10 @@ public class Workspace extends PagedView for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) { View item = container.getChildAt(itemIdx); if (op.evaluate((ItemInfo) item.getTag(), item)) { - return true; + return item; } } - return false; + return null; } void updateShortcuts(List shortcuts) { diff --git a/src/com/android/launcher3/allapps/AllAppsStore.java b/src/com/android/launcher3/allapps/AllAppsStore.java index 355ccadeba..2443b8360d 100644 --- a/src/com/android/launcher3/allapps/AllAppsStore.java +++ b/src/com/android/launcher3/allapps/AllAppsStore.java @@ -17,6 +17,7 @@ package com.android.launcher3.allapps; import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR; import static com.android.launcher3.model.data.AppInfo.EMPTY_ARRAY; +import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK; import android.view.View; import android.view.ViewGroup; @@ -157,11 +158,17 @@ public class AllAppsStore { * If this app is installed and supports incremental downloads, the progress bar will be updated * the app's total download progress. Otherwise, the progress bar will be updated to the app's * installation progress. + * + * If this app is fully downloaded, the app icon will be reapplied. */ public void updateProgressBar(AppInfo app) { updateAllIcons((child) -> { if (child.getTag() == app) { - child.applyProgressLevel(); + if ((app.runtimeStatusFlags & FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) == 0) { + child.applyFromApplicationInfo(app); + } else { + child.applyProgressLevel(); + } } }); } diff --git a/src/com/android/launcher3/allapps/FloatingHeaderView.java b/src/com/android/launcher3/allapps/FloatingHeaderView.java index af7896ac24..a0c598aa6e 100644 --- a/src/com/android/launcher3/allapps/FloatingHeaderView.java +++ b/src/com/android/launcher3/allapps/FloatingHeaderView.java @@ -298,7 +298,7 @@ public class FloatingHeaderView extends LinearLayout implements @Override protected void dispatchDraw(Canvas canvas) { - if (mHeaderCollapsed && mTabLayout.getVisibility() == VISIBLE + if (mHeaderCollapsed && !mCollapsed && mTabLayout.getVisibility() == VISIBLE && mHeaderColor != Color.TRANSPARENT) { mBGPaint.setColor(mHeaderColor); mBGPaint.setAlpha((int) (255 * mHeaderAnimator.getAnimatedFraction())); diff --git a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java index a03e48d29a..60a1732bb6 100644 --- a/src/com/android/launcher3/graphics/GridCustomizationsProvider.java +++ b/src/com/android/launcher3/graphics/GridCustomizationsProvider.java @@ -4,6 +4,7 @@ import static com.android.launcher3.Utilities.getPrefs; import static com.android.launcher3.util.Themes.KEY_THEMED_ICONS; import static com.android.launcher3.util.Themes.isThemedIconEnabled; +import android.annotation.TargetApi; import android.content.ContentProvider; import android.content.ContentValues; import android.content.pm.PackageManager; @@ -12,14 +13,24 @@ import android.database.Cursor; import android.database.MatrixCursor; import android.net.Uri; import android.os.Binder; +import android.os.Build; import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.Looper; +import android.os.Message; +import android.os.Messenger; +import android.util.ArrayMap; import android.util.Log; import android.util.Xml; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.InvariantDeviceProfile.GridOption; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.config.FeatureFlags; +import com.android.launcher3.util.Executors; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -65,6 +76,11 @@ public class GridCustomizationsProvider extends ContentProvider { private static final String ICON_THEMED = "/icon_themed"; private static final String BOOLEAN_VALUE = "boolean_value"; + private static final String KEY_SURFACE_PACKAGE = "surface_package"; + private static final String KEY_CALLBACK = "callback"; + + private final ArrayMap mActivePreviews = new ArrayMap<>(); + @Override public boolean onCreate() { return true; @@ -177,10 +193,74 @@ public class GridCustomizationsProvider extends ContentProvider { return null; } - if (!METHOD_GET_PREVIEW.equals(method)) { + if (!Utilities.ATLEAST_R || !METHOD_GET_PREVIEW.equals(method)) { return null; } + return getPreview(extras); + } - return new PreviewSurfaceRenderer(getContext(), extras).render(); + @TargetApi(Build.VERSION_CODES.R) + private synchronized Bundle getPreview(Bundle request) { + PreviewLifecycleObserver observer = null; + try { + PreviewSurfaceRenderer renderer = new PreviewSurfaceRenderer(getContext(), request); + + // Destroy previous + destroyObserver(mActivePreviews.get(renderer.getHostToken())); + + observer = new PreviewLifecycleObserver(renderer); + mActivePreviews.put(renderer.getHostToken(), observer); + + renderer.loadAsync(); + renderer.getHostToken().linkToDeath(observer, 0); + + Bundle result = new Bundle(); + result.putParcelable(KEY_SURFACE_PACKAGE, renderer.getSurfacePackage()); + + Messenger messenger = new Messenger(new Handler(Looper.getMainLooper(), observer)); + Message msg = Message.obtain(); + msg.replyTo = messenger; + result.putParcelable(KEY_CALLBACK, msg); + return result; + } catch (Exception e) { + Log.e(TAG, "Unable to generate preview", e); + if (observer != null) { + destroyObserver(observer); + } + return null; + } + } + + private synchronized void destroyObserver(PreviewLifecycleObserver observer) { + if (observer == null || observer.destroyed) { + return; + } + observer.destroyed = true; + Executors.MAIN_EXECUTOR.execute(observer.renderer::destroy); + PreviewLifecycleObserver cached = mActivePreviews.get(observer.renderer.getHostToken()); + if (cached == observer) { + mActivePreviews.remove(observer.renderer.getHostToken()); + } + } + + private class PreviewLifecycleObserver implements Handler.Callback, DeathRecipient { + + public final PreviewSurfaceRenderer renderer; + public boolean destroyed = false; + + PreviewLifecycleObserver(PreviewSurfaceRenderer renderer) { + this.renderer = renderer; + } + + @Override + public boolean handleMessage(Message message) { + destroyObserver(this); + return true; + } + + @Override + public void binderDied() { + destroyObserver(this); + } } } diff --git a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java index f5b6890867..2a1aec84a3 100644 --- a/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java +++ b/src/com/android/launcher3/graphics/LauncherPreviewRenderer.java @@ -23,7 +23,6 @@ import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT import static com.android.launcher3.model.ModelUtils.filterCurrentWorkspaceItems; import static com.android.launcher3.model.ModelUtils.getMissingHotseatRanks; import static com.android.launcher3.model.ModelUtils.sortWorkspaceItemsSpatially; -import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.annotation.TargetApi; import android.app.Fragment; @@ -32,7 +31,6 @@ import android.appwidget.AppWidgetProviderInfo; import android.content.Context; import android.content.ContextWrapper; import android.content.Intent; -import android.content.pm.ShortcutInfo; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Rect; @@ -43,11 +41,12 @@ import android.os.Handler; import android.os.Looper; import android.os.Process; import android.util.AttributeSet; -import android.util.Log; import android.view.ContextThemeWrapper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.WindowInsets; +import android.view.WindowManager; import android.widget.TextClock; import com.android.launcher3.BubbleTextView; @@ -57,23 +56,17 @@ import com.android.launcher3.Hotseat; import com.android.launcher3.InsettableFrameLayout; import com.android.launcher3.InvariantDeviceProfile; import com.android.launcher3.LauncherAppState; -import com.android.launcher3.LauncherModel; -import com.android.launcher3.LauncherSettings; import com.android.launcher3.LauncherSettings.Favorites; import com.android.launcher3.R; +import com.android.launcher3.Utilities; import com.android.launcher3.WorkspaceLayoutManager; import com.android.launcher3.config.FeatureFlags; import com.android.launcher3.folder.FolderIcon; import com.android.launcher3.icons.BaseIconFactory; import com.android.launcher3.icons.BitmapInfo; import com.android.launcher3.icons.LauncherIcons; -import com.android.launcher3.model.AllAppsList; import com.android.launcher3.model.BgDataModel; -import com.android.launcher3.model.BgDataModel.Callbacks; import com.android.launcher3.model.BgDataModel.FixedContainerItems; -import com.android.launcher3.model.LoaderResults; -import com.android.launcher3.model.LoaderTask; -import com.android.launcher3.model.ModelDelegate; import com.android.launcher3.model.WidgetItem; import com.android.launcher3.model.WidgetsModel; import com.android.launcher3.model.data.FolderInfo; @@ -100,13 +93,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.concurrent.Callable; import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.FutureTask; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; /** * Utility class for generating the preview of Launcher for a given InvariantDeviceProfile. @@ -120,8 +107,6 @@ import java.util.concurrent.TimeoutException; public class LauncherPreviewRenderer extends ContextWrapper implements ActivityContext, WorkspaceLayoutManager, LayoutInflater.Factory2 { - private static final String TAG = "LauncherPreviewRenderer"; - /** * Context used just for preview. It also provides a few objects (e.g. UserCache) just for * preview purposes. @@ -138,9 +123,15 @@ public class LauncherPreviewRenderer extends ContextWrapper private final ConcurrentLinkedQueue mIconPool = new ConcurrentLinkedQueue<>(); + private boolean mDestroyed = false; + public PreviewContext(Context base, InvariantDeviceProfile idp) { super(base); mIdp = idp; + mObjectMap.put(InvariantDeviceProfile.INSTANCE, idp); + mObjectMap.put(LauncherAppState.INSTANCE, + new LauncherAppState(this, null /* iconCacheFileName */)); + } @Override @@ -149,11 +140,9 @@ public class LauncherPreviewRenderer extends ContextWrapper } public void onDestroy() { - CustomWidgetManager customWidgetManager = (CustomWidgetManager) mObjectMap.get( - CustomWidgetManager.INSTANCE); - if (customWidgetManager != null) { - customWidgetManager.onDestroy(); - } + CustomWidgetManager.INSTANCE.get(this).onDestroy(); + LauncherAppState.INSTANCE.get(this).onTerminate(); + mDestroyed = true; } /** @@ -162,17 +151,12 @@ public class LauncherPreviewRenderer extends ContextWrapper */ public T getObject(MainThreadInitializedObject mainThreadInitializedObject, MainThreadInitializedObject.ObjectProvider provider) { + if (FeatureFlags.IS_STUDIO_BUILD && mDestroyed) { + throw new RuntimeException("Context already destroyed"); + } if (!mAllowedObjects.contains(mainThreadInitializedObject)) { throw new IllegalStateException("Leaking unknown objects"); } - if (mainThreadInitializedObject == LauncherAppState.INSTANCE) { - throw new IllegalStateException( - "Should not use MainThreadInitializedObject to initialize this with " - + "PreviewContext"); - } - if (mainThreadInitializedObject == InvariantDeviceProfile.INSTANCE) { - return (T) mIdp; - } if (mObjectMap.containsKey(mainThreadInitializedObject)) { return (T) mObjectMap.get(mainThreadInitializedObject); } @@ -210,7 +194,6 @@ public class LauncherPreviewRenderer extends ContextWrapper private final Context mContext; private final InvariantDeviceProfile mIdp; private final DeviceProfile mDp; - private final boolean mMigrated; private final Rect mInsets; private final WorkspaceItemInfo mWorkspaceItemInfo; private final LayoutInflater mHomeElementInflater; @@ -218,18 +201,26 @@ public class LauncherPreviewRenderer extends ContextWrapper private final Hotseat mHotseat; private final CellLayout mWorkspace; - public LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp, boolean migrated) { + public LauncherPreviewRenderer(Context context, InvariantDeviceProfile idp) { super(context); mUiHandler = new Handler(Looper.getMainLooper()); mContext = context; mIdp = idp; mDp = idp.getDeviceProfile(context).copy(context); - mMigrated = migrated; - // TODO: get correct insets once display cutout API is available. - mInsets = new Rect(); - mInsets.left = mInsets.right = (mDp.widthPx - mDp.availableWidthPx) / 2; - mInsets.top = mInsets.bottom = (mDp.heightPx - mDp.availableHeightPx) / 2; + if (Utilities.ATLEAST_R) { + WindowInsets currentWindowInsets = context.getSystemService(WindowManager.class) + .getCurrentWindowMetrics().getWindowInsets(); + mInsets = new Rect( + currentWindowInsets.getSystemWindowInsetLeft(), + currentWindowInsets.getSystemWindowInsetTop(), + currentWindowInsets.getSystemWindowInsetRight(), + currentWindowInsets.getSystemWindowInsetBottom()); + } else { + mInsets = new Rect(); + mInsets.left = mInsets.right = (mDp.widthPx - mDp.availableWidthPx) / 2; + mInsets.top = mInsets.bottom = (mDp.heightPx - mDp.availableHeightPx) / 2; + } mDp.updateInsets(mInsets); BaseIconFactory iconFactory = @@ -265,8 +256,9 @@ public class LauncherPreviewRenderer extends ContextWrapper } /** Populate preview and render it. */ - public View getRenderedView() { - populate(); + public View getRenderedView(BgDataModel dataModel, + Map widgetProviderInfoMap) { + populate(dataModel, widgetProviderInfoMap); return mRootView; } @@ -392,38 +384,17 @@ public class LauncherPreviewRenderer extends ContextWrapper } } - private void populate() { - WorkspaceFetcher fetcher; - PreviewContext previewContext = null; - if (mMigrated) { - previewContext = new PreviewContext(mContext, mIdp); - LauncherAppState appForPreview = new LauncherAppState( - previewContext, null /* iconCacheFileName */); - fetcher = new WorkspaceItemsInfoFromPreviewFetcher(appForPreview); - MODEL_EXECUTOR.execute(fetcher); - } else { - fetcher = new WorkspaceItemsInfoFetcher(); - LauncherAppState.getInstance(mContext).getModel().enqueueModelUpdateTask( - (LauncherModel.ModelUpdateTask) fetcher); - } - WorkspaceResult workspaceResult = fetcher.get(); - if (previewContext != null) { - previewContext.onDestroy(); - } - - if (workspaceResult == null) { - return; - } - + private void populate(BgDataModel dataModel, + Map widgetProviderInfoMap) { // Separate the items that are on the current screen, and the other remaining items. ArrayList currentWorkspaceItems = new ArrayList<>(); ArrayList otherWorkspaceItems = new ArrayList<>(); ArrayList currentAppWidgets = new ArrayList<>(); ArrayList otherAppWidgets = new ArrayList<>(); filterCurrentWorkspaceItems(0 /* currentScreenId */, - workspaceResult.mWorkspaceItems, currentWorkspaceItems, + dataModel.workspaceItems, currentWorkspaceItems, otherWorkspaceItems); - filterCurrentWorkspaceItems(0 /* currentScreenId */, workspaceResult.mAppWidgets, + filterCurrentWorkspaceItems(0 /* currentScreenId */, dataModel.appWidgets, currentAppWidgets, otherAppWidgets); sortWorkspaceItemsSpatially(mIdp, currentWorkspaceItems); for (ItemInfo itemInfo : currentWorkspaceItems) { @@ -444,12 +415,12 @@ public class LauncherPreviewRenderer extends ContextWrapper switch (itemInfo.itemType) { case Favorites.ITEM_TYPE_APPWIDGET: case Favorites.ITEM_TYPE_CUSTOM_APPWIDGET: - if (mMigrated) { - inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo, - workspaceResult.mWidgetProvidersMap); + if (widgetProviderInfoMap != null) { + inflateAndAddWidgets( + (LauncherAppWidgetInfo) itemInfo, widgetProviderInfoMap); } else { inflateAndAddWidgets((LauncherAppWidgetInfo) itemInfo, - workspaceResult.mWidgetsModel); + dataModel.widgetsModel); } break; default: @@ -458,8 +429,10 @@ public class LauncherPreviewRenderer extends ContextWrapper } IntArray ranks = getMissingHotseatRanks(currentWorkspaceItems, mDp.numShownHotseatIcons); - List predictions = workspaceResult.mHotseatPredictions == null - ? Collections.emptyList() : workspaceResult.mHotseatPredictions.items; + FixedContainerItems hotseatpredictions = + dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION); + List predictions = hotseatpredictions == null + ? Collections.emptyList() : hotseatpredictions.items; int count = Math.min(ranks.size(), predictions.size()); for (int i = 0; i < count; i++) { int rank = ranks.get(i); @@ -494,109 +467,4 @@ public class LauncherPreviewRenderer extends ContextWrapper view.measure(makeMeasureSpec(width, EXACTLY), makeMeasureSpec(height, EXACTLY)); view.layout(0, 0, width, height); } - - private static class WorkspaceItemsInfoFetcher implements LauncherModel.ModelUpdateTask, - WorkspaceFetcher { - - private final FutureTask mTask = new FutureTask<>(this); - - private LauncherAppState mApp; - private LauncherModel mModel; - private BgDataModel mBgDataModel; - private AllAppsList mAllAppsList; - - @Override - public void init(LauncherAppState app, LauncherModel model, BgDataModel dataModel, - AllAppsList allAppsList, Executor uiExecutor) { - mApp = app; - mModel = model; - mBgDataModel = dataModel; - mAllAppsList = allAppsList; - } - - @Override - public FutureTask getTask() { - return mTask; - } - - @Override - public void run() { - mTask.run(); - } - - @Override - public WorkspaceResult call() throws Exception { - if (!mModel.isModelLoaded()) { - Log.d(TAG, "Workspace not loaded, loading now"); - mModel.startLoaderForResults( - new LoaderResults(mApp, mBgDataModel, mAllAppsList, new Callbacks[0])); - return null; - } - - return new WorkspaceResult(mBgDataModel, mBgDataModel.widgetsModel, null); - } - } - - private static class WorkspaceItemsInfoFromPreviewFetcher extends LoaderTask implements - WorkspaceFetcher { - - private final FutureTask mTask = new FutureTask<>(this); - - WorkspaceItemsInfoFromPreviewFetcher(LauncherAppState app) { - super(app, null, new BgDataModel(), new ModelDelegate(), null); - } - - @Override - public FutureTask getTask() { - return mTask; - } - - @Override - public void run() { - mTask.run(); - } - - @Override - public WorkspaceResult call() { - List allShortcuts = new ArrayList<>(); - loadWorkspace(allShortcuts, LauncherSettings.Favorites.PREVIEW_CONTENT_URI, - LauncherSettings.Favorites.SCREEN + " = 0 or " - + LauncherSettings.Favorites.CONTAINER + " = " - + LauncherSettings.Favorites.CONTAINER_HOTSEAT); - return new WorkspaceResult(mBgDataModel, null, mWidgetProvidersMap); - } - } - - private interface WorkspaceFetcher extends Runnable, Callable { - FutureTask getTask(); - - default WorkspaceResult get() { - try { - return getTask().get(5, TimeUnit.SECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException e) { - Log.d(TAG, "Error fetching workspace items info", e); - return null; - } - } - } - - private static class WorkspaceResult { - private final ArrayList mWorkspaceItems; - private final ArrayList mAppWidgets; - private final FixedContainerItems mHotseatPredictions; - private final WidgetsModel mWidgetsModel; - private final Map mWidgetProvidersMap; - - private WorkspaceResult(BgDataModel dataModel, - WidgetsModel widgetsModel, - Map widgetProviderInfoMap) { - synchronized (dataModel) { - mWorkspaceItems = dataModel.workspaceItems; - mAppWidgets = dataModel.appWidgets; - mHotseatPredictions = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION); - mWidgetsModel = widgetsModel; - mWidgetProvidersMap = widgetProviderInfoMap; - } - } - } } diff --git a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java index 6193570e1f..a8c3d15bde 100644 --- a/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java +++ b/src/com/android/launcher3/graphics/PreviewSurfaceRenderer.java @@ -21,32 +21,49 @@ import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; import android.app.WallpaperColors; +import android.appwidget.AppWidgetProviderInfo; import android.content.Context; import android.hardware.display.DisplayManager; import android.os.Bundle; -import android.os.Handler; import android.os.IBinder; -import android.os.Looper; -import android.os.Message; -import android.os.Messenger; +import android.util.Log; import android.view.ContextThemeWrapper; import android.view.Display; import android.view.SurfaceControlViewHost; +import android.view.SurfaceControlViewHost.SurfacePackage; import android.view.View; +import android.view.WindowManager.LayoutParams; import android.view.animation.AccelerateDecelerateInterpolator; +import androidx.annotation.UiThread; +import androidx.annotation.WorkerThread; + import com.android.launcher3.InvariantDeviceProfile; +import com.android.launcher3.LauncherAppState; +import com.android.launcher3.LauncherSettings; import com.android.launcher3.R; +import com.android.launcher3.Utilities; +import com.android.launcher3.graphics.LauncherPreviewRenderer.PreviewContext; +import com.android.launcher3.model.BgDataModel; import com.android.launcher3.model.GridSizeMigrationTask; import com.android.launcher3.model.GridSizeMigrationTaskV2; +import com.android.launcher3.model.LoaderTask; +import com.android.launcher3.model.ModelDelegate; +import com.android.launcher3.model.ModelPreload; +import com.android.launcher3.util.ComponentKey; +import com.android.launcher3.util.RunnableList; import com.android.launcher3.util.Themes; import com.android.launcher3.widget.LocalColorExtractor; +import java.util.ArrayList; +import java.util.Map; import java.util.concurrent.TimeUnit; /** Render preview using surface view. */ @SuppressWarnings("NewApi") -public class PreviewSurfaceRenderer implements IBinder.DeathRecipient { +public class PreviewSurfaceRenderer { + + private static final String TAG = "PreviewSurfaceRenderer"; private static final int FADE_IN_ANIMATION_DURATION = 200; @@ -54,8 +71,6 @@ public class PreviewSurfaceRenderer implements IBinder.DeathRecipient { private static final String KEY_VIEW_WIDTH = "width"; private static final String KEY_VIEW_HEIGHT = "height"; private static final String KEY_DISPLAY_ID = "display_id"; - private static final String KEY_SURFACE_PACKAGE = "surface_package"; - private static final String KEY_CALLBACK = "callback"; private static final String KEY_COLORS = "wallpaper_colors"; private final Context mContext; @@ -65,10 +80,13 @@ public class PreviewSurfaceRenderer implements IBinder.DeathRecipient { private final int mHeight; private final Display mDisplay; private final WallpaperColors mWallpaperColors; + private final RunnableList mOnDestroyCallbacks = new RunnableList(); - private SurfaceControlViewHost mSurfaceControlViewHost; + private final SurfaceControlViewHost mSurfaceControlViewHost; - PreviewSurfaceRenderer(Context context, Bundle bundle) { + private boolean mDestroyed = false; + + public PreviewSurfaceRenderer(Context context, Bundle bundle) throws Exception { mContext = context; String gridName = bundle.getString("name"); @@ -77,106 +95,101 @@ public class PreviewSurfaceRenderer implements IBinder.DeathRecipient { gridName = InvariantDeviceProfile.getCurrentGridName(context); } mWallpaperColors = bundle.getParcelable(KEY_COLORS); - mIdp = new InvariantDeviceProfile(context, gridName); mHostToken = bundle.getBinder(KEY_HOST_TOKEN); mWidth = bundle.getInt(KEY_VIEW_WIDTH); mHeight = bundle.getInt(KEY_VIEW_HEIGHT); + mDisplay = context.getSystemService(DisplayManager.class) + .getDisplay(bundle.getInt(KEY_DISPLAY_ID)); - final DisplayManager displayManager = (DisplayManager) context.getSystemService( - Context.DISPLAY_SERVICE); - mDisplay = displayManager.getDisplay(bundle.getInt(KEY_DISPLAY_ID)); + mSurfaceControlViewHost = MAIN_EXECUTOR + .submit(() -> new SurfaceControlViewHost(mContext, mDisplay, mHostToken)) + .get(5, TimeUnit.SECONDS); + mOnDestroyCallbacks.add(mSurfaceControlViewHost::release); } - /** Handle a received surface view request. */ - Bundle render() { - if (mSurfaceControlViewHost != null) { - binderDied(); - } + public IBinder getHostToken() { + return mHostToken; + } - SurfaceControlViewHost.SurfacePackage surfacePackage; - try { - mSurfaceControlViewHost = MAIN_EXECUTOR - .submit(() -> new SurfaceControlViewHost(mContext, mDisplay, mHostToken)) - .get(5, TimeUnit.SECONDS); - surfacePackage = mSurfaceControlViewHost.getSurfacePackage(); - mHostToken.linkToDeath(this, 0); - } catch (Exception e) { - e.printStackTrace(); - return null; - } + public SurfacePackage getSurfacePackage() { + return mSurfaceControlViewHost.getSurfacePackage(); + } - MODEL_EXECUTOR.post(() -> { - final boolean success = doGridMigrationIfNecessary(); + /** + * Destroys the preview and all associated data + */ + @UiThread + public void destroy() { + mDestroyed = true; + mOnDestroyCallbacks.executeAllAndDestroy(); + } - final Context inflationContext; - if (mWallpaperColors != null) { - // Workaround to create a themed context - Context context = mContext.createDisplayContext(mDisplay); - LocalColorExtractor.newInstance(mContext) - .applyColorsOverride(context, mWallpaperColors); + /** + * Generates the preview in background + */ + public void loadAsync() { + MODEL_EXECUTOR.execute(this::loadModelData); + } - inflationContext = new ContextThemeWrapper(context, - Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints())); - } else { - inflationContext = new ContextThemeWrapper(mContext, R.style.AppTheme); + @WorkerThread + private void loadModelData() { + final boolean migrated = doGridMigrationIfNecessary(); + + final Context inflationContext; + if (mWallpaperColors != null) { + // Create a themed context, without affecting the main application context + Context context = mContext.createDisplayContext(mDisplay); + if (Utilities.ATLEAST_R) { + context = context.createWindowContext( + LayoutParams.TYPE_APPLICATION_OVERLAY, null); } - - MAIN_EXECUTOR.post(() -> { - // If mSurfaceControlViewHost is null due to any reason (e.g. binder died, - // happening when user leaves the preview screen before preview rendering finishes), - // we should return here. - SurfaceControlViewHost host = mSurfaceControlViewHost; - if (host == null) { - return; - } - - View view = new LauncherPreviewRenderer(inflationContext, mIdp, success) - .getRenderedView(); - // This aspect scales the view to fit in the surface and centers it - final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(), - mHeight / (float) view.getMeasuredHeight()); - view.setScaleX(scale); - view.setScaleY(scale); - view.setPivotX(0); - view.setPivotY(0); - view.setTranslationX((mWidth - scale * view.getWidth()) / 2); - view.setTranslationY((mHeight - scale * view.getHeight()) / 2); - view.setAlpha(0); - view.animate().alpha(1) - .setInterpolator(new AccelerateDecelerateInterpolator()) - .setDuration(FADE_IN_ANIMATION_DURATION) - .start(); - host.setView(view, view.getMeasuredWidth(), view.getMeasuredHeight()); - }); - }); - - Bundle result = new Bundle(); - result.putParcelable(KEY_SURFACE_PACKAGE, surfacePackage); - - Handler handler = new Handler(Looper.getMainLooper(), message -> { - binderDied(); - return true; - }); - Messenger messenger = new Messenger(handler); - Message msg = Message.obtain(); - msg.replyTo = messenger; - result.putParcelable(KEY_CALLBACK, msg); - return result; - } - - @Override - public void binderDied() { - if (mSurfaceControlViewHost != null) { - MAIN_EXECUTOR.execute(() -> { - mSurfaceControlViewHost.release(); - mSurfaceControlViewHost = null; - }); + LocalColorExtractor.newInstance(mContext) + .applyColorsOverride(context, mWallpaperColors); + inflationContext = new ContextThemeWrapper(context, + Themes.getActivityThemeRes(context, mWallpaperColors.getColorHints())); + } else { + inflationContext = new ContextThemeWrapper(mContext, R.style.AppTheme); + } + + if (migrated) { + PreviewContext previewContext = new PreviewContext(inflationContext, mIdp); + new LoaderTask( + LauncherAppState.getInstance(previewContext), + null, + new BgDataModel(), + new ModelDelegate(), null) { + + @Override + public void run() { + loadWorkspace(new ArrayList<>(), LauncherSettings.Favorites.PREVIEW_CONTENT_URI, + LauncherSettings.Favorites.SCREEN + " = 0 or " + + LauncherSettings.Favorites.CONTAINER + " = " + + LauncherSettings.Favorites.CONTAINER_HOTSEAT); + MAIN_EXECUTOR.execute(() -> { + renderView(previewContext, mBgDataModel, mWidgetProvidersMap); + mOnDestroyCallbacks.add(previewContext::onDestroy); + }); + } + }.run(); + } else { + new ModelPreload() { + + @Override + public void onComplete(boolean isSuccess) { + if (isSuccess) { + MAIN_EXECUTOR.execute(() -> + renderView(inflationContext, getBgDataModel(), null)); + } else { + Log.e(TAG, "Model loading failed"); + } + } + }.start(inflationContext); } - mHostToken.unlinkToDeath(this, 0); } + @WorkerThread private boolean doGridMigrationIfNecessary() { boolean needsToMigrate = MULTI_DB_GRID_MIRATION_ALGO.get() @@ -189,4 +202,29 @@ public class PreviewSurfaceRenderer implements IBinder.DeathRecipient { ? GridSizeMigrationTaskV2.migrateGridIfNeeded(mContext, mIdp) : GridSizeMigrationTask.migrateGridIfNeeded(mContext, mIdp); } + + @UiThread + private void renderView(Context inflationContext, BgDataModel dataModel, + Map widgetProviderInfoMap) { + if (mDestroyed) { + return; + } + View view = new LauncherPreviewRenderer(inflationContext, mIdp) + .getRenderedView(dataModel, widgetProviderInfoMap); + // This aspect scales the view to fit in the surface and centers it + final float scale = Math.min(mWidth / (float) view.getMeasuredWidth(), + mHeight / (float) view.getMeasuredHeight()); + view.setScaleX(scale); + view.setScaleY(scale); + view.setPivotX(0); + view.setPivotY(0); + view.setTranslationX((mWidth - scale * view.getWidth()) / 2); + view.setTranslationY((mHeight - scale * view.getHeight()) / 2); + view.setAlpha(0); + view.animate().alpha(1) + .setInterpolator(new AccelerateDecelerateInterpolator()) + .setDuration(FADE_IN_ANIMATION_DURATION) + .start(); + mSurfaceControlViewHost.setView(view, view.getMeasuredWidth(), view.getMeasuredHeight()); + } } diff --git a/src/com/android/launcher3/icons/IconCache.java b/src/com/android/launcher3/icons/IconCache.java index 297325a0ee..8e0a388784 100644 --- a/src/com/android/launcher3/icons/IconCache.java +++ b/src/com/android/launcher3/icons/IconCache.java @@ -130,6 +130,13 @@ public class IconCache extends BaseIconCache { } } + /** + * Closes the cache DB. This will clear any in-memory cache. + */ + public void close() { + mIconDb.close(); + } + /** * Fetches high-res icon for the provided ItemInfo and updates the caller when done. * diff --git a/src/com/android/launcher3/model/ModelPreload.java b/src/com/android/launcher3/model/ModelPreload.java index 713492b9fa..756b7da759 100644 --- a/src/com/android/launcher3/model/ModelPreload.java +++ b/src/com/android/launcher3/model/ModelPreload.java @@ -15,6 +15,8 @@ */ package com.android.launcher3.model; +import static com.android.launcher3.util.Executors.MODEL_EXECUTOR; + import android.content.Context; import android.util.Log; @@ -52,8 +54,14 @@ public class ModelPreload implements ModelUpdateTask { public final void run() { mModel.startLoaderForResultsIfNotLoaded( new LoaderResults(mApp, mBgDataModel, mAllAppsList, new Callbacks[0])); - Log.d(TAG, "Preload completed : " + mModel.isModelLoaded()); - onComplete(mModel.isModelLoaded()); + MODEL_EXECUTOR.post(() -> { + Log.d(TAG, "Preload completed : " + mModel.isModelLoaded()); + onComplete(mModel.isModelLoaded()); + }); + } + + public BgDataModel getBgDataModel() { + return mBgDataModel; } /** diff --git a/src/com/android/launcher3/model/data/ItemInfo.java b/src/com/android/launcher3/model/data/ItemInfo.java index e388965e91..3abcc2b826 100644 --- a/src/com/android/launcher3/model/data/ItemInfo.java +++ b/src/com/android/launcher3/model/data/ItemInfo.java @@ -68,6 +68,8 @@ public class ItemInfo { public static final boolean DEBUG = false; public static final int NO_ID = -1; + // An id that doesn't match any item, including predicted apps with have an id=NO_ID + public static final int NO_MATCHING_ID = Integer.MIN_VALUE; /** * The id in the settings database for this item diff --git a/src/com/android/launcher3/touch/ItemClickHandler.java b/src/com/android/launcher3/touch/ItemClickHandler.java index ce7dc07d6d..b5dcd3a041 100644 --- a/src/com/android/launcher3/touch/ItemClickHandler.java +++ b/src/com/android/launcher3/touch/ItemClickHandler.java @@ -27,6 +27,7 @@ import static com.android.launcher3.model.data.ItemInfoWithIcon.FLAG_DISABLED_SU import android.app.AlertDialog; import android.app.PendingIntent; +import android.content.Context; import android.content.Intent; import android.content.IntentSender; import android.content.pm.LauncherApps; @@ -193,6 +194,35 @@ public class ItemClickHandler { launcher.startActivitySafely(v, intent, item); } + /** + * Handles clicking on a disabled shortcut + */ + public static void handleDisabledItemClicked(WorkspaceItemInfo shortcut, Context context) { + final int disabledFlags = shortcut.runtimeStatusFlags + & WorkspaceItemInfo.FLAG_DISABLED_MASK; + if ((disabledFlags + & ~FLAG_DISABLED_SUSPENDED + & ~FLAG_DISABLED_QUIET_USER) == 0) { + // If the app is only disabled because of the above flags, launch activity anyway. + // Framework will tell the user why the app is suspended. + } else { + if (!TextUtils.isEmpty(shortcut.disabledMessage)) { + // Use a message specific to this shortcut, if it has one. + Toast.makeText(context, shortcut.disabledMessage, Toast.LENGTH_SHORT).show(); + return; + } + // Otherwise just use a generic error message. + int error = R.string.activity_not_available; + if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_SAFEMODE) != 0) { + error = R.string.safemode_shortcut_error; + } else if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_BY_PUBLISHER) != 0 + || (shortcut.runtimeStatusFlags & FLAG_DISABLED_LOCKED_USER) != 0) { + error = R.string.shortcut_not_available; + } + Toast.makeText(context, error, Toast.LENGTH_SHORT).show(); + } + } + /** * Event handler for an app shortcut click. * @@ -200,30 +230,8 @@ public class ItemClickHandler { */ public static void onClickAppShortcut(View v, WorkspaceItemInfo shortcut, Launcher launcher) { if (shortcut.isDisabled()) { - final int disabledFlags = shortcut.runtimeStatusFlags - & WorkspaceItemInfo.FLAG_DISABLED_MASK; - if ((disabledFlags & - ~FLAG_DISABLED_SUSPENDED & - ~FLAG_DISABLED_QUIET_USER) == 0) { - // If the app is only disabled because of the above flags, launch activity anyway. - // Framework will tell the user why the app is suspended. - } else { - if (!TextUtils.isEmpty(shortcut.disabledMessage)) { - // Use a message specific to this shortcut, if it has one. - Toast.makeText(launcher, shortcut.disabledMessage, Toast.LENGTH_SHORT).show(); - return; - } - // Otherwise just use a generic error message. - int error = R.string.activity_not_available; - if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_SAFEMODE) != 0) { - error = R.string.safemode_shortcut_error; - } else if ((shortcut.runtimeStatusFlags & FLAG_DISABLED_BY_PUBLISHER) != 0 || - (shortcut.runtimeStatusFlags & FLAG_DISABLED_LOCKED_USER) != 0) { - error = R.string.shortcut_not_available; - } - Toast.makeText(launcher, error, Toast.LENGTH_SHORT).show(); - return; - } + handleDisabledItemClicked(shortcut, launcher); + return; } // Check for abandoned promise diff --git a/src/com/android/launcher3/views/ScrimView.java b/src/com/android/launcher3/views/ScrimView.java index 0ba94ab240..fb1485b480 100644 --- a/src/com/android/launcher3/views/ScrimView.java +++ b/src/com/android/launcher3/views/ScrimView.java @@ -19,6 +19,7 @@ import static com.android.launcher3.util.SystemUiController.UI_STATE_SCRIM_VIEW; import android.content.Context; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.util.AttributeSet; @@ -39,6 +40,8 @@ public class ScrimView extends View implements Insettable { private SystemUiController mSystemUiController; private ScrimDrawingController mDrawingController; + private int mBackgroundColor; + private boolean mIsVisible = true; public ScrimView(Context context, AttributeSet attrs) { super(context, attrs); @@ -62,10 +65,21 @@ public class ScrimView extends View implements Insettable { @Override public void setBackgroundColor(int color) { + mBackgroundColor = color; updateSysUiColors(); super.setBackgroundColor(color); } + @Override + public void onVisibilityAggregated(boolean isVisible) { + super.onVisibilityAggregated(isVisible); + mIsVisible = isVisible; + } + + public boolean isFullyOpaque() { + return mIsVisible && getAlpha() == 1 && Color.alpha(mBackgroundColor) == 255; + } + @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); diff --git a/src/com/android/launcher3/views/TopRoundedCornerView.java b/src/com/android/launcher3/views/TopRoundedCornerView.java index 7888b08630..5519df1f49 100644 --- a/src/com/android/launcher3/views/TopRoundedCornerView.java +++ b/src/com/android/launcher3/views/TopRoundedCornerView.java @@ -21,7 +21,6 @@ import android.graphics.Paint; import android.graphics.Path; import android.graphics.RectF; import android.util.AttributeSet; -import android.widget.FrameLayout; import com.android.launcher3.R; import com.android.launcher3.util.Themes; @@ -41,7 +40,7 @@ public class TopRoundedCornerView extends SpringRelativeLayout { public TopRoundedCornerView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); - int radius = getResources().getDimensionPixelSize(R.dimen.bg_round_rect_radius); + float radius = Themes.getDialogCornerRadius(context); mRadii = new float[] {radius, radius, radius, radius, 0, 0, 0, 0}; mNavBarScrimPaint = new Paint(); diff --git a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java index 5deecd4c8a..8685aae737 100644 --- a/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java +++ b/src/com/android/launcher3/widget/LauncherAppWidgetHostView.java @@ -319,11 +319,15 @@ public class LauncherAppWidgetHostView extends NavigableAppWidgetHostView } mIsScrollable = checkScrollableRecursively(this); - if (!mIsInDragMode && getTag() instanceof LauncherAppWidgetInfo) { + mCurrentWidgetSize.left = left; + mCurrentWidgetSize.right = right; + mCurrentWidgetSize.top = top; + mCurrentWidgetSize.bottom = bottom; + LauncherAppWidgetInfo info = (LauncherAppWidgetInfo) getTag(); - getBoundsForViewInDragLayer(mLauncher.getDragLayer(), this, mCurrentWidgetSize, true, - mTmpFloatArray, mTempRectF); + getBoundsForViewInDragLayer(mLauncher.getDragLayer(), (View) getParent(), + mCurrentWidgetSize, true, mTmpFloatArray, mTempRectF); setRect(mTempRectF, mCurrentWidgetSize); updateColorExtraction(mCurrentWidgetSize, mWorkspace.getPageIndexForScreenId(info.screenId)); diff --git a/src/com/android/launcher3/widget/WidgetCell.java b/src/com/android/launcher3/widget/WidgetCell.java index 3fcd3f7b03..f7993dc408 100644 --- a/src/com/android/launcher3/widget/WidgetCell.java +++ b/src/com/android/launcher3/widget/WidgetCell.java @@ -33,7 +33,6 @@ import android.view.View.OnLayoutChangeListener; import android.view.ViewPropertyAnimator; import android.view.accessibility.AccessibilityNodeInfo; import android.widget.FrameLayout; -import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RemoteViews; import android.widget.TextView; @@ -45,7 +44,6 @@ import com.android.launcher3.CheckLongPressHelper; import com.android.launcher3.DeviceProfile; import com.android.launcher3.R; import com.android.launcher3.WidgetPreviewLoader; -import com.android.launcher3.icons.BaseIconFactory; import com.android.launcher3.icons.FastBitmapDrawable; import com.android.launcher3.icons.RoundDrawableWrapper; import com.android.launcher3.model.WidgetItem; @@ -80,7 +78,6 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { private FrameLayout mWidgetImageContainer; private WidgetImageView mWidgetImage; - private ImageView mWidgetBadge; private TextView mWidgetName; private TextView mWidgetDims; private TextView mWidgetDescription; @@ -136,7 +133,6 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { mWidgetImageContainer = findViewById(R.id.widget_preview_container); mWidgetImage = findViewById(R.id.widget_preview); - mWidgetBadge = findViewById(R.id.widget_badge); mWidgetName = findViewById(R.id.widget_name); mWidgetDims = findViewById(R.id.widget_dims); mWidgetDescription = findViewById(R.id.widget_description); @@ -161,7 +157,6 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { mWidgetImage.animate().cancel(); mWidgetImage.setDrawable(null); mWidgetImage.setVisibility(View.VISIBLE); - mWidgetBadge.setImageDrawable(null); mWidgetName.setText(null); mWidgetDims.setText(null); mWidgetDescription.setText(null); @@ -294,15 +289,6 @@ public class WidgetCell extends LinearLayout implements OnLayoutChangeListener { mAppWidgetHostViewPreview = null; } } - Drawable badge = mWidgetPreviewLoader.getBadgeForUser(mItem.user, - BaseIconFactory.getBadgeSizeForIconSize( - mActivity.getDeviceProfile().allAppsIconSizePx)); - if (badge == null) { - mWidgetBadge.setVisibility(View.GONE); - } else { - mWidgetBadge.setVisibility(View.VISIBLE); - mWidgetBadge.setImageDrawable(badge); - } if (mAnimatePreview) { mWidgetImageContainer.setAlpha(0f); ViewPropertyAnimator anim = mWidgetImageContainer.animate(); diff --git a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java index 34346ab672..664377999d 100644 --- a/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java +++ b/src/com/android/launcher3/widget/picker/SearchAndRecommendationsScrollController.java @@ -15,11 +15,13 @@ */ package com.android.launcher3.widget.picker; +import android.animation.ValueAnimator; import android.graphics.Point; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup.MarginLayoutParams; import android.widget.RelativeLayout; +import android.widget.TextView; import androidx.annotation.Nullable; @@ -32,14 +34,17 @@ import com.android.launcher3.workprofile.PersonalWorkPagedView; * vertical displacement upon scrolling. */ final class SearchAndRecommendationsScrollController implements - RecyclerViewFastScroller.OnFastScrollChangeListener { + RecyclerViewFastScroller.OnFastScrollChangeListener, ValueAnimator.AnimatorUpdateListener { private final boolean mHasWorkProfile; private final SearchAndRecommendationViewHolder mViewHolder; private final View mSearchAndRecommendationViewParent; private final WidgetsRecyclerView mPrimaryRecyclerView; private final WidgetsRecyclerView mSearchRecyclerView; + private final TextView mNoWidgetsView; private final int mTabsHeight; + private final ValueAnimator mAnimator = ValueAnimator.ofInt(0, 0); private final Point mTempOffset = new Point(); + private int mBottomInset; // The following are only non null if mHasWorkProfile is true. @Nullable private final WidgetsRecyclerView mWorkRecyclerView; @@ -47,8 +52,9 @@ final class SearchAndRecommendationsScrollController implements @Nullable private final PersonalWorkPagedView mPrimaryWorkViewPager; private WidgetsRecyclerView mCurrentRecyclerView; + private int mCurrentRecyclerViewScrollY = 0; - private OnContentChangeListener mOnContentChangeListener = () -> applyVerticalTransition(); + private OnContentChangeListener mOnContentChangeListener = () -> onScrollChanged(); /** * The vertical distance, in pixels, until the search is pinned at the top of the screen when @@ -78,7 +84,8 @@ final class SearchAndRecommendationsScrollController implements @Nullable WidgetsRecyclerView workRecyclerView, WidgetsRecyclerView searchRecyclerView, @Nullable View personalWorkTabsView, - @Nullable PersonalWorkPagedView primaryWorkViewPager) { + @Nullable PersonalWorkPagedView primaryWorkViewPager, + TextView noWidgetsView) { mHasWorkProfile = hasWorkProfile; mViewHolder = viewHolder; mViewHolder.mContainer.setSearchAndRecommendationScrollController(this); @@ -89,23 +96,35 @@ final class SearchAndRecommendationsScrollController implements mPrimaryWorkTabsView = personalWorkTabsView; mPrimaryWorkViewPager = primaryWorkViewPager; mTabsHeight = tabsHeight; - setCurrentRecyclerView(mPrimaryRecyclerView); + mNoWidgetsView = noWidgetsView; + setCurrentRecyclerView(mPrimaryRecyclerView, /* animateReset= */ false); + } + + public void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView) { + setCurrentRecyclerView(currentRecyclerView, /* animateReset= */ true); } /** Sets the current active {@link WidgetsRecyclerView}. */ - public void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView) { + private void setCurrentRecyclerView(WidgetsRecyclerView currentRecyclerView, + boolean animateReset) { + if (mCurrentRecyclerView == currentRecyclerView) { + return; + } if (mCurrentRecyclerView != null) { mCurrentRecyclerView.setOnContentChangeListener(null); } mCurrentRecyclerView = currentRecyclerView; mCurrentRecyclerView.setOnContentChangeListener(mOnContentChangeListener); - mViewHolder.mHeaderTitle.setTranslationY(0); - mViewHolder.mRecommendedWidgetsTable.setTranslationY(0); - mViewHolder.mSearchBarContainer.setTranslationY(0); + reset(animateReset); + } - if (mHasWorkProfile) { - mPrimaryWorkTabsView.setTranslationY(0); - } + /** + * Updates padding of {@link WidgetsFullSheet} contents to include {@code bottomInset} wherever + * necessary. + */ + public boolean updateBottomInset(int bottomInset) { + mBottomInset = bottomInset; + return updateMarginAndPadding(); } /** @@ -124,6 +143,8 @@ final class SearchAndRecommendationsScrollController implements + measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable); int topContainerHeight = measureHeightWithVerticalMargins(mViewHolder.mContainer); + int noWidgetsViewHeight = topContainerHeight - mBottomInset; + if (mHasWorkProfile) { mCollapsibleHeightForTabs = measureHeightWithVerticalMargins(mViewHolder.mHeaderTitle) + measureHeightWithVerticalMargins(mViewHolder.mRecommendedWidgetsTable); @@ -175,6 +196,10 @@ final class SearchAndRecommendationsScrollController implements int topOffsetAfterAllViewsCollapsed = topContainerHeight + mTabsHeight - mCollapsibleHeightForTabs; + if (mPrimaryWorkTabsView.getVisibility() == View.VISIBLE) { + noWidgetsViewHeight += mTabsHeight; + } + RelativeLayout.LayoutParams viewPagerLayoutParams = (RelativeLayout.LayoutParams) mPrimaryWorkViewPager.getLayoutParams(); if (viewPagerLayoutParams.topMargin != topOffsetAfterAllViewsCollapsed) { @@ -217,11 +242,25 @@ final class SearchAndRecommendationsScrollController implements mSearchRecyclerView.getPaddingBottom()); hasMarginOrPaddingUpdated = true; } + if (mNoWidgetsView.getPaddingTop() != noWidgetsViewHeight) { + mNoWidgetsView.setPadding( + mNoWidgetsView.getPaddingLeft(), + noWidgetsViewHeight, + mNoWidgetsView.getPaddingRight(), + mNoWidgetsView.getPaddingBottom()); + hasMarginOrPaddingUpdated = true; + } return hasMarginOrPaddingUpdated; } @Override public void onScrollChanged() { + int recyclerViewYOffset = mCurrentRecyclerView.getCurrentScrollY(); + if (recyclerViewYOffset < 0) return; + mCurrentRecyclerViewScrollY = recyclerViewYOffset; + if (mAnimator.isStarted()) { + mAnimator.cancel(); + } applyVerticalTransition(); } @@ -230,34 +269,43 @@ final class SearchAndRecommendationsScrollController implements * views (e.g. recycler views, tabs) upon scrolling / content changes in the recycler view. */ private void applyVerticalTransition() { - // Always use the recycler view offset because fast scroller offset has a different scale. - int recyclerViewYOffset = mCurrentRecyclerView.getCurrentScrollY(); - if (recyclerViewYOffset < 0) return; - if (mCollapsibleHeightForRecommendation > 0) { - int yDisplacement = Math.max(-recyclerViewYOffset, + int yDisplacement = Math.max(-mCurrentRecyclerViewScrollY, -mCollapsibleHeightForRecommendation); mViewHolder.mHeaderTitle.setTranslationY(yDisplacement); mViewHolder.mRecommendedWidgetsTable.setTranslationY(yDisplacement); } if (mCollapsibleHeightForSearch > 0) { - int searchYDisplacement = Math.max(-recyclerViewYOffset, -mCollapsibleHeightForSearch); + int searchYDisplacement = Math.max(-mCurrentRecyclerViewScrollY, + -mCollapsibleHeightForSearch); mViewHolder.mSearchBarContainer.setTranslationY(searchYDisplacement); } if (mHasWorkProfile && mCollapsibleHeightForTabs > 0) { - int yDisplacementForTabs = Math.max(-recyclerViewYOffset, -mCollapsibleHeightForTabs); + int yDisplacementForTabs = Math.max(-mCurrentRecyclerViewScrollY, + -mCollapsibleHeightForTabs); mPrimaryWorkTabsView.setTranslationY(yDisplacementForTabs); } } /** Resets any previous view translation. */ - public void reset() { - mViewHolder.mHeaderTitle.setTranslationY(0); - mViewHolder.mSearchBarContainer.setTranslationY(0); - if (mHasWorkProfile) { - mPrimaryWorkTabsView.setTranslationY(0); + public void reset(boolean animate) { + if (mCurrentRecyclerViewScrollY == 0) { + return; + } + if (mAnimator.isStarted()) { + mAnimator.cancel(); + } + + if (animate) { + mAnimator.setIntValues(mCurrentRecyclerViewScrollY, 0); + mAnimator.addUpdateListener(this); + mAnimator.setDuration(300); + mAnimator.start(); + } else { + mCurrentRecyclerViewScrollY = 0; + applyVerticalTransition(); } } @@ -308,6 +356,12 @@ final class SearchAndRecommendationsScrollController implements + marginLayoutParams.topMargin; } + @Override + public void onAnimationUpdate(ValueAnimator animation) { + mCurrentRecyclerViewScrollY = (Integer) animation.getAnimatedValue(); + applyVerticalTransition(); + } + /** * A listener to be notified when there is a content change in the recycler view that may affect * the relative position of the search and recommendation container. diff --git a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java index e44acc363b..039cad8291 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java +++ b/src/com/android/launcher3/widget/picker/WidgetsFullSheet.java @@ -61,7 +61,6 @@ import com.android.launcher3.widget.LauncherAppWidgetHost.ProviderChangedListene import com.android.launcher3.widget.model.WidgetsListBaseEntry; import com.android.launcher3.widget.picker.search.SearchModeListener; import com.android.launcher3.widget.picker.search.WidgetsSearchBar; -import com.android.launcher3.widget.picker.search.WidgetsSearchBarUIHelper; import com.android.launcher3.widget.util.WidgetsTableUtils; import com.android.launcher3.workprofile.PersonalWorkPagedView; import com.android.launcher3.workprofile.PersonalWorkSlidingTabStrip.OnActivePageChangedListener; @@ -76,8 +75,7 @@ import java.util.stream.IntStream; */ public class WidgetsFullSheet extends BaseWidgetSheet implements Insettable, ProviderChangedListener, OnActivePageChangedListener, - WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener, - WidgetsSearchBarUIHelper { + WidgetsRecyclerView.HeaderViewDimensionsProvider, SearchModeListener { private static final String TAG = WidgetsFullSheet.class.getSimpleName(); private static final long DEFAULT_OPEN_DURATION = 267; @@ -94,8 +92,8 @@ public class WidgetsFullSheet extends BaseWidgetSheet private final boolean mHasWorkProfile; private final SparseArray mAdapters = new SparseArray(); private final UserHandle mCurrentUser = Process.myUserHandle(); - private final Predicate mPrimaryWidgetsFilter = entry -> - mCurrentUser.equals(entry.mPkgItem.user); + private final Predicate mPrimaryWidgetsFilter = + entry -> mCurrentUser.equals(entry.mPkgItem.user); private final Predicate mWorkWidgetsFilter = mPrimaryWidgetsFilter.negate(); private final OnLayoutChangeListener mLayoutChangeListenerToShowTips = @@ -130,8 +128,10 @@ public class WidgetsFullSheet extends BaseWidgetSheet private final int mTabsHeight; private final int mWidgetCellHorizontalPadding; + @Nullable private WidgetsRecyclerView mCurrentWidgetsRecyclerView; @Nullable private PersonalWorkPagedView mViewPager; private boolean mIsInSearchMode; + private boolean mIsNoWidgetsViewNeeded; private int mMaxSpansPerRow = 4; private View mTabsView; private TextView mNoWidgetsView; @@ -188,6 +188,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet layoutInflater.inflate(R.layout.widgets_full_sheet_search_and_recommendations, springLayout, true); + mNoWidgetsView = findViewById(R.id.no_widgets_text); mSearchAndRecommendationViewHolder = new SearchAndRecommendationViewHolder( findViewById(R.id.search_and_recommendations_container)); mSearchAndRecommendationsScrollController = new SearchAndRecommendationsScrollController( @@ -198,10 +199,10 @@ public class WidgetsFullSheet extends BaseWidgetSheet mHasWorkProfile ? findViewById(R.id.work_widgets_list_view) : null, findViewById(R.id.search_widgets_list_view), mTabsView, - mViewPager); + mViewPager, + mNoWidgetsView); fastScroller.setOnFastScrollChangeListener(mSearchAndRecommendationsScrollController); - mNoWidgetsView = findViewById(R.id.no_widgets_text); onRecommendedWidgetsBound(); onWidgetsBound(); @@ -222,13 +223,18 @@ public class WidgetsFullSheet extends BaseWidgetSheet updateRecyclerViewVisibility(currentAdapterHolder); attachScrollbarToRecyclerView(currentRecyclerView); - resetExpandedHeaders(); } private void attachScrollbarToRecyclerView(WidgetsRecyclerView recyclerView) { recyclerView.bindFastScrollbar(); - mSearchAndRecommendationsScrollController.setCurrentRecyclerView(recyclerView); - reset(); + if (mCurrentWidgetsRecyclerView != recyclerView) { + // Only reset the scroll position & expanded apps if the currently shown recycler view + // has been updated. + reset(); + resetExpandedHeaders(); + mCurrentWidgetsRecyclerView = recyclerView; + mSearchAndRecommendationsScrollController.setCurrentRecyclerView(recyclerView); + } } private void updateRecyclerViewVisibility(AdapterHolder adapterHolder) { @@ -248,7 +254,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView.scrollToTop(); } mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView.scrollToTop(); - mSearchAndRecommendationsScrollController.reset(); + mSearchAndRecommendationsScrollController.reset(/* animate= */ true); } @VisibleForTesting @@ -291,6 +297,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet if (mHasWorkProfile) { setBottomPadding(mAdapters.get(AdapterHolder.WORK).mWidgetsRecyclerView, insets.bottom); } + mSearchAndRecommendationsScrollController.updateBottomInset(insets.bottom); if (insets.bottom > 0) { setupNavBarColor(); } else { @@ -406,6 +413,16 @@ public class WidgetsFullSheet extends BaseWidgetSheet } else { updateRecyclerViewVisibility(primaryUserAdapterHolder); } + // Update recommended widgets section so that it occupies appropriate space on screen to + // leave enough space for presence/absence of mNoWidgetsView. + boolean isNoWidgetsViewNeeded = + mAdapters.get(AdapterHolder.PRIMARY).mWidgetsListAdapter.getItemCount() == 0 + || (mHasWorkProfile && mAdapters.get(AdapterHolder.WORK) + .mWidgetsListAdapter.getItemCount() == 0); + if (mIsNoWidgetsViewNeeded != isNoWidgetsViewNeeded) { + mIsNoWidgetsViewNeeded = isNoWidgetsViewNeeded; + onRecommendedWidgetsBound(); + } } @Override @@ -413,7 +430,6 @@ public class WidgetsFullSheet extends BaseWidgetSheet if (mIsInSearchMode) return; setViewVisibilityBasedOnSearch(/*isInSearchMode= */ true); attachScrollbarToRecyclerView(mAdapters.get(AdapterHolder.SEARCH).mWidgetsRecyclerView); - resetExpandedHeaders(); } @Override @@ -473,9 +489,19 @@ public class WidgetsFullSheet extends BaseWidgetSheet WidgetsRecommendationTableLayout table = mSearchAndRecommendationViewHolder.mRecommendedWidgetsTable; if (recommendedWidgets.size() > 0) { - float maxTableHeight = - (mActivityContext.getDeviceProfile().availableHeightPx - mTabsHeight - - getHeaderViewHeight()) * RECOMMENDATION_TABLE_HEIGHT_RATIO; + float noWidgetsViewHeight = 0; + if (mIsNoWidgetsViewNeeded) { + // Make sure recommended section leaves enough space for noWidgetsView. + Rect noWidgetsViewTextBounds = new Rect(); + mNoWidgetsView.getPaint() + .getTextBounds(mNoWidgetsView.getText().toString(), /* start= */ 0, + mNoWidgetsView.getText().length(), noWidgetsViewTextBounds); + noWidgetsViewHeight = noWidgetsViewTextBounds.height(); + } + float maxTableHeight = (mActivityContext.getDeviceProfile().availableHeightPx + - mTabsHeight - getHeaderViewHeight() - noWidgetsViewHeight) + * RECOMMENDATION_TABLE_HEIGHT_RATIO; + List> recommendedWidgetsInTable = WidgetsTableUtils.groupWidgetItemsIntoTable(recommendedWidgets, mMaxSpansPerRow); @@ -535,6 +561,12 @@ public class WidgetsFullSheet extends BaseWidgetSheet } else if (getPopupContainer().isEventOverView(mContent, ev)) { mNoIntercept = !getRecyclerView().shouldContainerScroll(ev, getPopupContainer()); } + + if (mSearchAndRecommendationViewHolder.mSearchBar.isSearchBarFocused() + && !getPopupContainer().isEventOverView( + mSearchAndRecommendationViewHolder.mSearchBarContainer, ev)) { + mSearchAndRecommendationViewHolder.mSearchBar.clearSearchBarFocus(); + } } return super.onControllerInterceptTouchEvent(ev); } @@ -609,11 +641,6 @@ public class WidgetsFullSheet extends BaseWidgetSheet getWindowInsetsController().hide(WindowInsets.Type.ime()); } - @Override - public void clearSearchBarFocus() { - mSearchAndRecommendationViewHolder.mSearchBar.clearSearchBarFocus(); - } - private void showEducationTipOnView(View view) { mActivityContext.getSharedPrefs().edit() .putBoolean(WIDGETS_EDUCATION_TIP_SEEN, true).apply(); @@ -681,9 +708,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet apps.getWidgetCache(), apps.getIconCache(), /* iconClickListener= */ WidgetsFullSheet.this, - /* iconLongClickListener= */ WidgetsFullSheet.this, - /* WidgetsSearchBarUIHelper= */ - mAdapterType == SEARCH ? WidgetsFullSheet.this : null); + /* iconLongClickListener= */ WidgetsFullSheet.this); mWidgetsListAdapter.setHasStableIds(true); switch (mAdapterType) { case PRIMARY: diff --git a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java index 0504e74695..7963431932 100644 --- a/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java +++ b/src/com/android/launcher3/widget/picker/WidgetsListAdapter.java @@ -33,6 +33,7 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder; import com.android.launcher3.R; import com.android.launcher3.WidgetPreviewLoader; import com.android.launcher3.icons.IconCache; +import com.android.launcher3.model.data.PackageItemInfo; import com.android.launcher3.recyclerview.ViewHolderBinder; import com.android.launcher3.util.LabelComparator; import com.android.launcher3.util.PackageUserKey; @@ -41,12 +42,12 @@ import com.android.launcher3.widget.model.WidgetsListBaseEntry; import com.android.launcher3.widget.model.WidgetsListContentEntry; import com.android.launcher3.widget.model.WidgetsListHeaderEntry; import com.android.launcher3.widget.model.WidgetsListSearchHeaderEntry; -import com.android.launcher3.widget.picker.search.WidgetsSearchBarUIHelper; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -70,7 +71,6 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC private static final int VIEW_TYPE_WIDGETS_HEADER = R.id.view_type_widgets_header; private static final int VIEW_TYPE_WIDGETS_SEARCH_HEADER = R.id.view_type_widgets_search_header; - @Nullable private final WidgetsSearchBarUIHelper mSearchBarUIHelper; private final WidgetsDiffReporter mDiffReporter; private final SparseArray mViewHolderBinders = new SparseArray<>(); private final WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder; @@ -90,9 +90,7 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC public WidgetsListAdapter(Context context, LayoutInflater layoutInflater, WidgetPreviewLoader widgetPreviewLoader, IconCache iconCache, - OnClickListener iconClickListener, OnLongClickListener iconLongClickListener, - @Nullable WidgetsSearchBarUIHelper searchBarUIHelper) { - mSearchBarUIHelper = searchBarUIHelper; + OnClickListener iconClickListener, OnLongClickListener iconLongClickListener) { mDiffReporter = new WidgetsDiffReporter(iconCache, this); mWidgetsListTableViewHolderBinder = new WidgetsListTableViewHolderBinder(context, layoutInflater, iconClickListener, iconLongClickListener, @@ -153,6 +151,9 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC public void setWidgets(List tempEntries) { mAllEntries = tempEntries.stream().sorted(mRowComparator) .collect(Collectors.toList()); + if (shouldClearVisibleEntries()) { + mVisibleEntries.clear(); + } updateVisibleEntries(); } @@ -243,9 +244,6 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC @Override public void onHeaderClicked(boolean showWidgets, PackageUserKey packageUserKey) { - if (mSearchBarUIHelper != null) { - mSearchBarUIHelper.clearSearchBarFocus(); - } if (showWidgets) { mWidgetsContentVisiblePackageUserKey = packageUserKey; updateVisibleEntries(); @@ -272,6 +270,30 @@ public class WidgetsListAdapter extends Adapter implements OnHeaderC mWidgetsListTableViewHolderBinder.setMaxSpansPerRow(maxHorizontalSpans); } + /** + * Returns {@code true} if there is a change in {@link #mAllEntries} that results in an + * invalidation of {@link #mVisibleEntries}. e.g. there is change in the device language. + */ + private boolean shouldClearVisibleEntries() { + Map packagesInfo = + mAllEntries.stream() + .filter(entry -> entry instanceof WidgetsListHeaderEntry) + .map(entry -> entry.mPkgItem) + .collect(Collectors.toMap( + entry -> new PackageUserKey(entry.packageName, entry.user), + entry -> entry)); + for (WidgetsListBaseEntry visibleEntry: mVisibleEntries) { + PackageUserKey key = new PackageUserKey(visibleEntry.mPkgItem.packageName, + visibleEntry.mPkgItem.user); + PackageItemInfo packageItemInfo = packagesInfo.get(key); + if (packageItemInfo != null + && !visibleEntry.mPkgItem.title.equals(packageItemInfo.title)) { + return true; + } + } + return false; + } + /** Comparator for sorting WidgetListRowEntry based on package title. */ public static class WidgetListBaseRowEntryComparator implements Comparator { diff --git a/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java index 42f1bb2d21..65937b644a 100644 --- a/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java +++ b/src/com/android/launcher3/widget/picker/search/LauncherWidgetsSearchBar.java @@ -75,6 +75,11 @@ public class LauncherWidgetsSearchBar extends LinearLayout implements WidgetsSea mController.onDestroy(); } + @Override + public boolean isSearchBarFocused() { + return mEditText.isFocused(); + } + @Override public void clearSearchBarFocus() { mController.clearFocus(); diff --git a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java index 0ac47ce5f2..44a5e80f5e 100644 --- a/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java +++ b/src/com/android/launcher3/widget/picker/search/WidgetsSearchBar.java @@ -32,6 +32,9 @@ public interface WidgetsSearchBar { */ void reset(); + /** Returns {@code true} if the search bar is in focus. */ + boolean isSearchBarFocused(); + /** * Clears focus from search bar. */ diff --git a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java index 57f40db205..4cf52f071d 100644 --- a/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java +++ b/tests/tapl/com/android/launcher3/tapl/LauncherInstrumentation.java @@ -496,13 +496,14 @@ public final class LauncherInstrumentation { private void fail(String message) { checkForAnomaly(); Assert.fail(formatSystemHealthMessage(formatErrorWithEvents( - "http://go/tapl test failure:\nOverview: " + getContextDescription() + "http://go/tapl test failure:\nSummary: " + getContextDescription() + " - visible state is " + getVisibleStateMessage() + ";\nDetails: " + message, true))); } private String getContextDescription() { - return mDiagnosticContext.isEmpty() ? "" : String.join(", ", mDiagnosticContext); + return mDiagnosticContext.isEmpty() + ? "(no context)" : String.join(", ", mDiagnosticContext); } void assertTrue(String message, boolean condition) {