Snap for 7389169 from 576722164a to sc-release
Change-Id: I0c39cfe8dcf02ea20bd24ec6383fefd4a9636a30
This commit is contained in:
+9
-7
@@ -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,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"
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+20
-6
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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
|
||||
|
||||
+1
-1
@@ -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))
|
||||
|
||||
+1
-2
@@ -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);
|
||||
}
|
||||
|
||||
+1
-2
@@ -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);
|
||||
}
|
||||
|
||||
+1
-2
@@ -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),
|
||||
|
||||
+24
-8
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
+77
-23
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user