Snap for 7389169 from 576722164a to sc-release

Change-Id: I0c39cfe8dcf02ea20bd24ec6383fefd4a9636a30
This commit is contained in:
android-build-team Robot
2021-05-22 03:08:12 +00:00
69 changed files with 1750 additions and 1681 deletions
+9 -7
View File
@@ -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",
},
@@ -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) {
+22 -2
View File
@@ -22,9 +22,29 @@
<com.android.launcher3.taskbar.TaskbarView
android:id="@+id/taskbar_view"
android:layout_width="wrap_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"/>
android:gravity="center"
android:forceHasOverlappingRendering="false"
android:layout_gravity="bottom" >
<LinearLayout
android:id="@+id/system_button_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="@dimen/taskbar_nav_buttons_spacing"
android:paddingRight="@dimen/taskbar_nav_buttons_spacing"
android:forceHasOverlappingRendering="false"
android:gravity="center" />
<LinearLayout
android:id="@+id/hotseat_icons_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:forceHasOverlappingRendering="false"
android:gravity="center" />
</com.android.launcher3.taskbar.TaskbarView>
<com.android.launcher3.taskbar.ImeBarView
android:id="@+id/ime_bar_view"
-26
View File
@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2021 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<com.android.launcher3.taskbar.TaskbarView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/taskbar_view"
android:layout_width="match_parent"
android:layout_height="@dimen/taskbar_size"
android:background="@android:color/transparent"
android:layout_gravity="bottom"
android:gravity="center"
android:visibility="gone" />
+1
View File
@@ -149,4 +149,5 @@
<!-- Note that this applies to both sides of all icons, so visible space is double this. -->
<dimen name="taskbar_icon_spacing">8dp</dimen>
<dimen name="taskbar_folder_margin">16dp</dimen>
<dimen name="taskbar_nav_buttons_spacing">16dp</dimen>
</resources>
@@ -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 extends OverviewActionsView> 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)));
@@ -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);
@@ -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();
}
}
@@ -217,17 +217,11 @@ public class DepthController implements StateHandler<LauncherState>,
}
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();
}
}
@@ -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;
}
@@ -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);
}
@@ -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);
}
}
@@ -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<TaskbarActivityContext> 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<TaskbarActivityContext> {
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) {
@@ -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;
}
@@ -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<TaskbarActivityContext> {
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<TaskbarActivityContext>
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<TaskbarActivityContext>
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<TaskbarActivityContext>
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
cleanup();
onDestroy();
}
@Override
@@ -143,10 +108,25 @@ public class TaskbarContainerView extends BaseDragLayer<TaskbarActivityContext>
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<TaskbarActivityContext>
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.
@@ -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);
}
}
@@ -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
@@ -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<ItemInfo[]> 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<ItemInfo[]> 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);
}
}
@@ -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);
}
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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<LauncherState> {
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);
}
}
@@ -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);
}
}
@@ -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 extends Context & ActivityContext> T getActivityContext() {
return ActivityContext.lookupContext(getContext());
public void setIconsVisibility(boolean isVisible) {
mHotseatIconsContainer.setVisibility(isVisible ? VISIBLE : INVISIBLE);
}
public boolean areIconsVisible() {
return mHotseatIconsContainer.getVisibility() == VISIBLE;
}
}
@@ -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<WorkspaceItemInfo> 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();
}
}
@@ -750,6 +750,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
setIsLikelyToStartNewTask(isLikelyToStartNewTask, false /* animate */);
mStateCallback.setStateOnUiThread(STATE_GESTURE_STARTED);
mGestureStarted = true;
SystemUiProxy.INSTANCE.get(mContext).notifySwipeUpGestureStarted();
}
/**
@@ -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<STATE_TYPE extends BaseState<STATE_T
return null;
}
@Nullable
public TaskbarController getTaskbarController() {
return null;
}
public final boolean isResumed() {
ACTIVITY_TYPE activity = getCreatedActivity();
return activity != null && activity.hasBeenResumed();
@@ -367,13 +361,6 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
*/
protected abstract int getOverviewScrimColorForState(ACTIVITY_TYPE activity, STATE_TYPE state);
/**
* See {@link com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags}
* @param systemUiStateFlags The latest SystemUiStateFlags
*/
public void onSystemUiFlagsChanged(int systemUiStateFlags) {
}
/**
* Returns the expected STATE_TYPE from the provided GestureEndTarget.
*/
@@ -22,7 +22,6 @@ import static com.android.launcher3.LauncherState.QUICK_SWITCH;
import static com.android.launcher3.anim.AnimatorListeners.forEndCallback;
import static com.android.launcher3.anim.Interpolators.LINEAR;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -42,7 +41,7 @@ import com.android.launcher3.anim.PendingAnimation;
import com.android.launcher3.statehandlers.DepthController;
import com.android.launcher3.statehandlers.DepthController.ClampedDepthProperty;
import com.android.launcher3.statemanager.StateManager;
import com.android.launcher3.taskbar.TaskbarController;
import com.android.launcher3.taskbar.LauncherTaskbarUIController;
import com.android.launcher3.touch.PagedOrientationHandler;
import com.android.quickstep.GestureState.GestureEndTarget;
import com.android.quickstep.SysUINavigationMode.Mode;
@@ -164,13 +163,12 @@ public final class LauncherActivityInterface extends
}
@Nullable
@Override
public TaskbarController getTaskbarController() {
private LauncherTaskbarUIController getTaskbarController() {
BaseQuickstepLauncher launcher = getCreatedActivity();
if (launcher == null) {
return null;
}
return launcher.getTaskbarController();
return launcher.getTaskbarUIController();
}
@Nullable
@@ -278,13 +276,13 @@ public final class LauncherActivityInterface extends
@Override
public @Nullable Animator getParallelAnimationToLauncher(GestureEndTarget endTarget,
long duration) {
TaskbarController taskbarController = getTaskbarController();
LauncherTaskbarUIController uiController = getTaskbarController();
Animator superAnimator = super.getParallelAnimationToLauncher(endTarget, duration);
if (taskbarController == null) {
if (uiController == null) {
return superAnimator;
}
LauncherState toState = stateFromGestureEndTarget(endTarget);
Animator taskbarAnimator = taskbarController.createAnimToLauncher(toState, duration);
Animator taskbarAnimator = uiController.createAnimToLauncher(toState, duration);
if (superAnimator == null) {
return taskbarAnimator;
} else {
@@ -300,32 +298,22 @@ public final class LauncherActivityInterface extends
return state.getWorkspaceScrimColor(launcher);
}
@Override
public void onSystemUiFlagsChanged(int systemUiStateFlags) {
TaskbarController taskbarController = getTaskbarController();
if (taskbarController == null) {
return;
}
boolean isImeVisible = (systemUiStateFlags & SYSUI_STATE_IME_SHOWING) != 0;
taskbarController.setIsImeVisible(isImeVisible);
}
@Override
public boolean deferStartingActivity(RecentsAnimationDeviceState deviceState, MotionEvent ev) {
TaskbarController taskbarController = getTaskbarController();
if (taskbarController == null) {
LauncherTaskbarUIController uiController = getTaskbarController();
if (uiController == null) {
return super.deferStartingActivity(deviceState, ev);
}
return taskbarController.isEventOverAnyTaskbarItem(ev);
return uiController.isEventOverAnyTaskbarItem(ev);
}
@Override
public boolean shouldCancelCurrentGesture() {
TaskbarController taskbarController = getTaskbarController();
if (taskbarController == null) {
LauncherTaskbarUIController uiController = getTaskbarController();
if (uiController == null) {
return super.shouldCancelCurrentGesture();
}
return taskbarController.isDraggingItem();
return uiController.isDraggingItem();
}
@Override
@@ -21,6 +21,7 @@ import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.Utilities.boundToRange;
import static com.android.launcher3.Utilities.dpToPx;
import static com.android.launcher3.config.FeatureFlags.PROTOTYPE_APP_CLOSE;
import static com.android.launcher3.model.data.ItemInfo.NO_MATCHING_ID;
import static com.android.launcher3.views.FloatingIconView.SHAPE_PROGRESS_DURATION;
import static com.android.launcher3.views.FloatingIconView.getFloatingIconView;
@@ -234,7 +235,7 @@ public class LauncherSwipeHandlerV2 extends
// Find the associated item info for the launch cookie (if available), note that predicted
// apps actually have an id of -1, so use another default id here
int launchCookieItemId = -2;
int launchCookieItemId = NO_MATCHING_ID;
for (IBinder cookie : launchCookies) {
Integer itemId = ObjectWrapper.unwrap(cookie);
if (itemId != null) {
@@ -369,7 +369,7 @@ public class RotationTouchHelper implements
private void notifySysuiOfCurrentRotation(int rotation) {
UI_HELPER_EXECUTOR.execute(() -> SystemUiProxy.INSTANCE.get(mContext)
.onQuickSwitchToNewTask(rotation));
.notifyPrioritizedRotation(rotation));
}
/**
@@ -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);
}
}
}
@@ -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<O
private OverscrollPlugin mOverscrollPlugin;
/**
* Extension of OverviewProxy aidl interface without needing to modify the actual interface.
* This is for methods that need only need local access and not intended to make IPC calls.
* Local IOverviewProxy implementation with some methods for local components
*/
public abstract static class TISBinder extends IOverviewProxy.Stub {
public abstract void setTaskbarOverviewProxyDelegate(
@Nullable TaskbarOverviewProxyDelegate i);
}
private final TISBinder mMyBinder = new TISBinder() {
public void setTaskbarOverviewProxyDelegate(
@Nullable TaskbarOverviewProxyDelegate delegate) {
mTaskbarOverviewProxyDelegate = delegate;
}
public class TISBinder extends IOverviewProxy.Stub {
@BinderThread
public void onInitialize(Bundle bundle) {
@@ -274,40 +262,24 @@ public class TouchInteractionService extends Service implements PluginListener<O
@Override
public void onImeWindowStatusChanged(int displayId, IBinder token, int vis,
int backDisposition, boolean showImeSwitcher) throws RemoteException {
if (mTaskbarOverviewProxyDelegate == null) {
return;
}
MAIN_EXECUTOR.execute(() -> {
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<O
private DisplayManager mDisplayManager;
public TouchInteractionService() {
sInstance = this;
}
private TaskbarManager mTaskbarManager;
@Override
public void onCreate() {
@@ -348,13 +318,15 @@ public class TouchInteractionService extends Service implements PluginListener<O
mMainChoreographer = Choreographer.getInstance();
mAM = ActivityManagerWrapper.getInstance();
mDeviceState = new RecentsAnimationDeviceState(this, true);
mDisplayManager = getSystemService(DisplayManager.class);
mTaskbarManager = new TaskbarManager(this);
mRotationTouchHelper = mDeviceState.getRotationTouchHelper();
mDeviceState.addNavigationModeChangedCallback(this::onNavigationModeChanged);
mDeviceState.addOneHandedModeChangedCallback(this::onOneHandedModeOverlayChanged);
mDeviceState.runOnUserUnlocked(this::onUserUnlocked);
mDeviceState.runOnUserUnlocked(mTaskbarManager::onUserUnlocked);
ProtoTracer.INSTANCE.get(this).add(this);
mDisplayManager = getSystemService(DisplayManager.class);
sConnected = true;
}
@@ -468,8 +440,7 @@ public class TouchInteractionService extends Service implements PluginListener<O
int systemUiStateFlags = mDeviceState.getSystemUiStateFlags();
SystemUiProxy.INSTANCE.get(this).setLastSystemUiStateFlags(systemUiStateFlags);
mOverviewComponentObserver.onSystemUiStateChanged();
mOverviewComponentObserver.getActivityInterface().onSystemUiFlagsChanged(
systemUiStateFlags);
mTaskbarManager.onSystemUiFlagsChanged(systemUiStateFlags);
if ((lastSysUIFlags & SYSUI_STATE_TRACING_ENABLED) !=
(systemUiStateFlags & SYSUI_STATE_TRACING_ENABLED)) {
@@ -512,6 +483,7 @@ public class TouchInteractionService extends Service implements PluginListener<O
getSystemService(AccessibilityManager.class)
.unregisterSystemAction(SYSTEM_ACTION_ID_ALL_APPS);
mTaskbarManager.destroy();
sConnected = false;
super.onDestroy();
}
@@ -519,7 +491,7 @@ public class TouchInteractionService extends Service implements PluginListener<O
@Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "Touch service connected: user=" + getUserId());
return mMyBinder;
return new TISBinder();
}
private void onInputEvent(InputEvent ev) {
@@ -116,7 +116,7 @@ public class StaggeredWorkspaceAnim {
addStaggeredAnimationForView(child, lp.cellY + 1, totalRows);
}
} else {
final int hotseatRow, qsbRow, taskbarRow;
final int hotseatRow, qsbRow;
if (grid.isTaskbarPresent) {
qsbRow = grid.inv.numRows + 1;
hotseatRow = grid.inv.numRows + 2;
@@ -124,16 +124,12 @@ public class StaggeredWorkspaceAnim {
hotseatRow = grid.inv.numRows + 1;
qsbRow = grid.inv.numRows + 2;
}
// Taskbar and hotseat overlap.
taskbarRow = hotseatRow;
for (int i = hotseatIcons.getChildCount() - 1; i >= 0; i--) {
View child = hotseatIcons.getChildAt(i);
addStaggeredAnimationForView(child, hotseatRow, totalRows);
}
addStaggeredAnimationForView(hotseat.getQsb(), qsbRow, totalRows);
addStaggeredAnimationForView(hotseat.getTaskbarView(), taskbarRow, totalRows);
}
mAnimators.addListener(new AnimatorListenerAdapter() {
@@ -14,8 +14,13 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<Space
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone" />
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/widgets_picker_surface" />
<corners
android:topLeftRadius="?android:attr/dialogCornerRadius"
android:topRightRadius="?android:attr/dialogCornerRadius"
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp"
/>
</shape>
@@ -18,8 +18,8 @@
android:shape="rectangle">
<solid android:color="@color/widgets_picker_surface" />
<corners
android:topLeftRadius="@dimen/bg_round_rect_radius"
android:topRightRadius="@dimen/bg_round_rect_radius"
android:topLeftRadius="@dimen/default_dialog_corner_radius"
android:topRightRadius="@dimen/default_dialog_corner_radius"
android:bottomLeftRadius="0dp"
android:bottomRightRadius="0dp"
/>
-8
View File
@@ -33,14 +33,6 @@
android:layout_height="match_parent"
android:importantForAccessibility="no"
android:layout_gravity="fill"/>
<ImageView
android:id="@+id/widget_badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
android:layout_gravity="end|bottom"
android:layout_margin="@dimen/profile_badge_margin"/>
</com.android.launcher3.widget.WidgetCellPreview>
<!-- The name of the widget. -->
@@ -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
@@ -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))
@@ -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);
}
@@ -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);
}
@@ -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),
@@ -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<TestActivity> 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<WidgetsListBaseEntry> 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);
}
@@ -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,
+5 -2
View File
@@ -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);
+1 -20
View File
@@ -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;
}
}
-14
View File
@@ -1942,13 +1942,6 @@ public class Launcher extends StatefulActivity<LauncherState> 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<LauncherState> 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;
}
@@ -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) {
+6 -4
View File
@@ -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);
}
}
@@ -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
+16 -32
View File
@@ -2953,10 +2953,11 @@ public class Workspace extends PagedView<WorkspacePageIndicator>
};
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<WorkspacePageIndicator>
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<WorkspacePageIndicator>
}
/**
* 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<CellLayout> 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<WorkspacePageIndicator>
*/
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<WorkspacePageIndicator>
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<WorkspaceItemInfo> shortcuts) {
@@ -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();
}
}
});
}
@@ -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()));
@@ -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<IBinder, PreviewLifecycleObserver> 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);
}
}
}
@@ -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<LauncherIconsForPreview> 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> T getObject(MainThreadInitializedObject<T> mainThreadInitializedObject,
MainThreadInitializedObject.ObjectProvider<T> 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<ComponentKey, AppWidgetProviderInfo> 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<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
// Separate the items that are on the current screen, and the other remaining items.
ArrayList<ItemInfo> currentWorkspaceItems = new ArrayList<>();
ArrayList<ItemInfo> otherWorkspaceItems = new ArrayList<>();
ArrayList<LauncherAppWidgetInfo> currentAppWidgets = new ArrayList<>();
ArrayList<LauncherAppWidgetInfo> 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<ItemInfo> predictions = workspaceResult.mHotseatPredictions == null
? Collections.emptyList() : workspaceResult.mHotseatPredictions.items;
FixedContainerItems hotseatpredictions =
dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
List<ItemInfo> 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<WorkspaceResult> 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<WorkspaceResult> 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<WorkspaceResult> mTask = new FutureTask<>(this);
WorkspaceItemsInfoFromPreviewFetcher(LauncherAppState app) {
super(app, null, new BgDataModel(), new ModelDelegate(), null);
}
@Override
public FutureTask<WorkspaceResult> getTask() {
return mTask;
}
@Override
public void run() {
mTask.run();
}
@Override
public WorkspaceResult call() {
List<ShortcutInfo> 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<WorkspaceResult> {
FutureTask<WorkspaceResult> 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<ItemInfo> mWorkspaceItems;
private final ArrayList<LauncherAppWidgetInfo> mAppWidgets;
private final FixedContainerItems mHotseatPredictions;
private final WidgetsModel mWidgetsModel;
private final Map<ComponentKey, AppWidgetProviderInfo> mWidgetProvidersMap;
private WorkspaceResult(BgDataModel dataModel,
WidgetsModel widgetsModel,
Map<ComponentKey, AppWidgetProviderInfo> widgetProviderInfoMap) {
synchronized (dataModel) {
mWorkspaceItems = dataModel.workspaceItems;
mAppWidgets = dataModel.appWidgets;
mHotseatPredictions = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
mWidgetsModel = widgetsModel;
mWidgetProvidersMap = widgetProviderInfoMap;
}
}
}
}
@@ -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<ComponentKey, AppWidgetProviderInfo> 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());
}
}
@@ -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.
*
@@ -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;
}
/**
@@ -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
@@ -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
@@ -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);
@@ -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();
@@ -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));
@@ -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();
@@ -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.
@@ -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<AdapterHolder> mAdapters = new SparseArray();
private final UserHandle mCurrentUser = Process.myUserHandle();
private final Predicate<WidgetsListBaseEntry> mPrimaryWidgetsFilter = entry ->
mCurrentUser.equals(entry.mPkgItem.user);
private final Predicate<WidgetsListBaseEntry> mPrimaryWidgetsFilter =
entry -> mCurrentUser.equals(entry.mPkgItem.user);
private final Predicate<WidgetsListBaseEntry> 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<ArrayList<WidgetItem>> 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:
@@ -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<ViewHolder> 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<ViewHolderBinder> mViewHolderBinders = new SparseArray<>();
private final WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder;
@@ -90,9 +90,7 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> 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<ViewHolder> implements OnHeaderC
public void setWidgets(List<WidgetsListBaseEntry> tempEntries) {
mAllEntries = tempEntries.stream().sorted(mRowComparator)
.collect(Collectors.toList());
if (shouldClearVisibleEntries()) {
mVisibleEntries.clear();
}
updateVisibleEntries();
}
@@ -243,9 +244,6 @@ public class WidgetsListAdapter extends Adapter<ViewHolder> 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<ViewHolder> 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<PackageUserKey, PackageItemInfo> 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<WidgetsListBaseEntry> {
@@ -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();
@@ -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.
*/
@@ -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) {