Snap for 11672362 from c9d07b0639 to 24Q3-release

Change-Id: I8409e07a2f4fd796e20e128ce0c40ac47140a33e
This commit is contained in:
Android Build Coastguard Worker
2024-04-04 23:21:01 +00:00
79 changed files with 1500 additions and 600 deletions
+4 -4
View File
@@ -75,7 +75,7 @@ android_library {
"androidx.test.uiautomator_uiautomator",
"androidx.preference_preference",
"SystemUISharedLib",
"animationlib",
"//frameworks/libs/systemui:animationlib",
"launcher-testing-shared",
],
srcs: [
@@ -150,9 +150,9 @@ android_library {
"androidx.cardview_cardview",
"androidx.window_window",
"com.google.android.material_material",
"iconloader_base",
"view_capture",
"animationlib",
"//frameworks/libs/systemui:iconloader_base",
"//frameworks/libs/systemui:view_capture",
"//frameworks/libs/systemui:animationlib",
"SystemUI-statsd",
"launcher-testing-shared",
"androidx.lifecycle_lifecycle-common-java8",
@@ -21,10 +21,9 @@
<LinearLayout
android:id="@+id/action_buttons"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="@dimen/overview_actions_height"
android:layout_gravity="bottom"
android:gravity="center_horizontal"
android:layout_gravity="bottom|center_horizontal"
android:orientation="horizontal">
<Button
@@ -36,17 +35,12 @@
android:text="@string/action_screenshot"
android:theme="@style/ThemeControlHighlightWorkspaceColor" />
<Space
android:id="@+id/action_split_space"
android:layout_width="@dimen/overview_actions_button_spacing"
android:layout_height="1dp"
android:visibility="gone" />
<Button
android:id="@+id/action_split"
style="@style/OverviewActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/overview_actions_button_spacing"
android:text="@string/action_split"
android:theme="@style/ThemeControlHighlightWorkspaceColor"
android:visibility="gone" />
@@ -1,55 +0,0 @@
/*
* Copyright (C) 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.launcher3;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import androidx.annotation.Nullable;
import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.quickstep.SystemUiProxy;
import com.android.wm.shell.shared.IHomeTransitionListener;
/**
* Controls launcher response to home activity visibility changing.
*/
public class HomeTransitionController {
@Nullable private QuickstepLauncher mLauncher;
@Nullable private IHomeTransitionListener mHomeTransitionListener;
public void registerHomeTransitionListener(QuickstepLauncher launcher) {
mLauncher = launcher;
mHomeTransitionListener = new IHomeTransitionListener.Stub() {
@Override
public void onHomeVisibilityChanged(boolean isVisible) {
MAIN_EXECUTOR.execute(() -> {
if (mLauncher != null && mLauncher.getTaskbarUIController() != null) {
mLauncher.getTaskbarUIController().onLauncherVisibilityChanged(isVisible);
}
});
}
};
SystemUiProxy.INSTANCE.get(mLauncher).setHomeTransitionListener(mHomeTransitionListener);
}
public void unregisterHomeTransitionListener() {
SystemUiProxy.INSTANCE.get(mLauncher).setHomeTransitionListener(null);
mHomeTransitionListener = null;
mLauncher = null;
}
}
@@ -35,23 +35,31 @@ import android.view.WindowInsetsController;
import android.view.WindowManager;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.dragndrop.SimpleDragLayer;
import com.android.launcher3.model.WidgetItem;
import com.android.launcher3.model.WidgetPredictionsRequester;
import com.android.launcher3.model.WidgetsModel;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.popup.PopupDataProvider;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.BaseWidgetSheet;
import com.android.launcher3.widget.WidgetCell;
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
import com.android.launcher3.widget.picker.WidgetsFullSheet;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
/** An Activity that can host Launcher's widget picker. */
public class WidgetPickerActivity extends BaseActivity {
private static final String TAG = "WidgetPickerActivity";
/**
* Name of the extra that indicates that a widget being dragged.
*
@@ -64,14 +72,33 @@ public class WidgetPickerActivity extends BaseActivity {
// the intent, then widgets will not be filtered for size.
private static final String EXTRA_DESIRED_WIDGET_WIDTH = "desired_widget_width";
private static final String EXTRA_DESIRED_WIDGET_HEIGHT = "desired_widget_height";
/**
* Widgets currently added by the user in the UI surface.
* <p>This allows widget picker to exclude existing widgets from suggestions.</p>
*/
private static final String EXTRA_ADDED_APP_WIDGETS = "added_app_widgets";
/**
* A unique identifier of the surface hosting the widgets;
* <p>"widgets" is reserved for home screen surface.</p>
* <p>"widgets_hub" is reserved for glanceable hub surface.</p>
*/
private static final String EXTRA_UI_SURFACE = "ui_surface";
private static final Pattern UI_SURFACE_PATTERN =
Pattern.compile("^(widgets|widgets_hub)$");
private SimpleDragLayer<WidgetPickerActivity> mDragLayer;
private WidgetsModel mModel;
private LauncherAppState mApp;
private WidgetPredictionsRequester mWidgetPredictionsRequester;
private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {});
private int mDesiredWidgetWidth;
private int mDesiredWidgetHeight;
private int mWidgetCategoryFilter;
@Nullable
private String mUiSurface;
// Widgets existing on the host surface.
@NonNull
private List<AppWidgetProviderInfo> mAddedWidgets = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -80,9 +107,8 @@ public class WidgetPickerActivity extends BaseActivity {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER);
LauncherAppState app = LauncherAppState.getInstance(this);
InvariantDeviceProfile idp = app.getInvariantDeviceProfile();
mApp = LauncherAppState.getInstance(this);
InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile();
mDeviceProfile = idp.getDeviceProfile(this);
mModel = new WidgetsModel();
@@ -97,6 +123,11 @@ public class WidgetPickerActivity extends BaseActivity {
widgetSheet.disableNavBarScrim(true);
widgetSheet.addOnCloseListener(this::finish);
parseIntentExtras();
refreshAndBindWidgets();
}
private void parseIntentExtras() {
// A value of 0 for either size means that no filtering will occur in that dimension. If
// both values are 0, then no size filtering will occur.
mDesiredWidgetWidth =
@@ -108,7 +139,15 @@ public class WidgetPickerActivity extends BaseActivity {
mWidgetCategoryFilter =
getIntent().getIntExtra(AppWidgetManager.EXTRA_CATEGORY_FILTER, 0);
refreshAndBindWidgets();
String uiSurfaceParam = getIntent().getStringExtra(EXTRA_UI_SURFACE);
if (uiSurfaceParam != null && UI_SURFACE_PATTERN.matcher(uiSurfaceParam).matches()) {
mUiSurface = uiSurfaceParam;
}
ArrayList<AppWidgetProviderInfo> addedWidgets = getIntent().getParcelableArrayListExtra(
EXTRA_ADDED_APP_WIDGETS, AppWidgetProviderInfo.class);
if (addedWidgets != null) {
mAddedWidgets = addedWidgets;
}
}
@NonNull
@@ -179,11 +218,12 @@ public class WidgetPickerActivity extends BaseActivity {
};
}
/** Updates the model with widgets and provides them after applying the provided filter. */
private void refreshAndBindWidgets() {
MODEL_EXECUTOR.execute(() -> {
LauncherAppState app = LauncherAppState.getInstance(this);
mModel.update(app, null);
final ArrayList<WidgetsListBaseEntry> widgets =
final List<WidgetsListBaseEntry> allWidgets =
mModel.getFilteredWidgetsListForPicker(
app.getContext(),
/*widgetItemFilter=*/ widget -> {
@@ -193,10 +233,37 @@ public class WidgetPickerActivity extends BaseActivity {
return verdict.isAcceptable;
}
);
MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(widgets));
bindWidgets(allWidgets);
if (mUiSurface != null) {
Map<PackageUserKey, List<WidgetItem>> allWidgetsMap = allWidgets.stream()
.filter(WidgetsListHeaderEntry.class::isInstance)
.collect(Collectors.toMap(
entry -> PackageUserKey.fromPackageItemInfo(entry.mPkgItem),
entry -> entry.mWidgets)
);
mWidgetPredictionsRequester = new WidgetPredictionsRequester(app.getContext(),
mUiSurface, allWidgetsMap);
mWidgetPredictionsRequester.request(mAddedWidgets, this::bindRecommendedWidgets);
}
});
}
private void bindWidgets(List<WidgetsListBaseEntry> widgets) {
MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(widgets));
}
private void bindRecommendedWidgets(List<ItemInfo> recommendedWidgets) {
MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setRecommendedWidgets(recommendedWidgets));
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mWidgetPredictionsRequester != null) {
mWidgetPredictionsRequester.clear();
}
}
private WidgetAcceptabilityVerdict isWidgetAcceptable(WidgetItem widget) {
final AppWidgetProviderInfo info = widget.widgetInfo;
if (info == null) {
@@ -0,0 +1,233 @@
/*
* Copyright (C) 2024 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.model;
import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import android.app.prediction.AppPredictionContext;
import android.app.prediction.AppPredictionManager;
import android.app.prediction.AppPredictor;
import android.app.prediction.AppTarget;
import android.app.prediction.AppTargetEvent;
import android.app.prediction.AppTargetId;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ComponentName;
import android.content.Context;
import android.os.Bundle;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.annotation.WorkerThread;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.ComponentKey;
import com.android.launcher3.util.PackageUserKey;
import com.android.launcher3.widget.PendingAddWidgetInfo;
import com.android.launcher3.widget.picker.WidgetRecommendationCategoryProvider;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
/**
* Works with app predictor to fetch and process widget predictions displayed in a standalone
* widget picker activity for a UI surface.
*/
public class WidgetPredictionsRequester {
private static final int NUM_OF_RECOMMENDED_WIDGETS_PREDICATION = 20;
private static final String BUNDLE_KEY_ADDED_APP_WIDGETS = "added_app_widgets";
@Nullable
private AppPredictor mAppPredictor;
private final Context mContext;
@NonNull
private final String mUiSurface;
@NonNull
private final Map<PackageUserKey, List<WidgetItem>> mAllWidgets;
public WidgetPredictionsRequester(Context context, @NonNull String uiSurface,
@NonNull Map<PackageUserKey, List<WidgetItem>> allWidgets) {
mContext = context;
mUiSurface = uiSurface;
mAllWidgets = Collections.unmodifiableMap(allWidgets);
}
/**
* Requests predictions from the app predictions manager and registers the provided callback to
* receive updates when predictions are available.
*
* @param existingWidgets widgets that are currently added to the surface;
* @param callback consumer of prediction results to be called when predictions are
* available
*/
public void request(List<AppWidgetProviderInfo> existingWidgets,
Consumer<List<ItemInfo>> callback) {
Bundle bundle = buildBundleForPredictionSession(existingWidgets, mUiSurface);
Predicate<WidgetItem> filter = notOnUiSurfaceFilter(existingWidgets);
MODEL_EXECUTOR.execute(() -> {
clear();
AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class);
if (apm == null) {
return;
}
mAppPredictor = apm.createAppPredictionSession(
new AppPredictionContext.Builder(mContext)
.setUiSurface(mUiSurface)
.setExtras(bundle)
.setPredictedTargetCount(NUM_OF_RECOMMENDED_WIDGETS_PREDICATION)
.build());
mAppPredictor.registerPredictionUpdates(MODEL_EXECUTOR,
targets -> bindPredictions(targets, filter, callback));
mAppPredictor.requestPredictionUpdate();
});
}
/**
* Returns a bundle that can be passed in a prediction session
*
* @param addedWidgets widgets that are already added by the user in the ui surface
* @param uiSurface a unique identifier of the surface hosting widgets; format
* "widgets_xx"; note - "widgets" is reserved for home screen surface.
*/
@VisibleForTesting
static Bundle buildBundleForPredictionSession(List<AppWidgetProviderInfo> addedWidgets,
String uiSurface) {
Bundle bundle = new Bundle();
ArrayList<AppTargetEvent> addedAppTargetEvents = new ArrayList<>();
for (AppWidgetProviderInfo info : addedWidgets) {
ComponentName componentName = info.provider;
AppTargetEvent appTargetEvent = buildAppTargetEvent(uiSurface, info, componentName);
addedAppTargetEvents.add(appTargetEvent);
}
bundle.putParcelableArrayList(BUNDLE_KEY_ADDED_APP_WIDGETS, addedAppTargetEvents);
return bundle;
}
/**
* Builds the AppTargetEvent for added widgets in a form that can be passed to the widget
* predictor.
* Also see {@link PredictionHelper}
*/
private static AppTargetEvent buildAppTargetEvent(String uiSurface, AppWidgetProviderInfo info,
ComponentName componentName) {
AppTargetId appTargetId = new AppTargetId("widget:" + componentName.getPackageName());
AppTarget appTarget = new AppTarget.Builder(appTargetId, componentName.getPackageName(),
/*user=*/ info.getProfile()).setClassName(componentName.getClassName()).build();
return new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_PIN)
.setLaunchLocation(uiSurface).build();
}
/**
* Returns a filter to match {@link WidgetItem}s that don't exist on the UI surface.
*/
@NonNull
@VisibleForTesting
static Predicate<WidgetItem> notOnUiSurfaceFilter(
List<AppWidgetProviderInfo> existingWidgets) {
Set<ComponentKey> existingComponentKeys = existingWidgets.stream().map(
widget -> new ComponentKey(widget.provider, widget.getProfile())).collect(
Collectors.toSet());
return widgetItem -> !existingComponentKeys.contains(widgetItem);
}
/** Provides the predictions returned by the predictor to the registered callback. */
@WorkerThread
private void bindPredictions(List<AppTarget> targets, Predicate<WidgetItem> filter,
Consumer<List<ItemInfo>> callback) {
List<WidgetItem> filteredPredictions = filterPredictions(targets, mAllWidgets, filter);
List<ItemInfo> mappedPredictions = mapWidgetItemsToItemInfo(filteredPredictions);
MAIN_EXECUTOR.execute(() -> callback.accept(mappedPredictions));
}
/**
* Applies the provided filter (e.g. widgets not on workspace) on the predictions returned by
* the predictor.
*/
@VisibleForTesting
static List<WidgetItem> filterPredictions(List<AppTarget> predictions,
Map<PackageUserKey, List<WidgetItem>> allWidgets, Predicate<WidgetItem> filter) {
List<WidgetItem> servicePredictedItems = new ArrayList<>();
List<WidgetItem> localFilteredWidgets = new ArrayList<>();
for (AppTarget prediction : predictions) {
List<WidgetItem> widgetsInPackage = allWidgets.get(
new PackageUserKey(prediction.getPackageName(), prediction.getUser()));
if (widgetsInPackage == null || widgetsInPackage.isEmpty()) {
continue;
}
String className = prediction.getClassName();
if (!TextUtils.isEmpty(className)) {
WidgetItem item = widgetsInPackage.stream()
.filter(w -> className.equals(w.componentName.getClassName()))
.filter(filter)
.findFirst().orElse(null);
if (item != null) {
servicePredictedItems.add(item);
continue;
}
}
// No widget was added by the service, try local filtering
widgetsInPackage.stream().filter(filter).findFirst()
.ifPresent(localFilteredWidgets::add);
}
if (servicePredictedItems.isEmpty()) {
servicePredictedItems.addAll(localFilteredWidgets);
}
return servicePredictedItems;
}
/**
* Converts the list of {@link WidgetItem}s to the list of {@link ItemInfo}s.
*/
private List<ItemInfo> mapWidgetItemsToItemInfo(List<WidgetItem> widgetItems) {
List<ItemInfo> items;
if (enableCategorizedWidgetSuggestions()) {
WidgetRecommendationCategoryProvider categoryProvider =
WidgetRecommendationCategoryProvider.newInstance(mContext);
items = widgetItems.stream()
.map(it -> new PendingAddWidgetInfo(it.widgetInfo, CONTAINER_WIDGETS_PREDICTION,
categoryProvider.getWidgetRecommendationCategory(mContext, it)))
.collect(Collectors.toList());
} else {
items = widgetItems.stream().map(it -> new PendingAddWidgetInfo(it.widgetInfo,
CONTAINER_WIDGETS_PREDICTION)).collect(Collectors.toList());
}
return items;
}
/** Cleans up any open prediction sessions. */
public void clear() {
if (mAppPredictor != null) {
mAppPredictor.destroy();
mAppPredictor = null;
}
}
}
@@ -19,7 +19,6 @@ import static android.view.View.VISIBLE;
import static com.android.launcher3.LauncherState.BACKGROUND_APP;
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import android.os.Debug;
import android.os.SystemProperties;
@@ -136,9 +135,6 @@ public class DesktopVisibilityController {
Log.d(TAG, "setVisibleFreeformTasksCount: visibleTasksCount=" + visibleTasksCount
+ " currentValue=" + mVisibleFreeformTasksCount);
}
if (!enableDesktopWindowingMode()) {
return;
}
if (visibleTasksCount != mVisibleFreeformTasksCount) {
final boolean wasVisible = mVisibleFreeformTasksCount > 0;
@@ -180,9 +176,6 @@ public class DesktopVisibilityController {
Log.d(TAG, "setOverviewStateEnabled: enabled=" + overviewStateEnabled
+ " currentValue=" + mInOverviewState);
}
if (!enableDesktopWindowingMode()) {
return;
}
if (overviewStateEnabled != mInOverviewState) {
mInOverviewState = overviewStateEnabled;
if (mInOverviewState) {
@@ -202,9 +195,6 @@ public class DesktopVisibilityController {
Log.d(TAG, "setBackgroundStateEnabled: enabled=" + backgroundStateEnabled
+ " currentValue=" + mBackgroundStateEnabled);
}
if (!enableDesktopWindowingMode()) {
return;
}
if (backgroundStateEnabled != mBackgroundStateEnabled) {
mBackgroundStateEnabled = backgroundStateEnabled;
if (mBackgroundStateEnabled) {
@@ -229,9 +219,6 @@ public class DesktopVisibilityController {
* Notify controller that recents gesture has started.
*/
public void setRecentsGestureStart() {
if (!enableDesktopWindowingMode()) {
return;
}
if (DEBUG) {
Log.d(TAG, "setRecentsGestureStart");
}
@@ -243,9 +230,6 @@ public class DesktopVisibilityController {
* {@link com.android.quickstep.GestureState.GestureEndTarget}
*/
public void setRecentsGestureEnd(@Nullable GestureState.GestureEndTarget endTarget) {
if (!enableDesktopWindowingMode()) {
return;
}
if (DEBUG) {
Log.d(TAG, "setRecentsGestureEnd: endTarget=" + endTarget);
}
@@ -15,8 +15,6 @@
*/
package com.android.launcher3.taskbar;
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import android.content.ComponentName;
import android.content.pm.ActivityInfo;
@@ -117,9 +115,7 @@ public final class KeyboardQuickSwitchController implements
DesktopVisibilityController desktopController =
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
final boolean onDesktop =
enableDesktopWindowingMode()
&& desktopController != null
&& desktopController.areFreeformTasksVisible();
desktopController != null && desktopController.areFreeformTasksVisible();
if (mModel.isTaskListValid(mTaskListChangeId)) {
// When we are opening the KQS with no focus override, check if the first task is
@@ -158,14 +154,12 @@ public final class KeyboardQuickSwitchController implements
// Hide all desktop tasks and show them on the hidden tile
int hiddenDesktopTasks = 0;
if (enableDesktopWindowingMode()) {
DesktopTask desktopTask = findDesktopTask(tasks);
if (desktopTask != null) {
hiddenDesktopTasks = desktopTask.tasks.size();
tasks = tasks.stream()
.filter(t -> !(t instanceof DesktopTask))
.collect(Collectors.toCollection(ArrayList<GroupTask>::new));
}
DesktopTask desktopTask = findDesktopTask(tasks);
if (desktopTask != null) {
hiddenDesktopTasks = desktopTask.tasks.size();
tasks = tasks.stream()
.filter(t -> !(t instanceof DesktopTask))
.collect(Collectors.toCollection(ArrayList<GroupTask>::new));
}
mTasks = tasks.stream()
.limit(MAX_TASKS)
@@ -21,7 +21,6 @@ import static com.android.launcher3.statemanager.BaseState.FLAG_NON_INTERACTIVE;
import static com.android.launcher3.taskbar.TaskbarEduTooltipControllerKt.TOOLTIP_STEP_FEATURES;
import static com.android.launcher3.taskbar.TaskbarLauncherStateController.FLAG_VISIBLE;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import android.animation.Animator;
import android.animation.AnimatorSet;
@@ -35,6 +34,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Flags;
import com.android.launcher3.LauncherState;
import com.android.launcher3.QuickstepTransitionManager;
import com.android.launcher3.R;
@@ -49,8 +49,10 @@ import com.android.launcher3.uioverrides.QuickstepLauncher;
import com.android.launcher3.util.DisplayController;
import com.android.launcher3.util.MultiPropertyFactory;
import com.android.launcher3.util.OnboardingPrefs;
import com.android.quickstep.HomeVisibilityState;
import com.android.quickstep.LauncherActivityInterface;
import com.android.quickstep.RecentsAnimationCallbacks;
import com.android.quickstep.SystemUiProxy;
import com.android.quickstep.util.GroupTask;
import com.android.quickstep.util.TISBindHelper;
import com.android.quickstep.views.RecentsView;
@@ -79,6 +81,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
AnimatedFloat.VALUE, DISPLAY_PROGRESS_COUNT, Float::max);
private final QuickstepLauncher mLauncher;
private final HomeVisibilityState mHomeState;
private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
dp -> {
@@ -87,6 +90,8 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
mControllers.taskbarViewController.onRotationChanged(dp);
}
};
private final HomeVisibilityState.VisibilityChangeListener mVisibilityChangeListener =
this::onLauncherVisibilityChanged;
// Initialized in init.
private final TaskbarLauncherStateController
@@ -94,6 +99,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
public LauncherTaskbarUIController(QuickstepLauncher launcher) {
mLauncher = launcher;
mHomeState = SystemUiProxy.INSTANCE.get(mLauncher).getHomeVisibilityState();
}
@Override
@@ -104,8 +110,11 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
mControllers.getSharedState().sysuiStateFlags);
mLauncher.setTaskbarUIController(this);
onLauncherVisibilityChanged(mLauncher.hasBeenResumed(), true /* fromInit */);
mHomeState.addListener(mVisibilityChangeListener);
onLauncherVisibilityChanged(
Flags.useActivityOverlay()
? mHomeState.isHomeVisible() : mLauncher.hasBeenResumed(),
true /* fromInit */);
onStashedInAppChanged(mLauncher.getDeviceProfile());
mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
@@ -129,6 +138,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
mLauncher.setTaskbarUIController(null);
mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
mHomeState.removeListener(mVisibilityChangeListener);
updateTaskTransitionSpec(true);
}
@@ -209,9 +219,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
DesktopVisibilityController desktopController =
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
final boolean onDesktop =
enableDesktopWindowingMode()
&& desktopController != null
&& desktopController.areFreeformTasksVisible();
desktopController != null && desktopController.areFreeformTasksVisible();
if (onDesktop) {
isVisible = false;
}
@@ -234,7 +242,8 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
@Override
public void refreshResumedState() {
onLauncherVisibilityChanged(mLauncher.hasBeenResumed());
onLauncherVisibilityChanged(Flags.useActivityOverlay()
? mHomeState.isHomeVisible() : mLauncher.hasBeenResumed());
}
@Override
@@ -656,7 +656,8 @@ public class TaskbarLauncherStateController {
* Returns if the current Launcher state has hotseat on top of other elemnets.
*/
public boolean isInHotseatOnTopStates() {
return mLauncherState != LauncherState.ALL_APPS;
return mLauncherState != LauncherState.ALL_APPS
&& !mLauncher.getWorkspace().isOverlayShown();
}
boolean isInOverview() {
@@ -27,7 +27,6 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP;
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
@@ -281,12 +280,10 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
private void navigateHome() {
TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY);
if (enableDesktopWindowingMode()) {
DesktopVisibilityController desktopVisibilityController =
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
if (desktopVisibilityController != null) {
desktopVisibilityController.onHomeActionTriggered();
}
DesktopVisibilityController desktopVisibilityController =
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
if (desktopVisibilityController != null) {
desktopVisibilityController.onHomeActionTriggered();
}
mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_HOME);
@@ -118,7 +118,7 @@ public class TaskbarPopupController implements TaskbarControllers.LoggableTaskba
FolderInfo fi = (FolderInfo) info;
if (fi.anyMatch(matcher)) {
FolderDotInfo folderDotInfo = new FolderDotInfo();
for (WorkspaceItemInfo si : fi.getContents()) {
for (ItemInfo si : fi.getContents()) {
folderDotInfo.addDotInfo(mPopupDataProvider.getDotInfoForItem(si));
}
((FolderIcon) v).setDotInfo(folderDotInfo);
@@ -207,10 +207,10 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
}
@Override
protected void onScaleProgressChanged() {
super.onScaleProgressChanged();
mAppsView.setClipChildren(!mIsBackProgressing);
mAppsView.getAppsRecyclerViewContainer().setClipChildren(!mIsBackProgressing);
protected void onUserSwipeToDismissProgressChanged() {
super.onUserSwipeToDismissProgressChanged();
mAppsView.setClipChildren(!mIsDismissInProgress);
mAppsView.getAppsRecyclerViewContainer().setClipChildren(!mIsDismissInProgress);
}
@Override
@@ -264,7 +264,7 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
if (mAllAppsCallbacks.handleSearchBackInvoked()) {
// We need to scale back taskbar all apps if we navigate back within search inside all
// apps
animateSlideInViewToNoScale();
animateSwipeToDismissProgressToStart();
} else {
super.onBackInvoked();
}
@@ -220,7 +220,7 @@ public class BubbleBarController extends IBubblesListener.Stub {
mBubbleStashedHandleViewController.setHiddenForBubbles(
!sBubbleBarEnabled || mBubbles.isEmpty());
mBubbleBarViewController.setUpdateSelectedBubbleAfterCollapse(
key -> setSelectedBubble(mBubbles.get(key)));
key -> setSelectedBubbleInternal(mBubbles.get(key)));
});
}
@@ -390,7 +390,7 @@ public class BubbleBarController extends IBubblesListener.Stub {
}
}
if (bubbleToSelect != null) {
setSelectedBubble(bubbleToSelect);
setSelectedBubbleInternal(bubbleToSelect);
if (previouslySelectedBubble == null) {
mBubbleStashController.animateToInitialState(update.expanded);
}
@@ -409,8 +409,7 @@ public class BubbleBarController extends IBubblesListener.Stub {
if (update.bubbleBarLocation != mBubbleBarViewController.getBubbleBarLocation()) {
// Animate when receiving updates. Skip it if we received the initial state.
boolean animate = !update.initialState;
mBubbleBarViewController.setBubbleBarLocation(update.bubbleBarLocation, animate);
mBubbleStashController.setBubbleBarLocation(update.bubbleBarLocation);
updateBubbleBarLocationInternal(update.bubbleBarLocation, animate);
}
}
}
@@ -436,7 +435,7 @@ public class BubbleBarController extends IBubblesListener.Stub {
/** Updates the currently selected bubble for launcher views and tells WMShell to show it. */
public void showAndSelectBubble(BubbleBarItem b) {
if (DEBUG) Log.w(TAG, "showingSelectedBubble: " + b.getKey());
setSelectedBubble(b);
setSelectedBubbleInternal(b);
showSelectedBubble();
}
@@ -445,7 +444,7 @@ public class BubbleBarController extends IBubblesListener.Stub {
* WMShell that the selection has changed, that should go through either
* {@link #showSelectedBubble()} or {@link #showAndSelectBubble(BubbleBarItem)}.
*/
private void setSelectedBubble(BubbleBarItem b) {
private void setSelectedBubbleInternal(BubbleBarItem b) {
if (!Objects.equals(b, mSelectedBubble)) {
if (DEBUG) Log.w(TAG, "selectingBubble: " + b.getKey());
mSelectedBubble = b;
@@ -464,6 +463,21 @@ public class BubbleBarController extends IBubblesListener.Stub {
return null;
}
/**
* Set a new bubble bar location.
* <p>
* Updates the value locally in Launcher and in WMShell.
*/
public void updateBubbleBarLocation(BubbleBarLocation location) {
updateBubbleBarLocationInternal(location, false /* animate */);
mSystemUiProxy.setBubbleBarLocation(location);
}
private void updateBubbleBarLocationInternal(BubbleBarLocation location, boolean animate) {
mBubbleBarViewController.setBubbleBarLocation(location, animate);
mBubbleStashController.setBubbleBarLocation(location);
}
//
// Loading data for the bubbles
//
@@ -24,7 +24,9 @@ import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.LayoutDirection;
@@ -108,6 +110,7 @@ public class BubbleBarView extends FrameLayout {
private final float mIconSize;
// The elevation of the bubbles within the bar
private final float mBubbleElevation;
private final float mDragElevation;
private final int mPointerSize;
// Whether the bar is expanded (i.e. the bubble activity is being displayed).
@@ -138,11 +141,15 @@ public class BubbleBarView extends FrameLayout {
@Nullable
private Consumer<String> mUpdateSelectedBubbleAfterCollapse;
private boolean mDragging;
@Nullable
private BubbleView mDraggedBubbleView;
private int mPreviousLayoutDirection = LayoutDirection.UNDEFINED;
private boolean mLocationChangePending;
public BubbleBarView(Context context) {
this(context, null);
}
@@ -163,6 +170,7 @@ public class BubbleBarView extends FrameLayout {
mIconSpacing = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_spacing);
mIconSize = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size);
mBubbleElevation = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_elevation);
mDragElevation = getResources().getDimensionPixelSize(R.dimen.bubblebar_drag_elevation);
mPointerSize = getResources().getDimensionPixelSize(R.dimen.bubblebar_pointer_size);
setClipToPadding(false);
@@ -237,12 +245,13 @@ public class BubbleBarView extends FrameLayout {
}
}
@SuppressLint("RtlHardcoded")
private void onBubbleBarLocationChanged() {
mLocationChangePending = false;
final boolean onLeft = mBubbleBarLocation.isOnLeft(isLayoutRtl());
mBubbleBarBackground.setAnchorLeft(onLeft);
mRelativePivotX = onLeft ? 0f : 1f;
ViewGroup.LayoutParams layoutParams = getLayoutParams();
if (layoutParams instanceof LayoutParams lp) {
if (getLayoutParams() instanceof LayoutParams lp) {
lp.gravity = Gravity.BOTTOM | (onLeft ? Gravity.LEFT : Gravity.RIGHT);
setLayoutParams(lp);
}
@@ -267,11 +276,82 @@ public class BubbleBarView extends FrameLayout {
}
}
/**
* Set whether this view is being currently being dragged
*/
public void setIsDragging(boolean dragging) {
if (mDragging == dragging) {
return;
}
mDragging = dragging;
setElevation(dragging ? mDragElevation : mBubbleElevation);
if (!dragging && mLocationChangePending) {
// During drag finish animation we may update the translation x value to shift the
// bubble to the new drop target. Clear the translation here.
setTranslationX(0f);
onBubbleBarLocationChanged();
}
}
/**
* Adjust resting position for the bubble bar while it is being dragged.
* <p>
* Bubble bar is laid out on left or right side of the screen. When it is being dragged to
* the opposite side, the resting position should be on that side. Calculate any additional
* translation that may be required to move the bubble bar to the new side.
*
* @param restingPosition relative resting position of the bubble bar from the laid out position
*/
@SuppressLint("RtlHardcoded")
void adjustRelativeRestingPosition(PointF restingPosition) {
final boolean locationOnLeft = mBubbleBarLocation.isOnLeft(isLayoutRtl());
// Bubble bar is placed left or right with gravity. Check where it is currently.
final int absoluteGravity = Gravity.getAbsoluteGravity(
((LayoutParams) getLayoutParams()).gravity, getLayoutDirection());
final boolean gravityOnLeft =
(absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.LEFT;
// Bubble bar is pinned to the same side per gravity and the desired location.
// Resting translation does not need to be adjusted.
if (locationOnLeft == gravityOnLeft) {
return;
}
// Bubble bar is laid out on left or right side of the screen. And the desired new
// location is on the other side. Calculate x translation value required to shift the
// bubble bar from one side to the other.
float x = getDistanceFromOtherSide();
if (locationOnLeft) {
// New location is on the left, shift left
// before -> |......ooo.| after -> |.ooo......|
restingPosition.x = -x;
} else {
// New location is on the right, shift right
// before -> |.ooo......| after -> |......ooo.|
restingPosition.x = x;
}
}
private float getDistanceFromOtherSide() {
// Calculate the shift needed to position the bubble bar on the other side
int displayWidth = getResources().getDisplayMetrics().widthPixels;
int margin = 0;
if (getLayoutParams() instanceof MarginLayoutParams lp) {
margin += lp.leftMargin;
margin += lp.rightMargin;
}
return (float) (displayWidth - getWidth() - margin);
}
private void setBubbleBarLocationInternal(BubbleBarLocation bubbleBarLocation) {
if (bubbleBarLocation != mBubbleBarLocation) {
mBubbleBarLocation = bubbleBarLocation;
onBubbleBarLocationChanged();
invalidate();
if (mDragging) {
mLocationChangePending = true;
} else {
onBubbleBarLocationChanged();
invalidate();
}
}
}
@@ -25,7 +25,6 @@ import android.view.ViewConfiguration;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.launcher3.R;
import com.android.launcher3.taskbar.TaskbarActivityContext;
/**
@@ -55,9 +54,8 @@ public class BubbleDragController {
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
mBubbleDismissController = bubbleControllers.bubbleDismissController;
mBubbleBarPinController = bubbleControllers.bubbleBarPinController;
mBubbleBarPinController.setListener(location -> {
// TODO(b/330585397): update bubble bar location in shell
});
mBubbleBarPinController.setListener(
bubbleControllers.bubbleBarController::updateBubbleBarLocation);
mBubbleDismissController.setListener(
stuck -> mBubbleBarPinController.setDropTargetHidden(stuck));
}
@@ -96,11 +94,8 @@ public class BubbleDragController {
@SuppressLint("ClickableViewAccessibility")
public void setupBubbleBarView(@NonNull BubbleBarView bubbleBarView) {
PointF initialRelativePivot = new PointF();
final int restingElevation = bubbleBarView.getResources().getDimensionPixelSize(
R.dimen.bubblebar_elevation);
final int dragElevation = bubbleBarView.getResources().getDimensionPixelSize(
R.dimen.bubblebar_drag_elevation);
bubbleBarView.setOnTouchListener(new BubbleTouchListener() {
@Override
protected boolean onTouchDown(@NonNull View view, @NonNull MotionEvent event) {
if (bubbleBarView.isExpanded()) return false;
@@ -114,7 +109,7 @@ public class BubbleDragController {
// By default the bubble bar view pivot is in bottom right corner, while dragging
// it should be centered in order to align it with the dismiss target view
bubbleBarView.setRelativePivot(/* x = */ 0.5f, /* y = */ 0.5f);
bubbleBarView.setElevation(dragElevation);
bubbleBarView.setIsDragging(true);
mBubbleBarPinController.onDragStart(
bubbleBarView.getBubbleBarLocation().isOnLeft(bubbleBarView.isLayoutRtl()));
}
@@ -138,7 +133,14 @@ public class BubbleDragController {
void onDragEnd() {
// Restoring the initial pivot for the bubble bar view
bubbleBarView.setRelativePivot(initialRelativePivot.x, initialRelativePivot.y);
bubbleBarView.setElevation(restingElevation);
bubbleBarView.setIsDragging(false);
}
@Override
protected PointF getRestingPosition() {
PointF restingPosition = super.getRestingPosition();
bubbleBarView.adjustRelativeRestingPosition(restingPosition);
return restingPosition;
}
});
}
@@ -226,6 +228,13 @@ public class BubbleDragController {
protected void onDragDismiss() {
}
/**
* Get the resting position of the view when drag is released
*/
protected PointF getRestingPosition() {
return mViewInitialPosition;
}
@Override
@SuppressLint("ClickableViewAccessibility")
public boolean onTouch(@NonNull View view, @NonNull MotionEvent event) {
@@ -353,7 +362,7 @@ public class BubbleDragController {
mAnimator.animateDismiss(mViewInitialPosition, onComplete);
} else {
onDragRelease();
mAnimator.animateToInitialState(mViewInitialPosition, getCurrentVelocity(),
mAnimator.animateToInitialState(getRestingPosition(), getCurrentVelocity(),
onComplete);
}
mBubbleDismissController.hideDismissView();
@@ -62,8 +62,8 @@ import static com.android.launcher3.util.DisplayController.CHANGE_NAVIGATION_MOD
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
import static com.android.quickstep.util.AnimUtils.completeRunnableListCallback;
import static com.android.quickstep.util.SplitAnimationTimings.TABLET_HOME_TO_SPLIT;
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
import android.animation.Animator;
@@ -105,7 +105,6 @@ import com.android.app.viewcapture.SettingsAwareViewCapture;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Flags;
import com.android.launcher3.HomeTransitionController;
import com.android.launcher3.InvariantDeviceProfile;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings.Favorites;
@@ -217,7 +216,7 @@ public class QuickstepLauncher extends Launcher {
private FixedContainerItems mAllAppsPredictions;
private HotseatPredictionController mHotseatPredictionController;
private DepthController mDepthController;
private DesktopVisibilityController mDesktopVisibilityController;
private @Nullable DesktopVisibilityController mDesktopVisibilityController;
private QuickstepTransitionManager mAppTransitionManager;
private OverviewActionsView mActionsView;
private TISBindHelper mTISBindHelper;
@@ -245,8 +244,6 @@ public class QuickstepLauncher extends Launcher {
private boolean mIsPredictiveBackToHomeInProgress;
private HomeTransitionController mHomeTransitionController;
@Override
protected void setupViews() {
super.setupViews();
@@ -277,15 +274,10 @@ public class QuickstepLauncher extends Launcher {
mAppTransitionManager.registerRemoteAnimations();
mAppTransitionManager.registerRemoteTransitions();
if (FeatureFlags.enableHomeTransitionListener()) {
mHomeTransitionController = new HomeTransitionController();
mHomeTransitionController.registerHomeTransitionListener(this);
}
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
mDepthController = new DepthController(this);
mDesktopVisibilityController = new DesktopVisibilityController(this);
if (enableDesktopWindowingMode()) {
mDesktopVisibilityController = new DesktopVisibilityController(this);
mDesktopVisibilityController.registerSystemUiListener();
mSplitSelectStateController.initSplitFromDesktopController(this);
}
@@ -523,10 +515,6 @@ public class QuickstepLauncher extends Launcher {
mLauncherUnfoldAnimationController.onDestroy();
}
if (mHomeTransitionController != null) {
mHomeTransitionController.unregisterHomeTransitionListener();
}
if (mDesktopVisibilityController != null) {
mDesktopVisibilityController.unregisterSystemUiListener();
}
@@ -948,15 +936,13 @@ public class QuickstepLauncher extends Launcher {
@Override
public void setResumed() {
if (enableDesktopWindowingMode()) {
DesktopVisibilityController controller = mDesktopVisibilityController;
if (controller != null && controller.areFreeformTasksVisible()
&& !controller.isRecentsGestureInProgress()) {
// Return early to skip setting activity to appear as resumed
// TODO(b/255649902): shouldn't be needed when we have a separate launcher state
// for desktop that we can use to control other parts of launcher
return;
}
if (mDesktopVisibilityController != null
&& mDesktopVisibilityController.areFreeformTasksVisible()
&& !mDesktopVisibilityController.isRecentsGestureInProgress()) {
// Return early to skip setting activity to appear as resumed
// TODO(b/255649902): shouldn't be needed when we have a separate launcher state
// for desktop that we can use to control other parts of launcher
return;
}
super.setResumed();
}
@@ -1090,6 +1076,7 @@ public class QuickstepLauncher extends Launcher {
return mDepthController;
}
@Nullable
public DesktopVisibilityController getDesktopVisibilityController() {
return mDesktopVisibilityController;
}
@@ -68,10 +68,13 @@ public class AllAppsState extends LauncherState {
@Override
public void onBackInvoked(Launcher launcher) {
// In predictive back swipe, onBackInvoked() will be called after onBackStarted().
// Because the 2nd InteractionJankMonitor.begin() will be ignore within timeout, it's safe
// to call InteractionJankMonitorWrapper.begin here.
InteractionJankMonitorWrapper.begin(launcher.getAppsView(),
Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK);
// In 3 button mode, onBackStarted() is not called but onBackInvoked() will be called.
// Thus In onBackInvoked(), we should only begin instrumenting if we didn't call
// onBackStarted() to start instrumenting CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK.
if (!InteractionJankMonitorWrapper.isInstrumenting(Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK)) {
InteractionJankMonitorWrapper.begin(
launcher.getAppsView(), Cuj.CUJ_LAUNCHER_CLOSE_ALL_APPS_BACK);
}
super.onBackInvoked(launcher);
}
@@ -18,7 +18,6 @@ package com.android.launcher3.uioverrides.states;
import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import android.content.Context;
import android.graphics.Color;
@@ -92,8 +91,7 @@ public class BackgroundAppState extends OverviewState {
@Override
protected float getDepthUnchecked(Context context) {
if (enableDesktopWindowingMode()
&& Launcher.getLauncher(context).areFreeformTasksVisible()) {
if (Launcher.getLauncher(context).areFreeformTasksVisible()) {
// Don't blur the background while freeform tasks are visible
return BaseDepthController.DEPTH_0_PERCENT;
} else if (enableScalingRevealHomeAnimation()) {
@@ -16,7 +16,6 @@
package com.android.launcher3.uioverrides.states;
import static com.android.launcher3.logging.StatsLogManager.LAUNCHER_STATE_BACKGROUND;
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import android.graphics.Color;
@@ -46,11 +45,9 @@ public class QuickSwitchState extends BackgroundAppState {
@Override
public int getWorkspaceScrimColor(Launcher launcher) {
if (enableDesktopWindowingMode()) {
if (launcher.areFreeformTasksVisible()) {
// No scrim while freeform tasks are visible
return Color.TRANSPARENT;
}
if (launcher.areFreeformTasksVisible()) {
// No scrim while freeform tasks are visible
return Color.TRANSPARENT;
}
DeviceProfile dp = launcher.getDeviceProfile();
if (dp.isTaskbarPresentInApps) {
@@ -61,7 +61,6 @@ import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent
import static com.android.quickstep.util.ActiveGestureErrorDetector.GestureEvent.ON_SETTLED_ON_END_TARGET;
import static com.android.quickstep.views.RecentsView.UPDATE_SYSUI_FLAGS_THRESHOLD;
import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS;
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -947,7 +946,7 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
public void onRecentsAnimationStart(RecentsAnimationController controller,
RecentsAnimationTargets targets) {
super.onRecentsAnimationStart(controller, targets);
if (enableDesktopWindowingMode() && targets.hasDesktopTasks()) {
if (targets.hasDesktopTasks()) {
mRemoteTargetHandles = mTargetGluer.assignTargetsForDesktop(targets);
} else {
int untrimmedAppCount = mRemoteTargetHandles.length;
@@ -1170,13 +1169,11 @@ public abstract class AbsSwipeUpHandler<T extends StatefulActivity<S>,
mStateCallback.setState(STATE_SCALED_CONTROLLER_HOME | STATE_CAPTURE_SCREENSHOT);
// Notify the SysUI to use fade-in animation when entering PiP
SystemUiProxy.INSTANCE.get(mContext).setPipAnimationTypeToAlpha();
if (enableDesktopWindowingMode()) {
DesktopVisibilityController desktopVisibilityController =
mActivityInterface.getDesktopVisibilityController();
if (desktopVisibilityController != null) {
// Notify the SysUI to stash desktop apps if they are visible
DesktopVisibilityController desktopVisibilityController =
mActivityInterface.getDesktopVisibilityController();
if (desktopVisibilityController != null) {
desktopVisibilityController.onHomeActionTriggered();
}
desktopVisibilityController.onHomeActionTriggered();
}
break;
case RECENTS:
@@ -25,7 +25,6 @@ import static com.android.quickstep.GestureState.GestureEndTarget.LAST_TASK;
import static com.android.quickstep.GestureState.GestureEndTarget.RECENTS;
import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_FADE_ANIM;
import static com.android.quickstep.util.RecentsAtomicAnimationFactory.INDEX_RECENTS_TRANSLATE_X_ANIM;
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
import static com.android.quickstep.views.RecentsView.FULLSCREEN_PROGRESS;
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
@@ -109,19 +108,17 @@ public abstract class BaseActivityInterface<STATE_TYPE extends BaseState<STATE_T
if (endTarget != null) {
// We were on our way to this state when we got canceled, end there instead.
startState = stateFromGestureEndTarget(endTarget);
if (enableDesktopWindowingMode()) {
DesktopVisibilityController controller = getDesktopVisibilityController();
if (controller != null && controller.areFreeformTasksVisible()
&& endTarget == LAST_TASK) {
// When we are cancelling the transition and going back to last task, move to
// rest state instead when desktop tasks are visible.
// If a fullscreen task is visible, launcher goes to normal state when the
// activity is stopped. This does not happen when freeform tasks are visible
// on top of launcher. Force the launcher state to rest state here.
startState = activity.getStateManager().getRestState();
// Do not animate the transition
activityVisible = false;
}
DesktopVisibilityController controller = getDesktopVisibilityController();
if (controller != null && controller.areFreeformTasksVisible()
&& endTarget == LAST_TASK) {
// When we are cancelling the transition and going back to last task, move to
// rest state instead when desktop tasks are visible.
// If a fullscreen task is visible, launcher goes to normal state when the
// activity is stopped. This does not happen when freeform tasks are visible
// on top of launcher. Force the launcher state to rest state here.
startState = activity.getStateManager().getRestState();
// Do not animate the transition
activityVisible = false;
}
}
activity.getStateManager().goToState(startState, activityVisible);
@@ -0,0 +1,66 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quickstep
import android.os.RemoteException
import android.util.Log
import com.android.launcher3.config.FeatureFlags
import com.android.launcher3.util.Executors
import com.android.wm.shell.shared.IHomeTransitionListener.Stub
import com.android.wm.shell.shared.IShellTransitions
/** Class to track visibility state of Launcher */
class HomeVisibilityState {
var isHomeVisible = true
private set
private var listeners = mutableSetOf<VisibilityChangeListener>()
fun addListener(l: VisibilityChangeListener) = listeners.add(l)
fun removeListener(l: VisibilityChangeListener) = listeners.remove(l)
fun init(transitions: IShellTransitions?) {
if (!FeatureFlags.enableHomeTransitionListener()) return
try {
transitions?.setHomeTransitionListener(
object : Stub() {
override fun onHomeVisibilityChanged(isVisible: Boolean) {
Executors.MAIN_EXECUTOR.execute {
isHomeVisible = isVisible
listeners.forEach { it.onHomeVisibilityChanged(isVisible) }
}
}
}
)
} catch (e: RemoteException) {
Log.w(TAG, "Failed call setHomeTransitionListener", e)
}
}
interface VisibilityChangeListener {
fun onHomeVisibilityChanged(isVisible: Boolean)
}
override fun toString() = "{HomeVisibilityState isHomeVisible=$isHomeVisible}"
companion object {
private const val TAG = "HomeVisibilityState"
}
}
@@ -35,6 +35,7 @@ import androidx.annotation.Nullable;
import androidx.annotation.UiThread;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.Flags;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherAnimUtils;
import com.android.launcher3.LauncherInitListener;
@@ -206,8 +207,17 @@ public final class LauncherActivityInterface extends
@UiThread
private Launcher getVisibleLauncher() {
Launcher launcher = getCreatedActivity();
return (launcher != null) && launcher.isStarted()
&& (isInLiveTileMode() || launcher.hasBeenResumed()) ? launcher : null;
if (launcher == null) {
return null;
}
if (launcher.isStarted() && (isInLiveTileMode() || launcher.hasBeenResumed())) {
return launcher;
}
if (Flags.useActivityOverlay()
&& SystemUiProxy.INSTANCE.get(launcher).getHomeVisibilityState().isHomeVisible()) {
return launcher;
}
return null;
}
@Override
@@ -39,6 +39,7 @@ import android.os.Handler;
import android.os.RemoteException;
import android.util.Log;
import android.util.Pair;
import android.view.Choreographer;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
@@ -99,7 +100,7 @@ public class LauncherBackAnimationController {
private final int mWindowScaleMarginX;
private float mWindowScaleEndCornerRadius;
private float mWindowScaleStartCornerRadius;
private final Interpolator mProgressInterpolator = Interpolators.STANDARD_DECELERATE;
private final Interpolator mProgressInterpolator = Interpolators.BACK_GESTURE;
private final Interpolator mVerticalMoveInterpolator = new DecelerateInterpolator();
private final PointF mInitialTouchPos = new PointF();
@@ -302,7 +303,7 @@ public class LauncherBackAnimationController {
if (mScrimLayer == null) {
addScrimLayer();
}
mTransaction.apply();
applyTransaction();
}
private void setLauncherTargetViewVisible(boolean isVisible) {
@@ -342,7 +343,8 @@ public class LauncherBackAnimationController {
return;
}
if (mScrimLayer.isValid()) {
mTransaction.remove(mScrimLayer).apply();
mTransaction.remove(mScrimLayer);
applyTransaction();
}
mScrimLayer = null;
}
@@ -396,7 +398,11 @@ public class LauncherBackAnimationController {
mTransaction.setWindowCrop(mBackTarget.leash, mStartRect);
mTransaction.setCornerRadius(mBackTarget.leash, cornerRadius);
}
applyTransaction();
}
private void applyTransaction() {
mTransaction.setFrameTimelineVsync(Choreographer.getInstance().getVsyncId());
mTransaction.apply();
}
@@ -511,7 +517,7 @@ public class LauncherBackAnimationController {
float value = (Float) animation.getAnimatedValue();
if (mScrimLayer != null && mScrimLayer.isValid()) {
mTransaction.setAlpha(mScrimLayer, value * mScrimAlpha);
mTransaction.apply();
applyTransaction();
}
});
mScrimAlphaAnimator.addListener(new AnimatorListenerAdapter() {
@@ -270,9 +270,13 @@ public class RecentTasksList {
int numVisibleTasks = 0;
for (GroupedRecentTaskInfo rawTask : rawTasks) {
if (enableDesktopWindowingMode() && rawTask.getType() == TYPE_FREEFORM) {
GroupTask desktopTask = createDesktopTask(rawTask);
allTasks.add(desktopTask);
if (rawTask.getType() == TYPE_FREEFORM) {
// TYPE_FREEFORM tasks is only created when enableDesktopWindowingMode() is true,
// leftover TYPE_FREEFORM tasks created when flag was on should be ignored.
if (enableDesktopWindowingMode()) {
GroupTask desktopTask = createDesktopTask(rawTask);
allTasks.add(desktopTask);
}
continue;
}
ActivityManager.RecentTaskInfo taskInfo1 = rawTask.getTaskInfo1();
@@ -17,7 +17,6 @@
package com.android.quickstep;
import static com.android.quickstep.util.SplitScreenUtils.convertShellSplitBoundsToLauncher;
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import static com.android.wm.shell.util.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
import android.app.WindowConfiguration;
@@ -68,17 +67,15 @@ public class RemoteTargetGluer {
* running tasks
*/
public RemoteTargetGluer(Context context, BaseActivityInterface sizingStrategy) {
if (enableDesktopWindowingMode()) {
DesktopVisibilityController desktopVisibilityController =
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
if (desktopVisibilityController != null) {
int visibleTasksCount = desktopVisibilityController.getVisibleFreeformTasksCount();
if (visibleTasksCount > 0) {
// Allocate +1 to account for a new task added to the desktop mode
int numHandles = visibleTasksCount + 1;
init(context, sizingStrategy, numHandles, true /* forDesktop */);
return;
}
DesktopVisibilityController desktopVisibilityController =
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
if (desktopVisibilityController != null) {
int visibleTasksCount = desktopVisibilityController.getVisibleFreeformTasksCount();
if (visibleTasksCount > 0) {
// Allocate +1 to account for a new task added to the desktop mode
int numHandles = visibleTasksCount + 1;
init(context, sizingStrategy, numHandles, true /* forDesktop */);
return;
}
}
@@ -63,7 +63,6 @@ import androidx.annotation.WorkerThread;
import com.android.internal.logging.InstanceId;
import com.android.internal.util.ScreenshotRequest;
import com.android.internal.view.AppearanceRegion;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.util.MainThreadInitializedObject;
import com.android.launcher3.util.Preconditions;
import com.android.quickstep.util.ActiveGestureLog;
@@ -82,6 +81,7 @@ import com.android.systemui.unfold.progress.IUnfoldTransitionListener;
import com.android.wm.shell.back.IBackAnimation;
import com.android.wm.shell.bubbles.IBubbles;
import com.android.wm.shell.bubbles.IBubblesListener;
import com.android.wm.shell.common.bubbles.BubbleBarLocation;
import com.android.wm.shell.common.pip.IPip;
import com.android.wm.shell.common.pip.IPipAnimationListener;
import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
@@ -91,7 +91,6 @@ import com.android.wm.shell.draganddrop.IDragAndDrop;
import com.android.wm.shell.onehanded.IOneHanded;
import com.android.wm.shell.recents.IRecentTasks;
import com.android.wm.shell.recents.IRecentTasksListener;
import com.android.wm.shell.shared.IHomeTransitionListener;
import com.android.wm.shell.shared.IShellTransitions;
import com.android.wm.shell.splitscreen.ISplitScreen;
import com.android.wm.shell.splitscreen.ISplitScreenListener;
@@ -157,7 +156,7 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle {
private IOnBackInvokedCallback mBackToLauncherCallback;
private IRemoteAnimationRunner mBackToLauncherRunner;
private IDragAndDrop mDragAndDrop;
private IHomeTransitionListener mHomeTransitionListener;
private final HomeVisibilityState mHomeVisibilityState = new HomeVisibilityState();
// Used to dedupe calls to SystemUI
private int mLastShelfHeight;
@@ -269,7 +268,7 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle {
setBubblesListener(mBubblesListener);
registerSplitScreenListener(mSplitScreenListener);
registerSplitSelectListener(mSplitSelectListener);
setHomeTransitionListener(mHomeTransitionListener);
mHomeVisibilityState.init(mShellTransitions);
setStartingWindowListener(mStartingWindowListener);
setLauncherUnlockAnimationController(
mLauncherActivityClass, mLauncherUnlockAnimationController);
@@ -454,6 +453,17 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle {
}
}
@Override
public void setOverrideHomeButtonLongPress(long duration, float slopMultiplier) {
if (mSystemUiProxy != null) {
try {
mSystemUiProxy.setOverrideHomeButtonLongPress(duration, slopMultiplier);
} catch (RemoteException e) {
Log.w(TAG, "Failed call setOverrideHomeButtonLongPress", e);
}
}
}
@Override
public void notifyAccessibilityButtonClicked(int displayId) {
if (mSystemUiProxy != null) {
@@ -808,6 +818,18 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle {
}
}
/**
* Tells SysUI to update the bubble bar location to the new location.
* @param location new location for the bubble bar
*/
public void setBubbleBarLocation(BubbleBarLocation location) {
try {
mBubbles.setBubbleBarLocation(location);
} catch (RemoteException e) {
Log.w(TAG, "Failed call setBubbleBarLocation");
}
}
//
// Splitscreen
//
@@ -1102,22 +1124,8 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle {
mRemoteTransitions.remove(remoteTransition);
}
public void setHomeTransitionListener(IHomeTransitionListener listener) {
if (!FeatureFlags.enableHomeTransitionListener()) {
return;
}
mHomeTransitionListener = listener;
if (mShellTransitions != null) {
try {
mShellTransitions.setHomeTransitionListener(listener);
} catch (RemoteException e) {
Log.w(TAG, "Failed call setHomeTransitionListener", e);
}
} else {
Log.w(TAG, "Unable to call setHomeTransitionListener because ShellTransitions is null");
}
public HomeVisibilityState getHomeVisibilityState() {
return mHomeVisibilityState;
}
/**
@@ -1558,7 +1566,7 @@ public class SystemUiProxy implements ISystemUiProxy, NavHandle {
pw.println("\tmSplitSelectListener=" + mSplitSelectListener);
pw.println("\tmOneHanded=" + mOneHanded);
pw.println("\tmShellTransitions=" + mShellTransitions);
pw.println("\tmHomeTransitionListener=" + mHomeTransitionListener);
pw.println("\tmHomeVisibilityState=" + mHomeVisibilityState);
pw.println("\tmStartingWindow=" + mStartingWindow);
pw.println("\tmStartingWindowListener=" + mStartingWindowListener);
pw.println("\tmSysuiUnlockAnimationController=" + mSysuiUnlockAnimationController);
@@ -34,6 +34,7 @@ import com.android.quickstep.InputConsumer;
import com.android.quickstep.NavHandle;
import com.android.quickstep.RecentsAnimationDeviceState;
import com.android.quickstep.TopTaskTracker;
import com.android.quickstep.util.AssistStateManager;
import com.android.systemui.shared.system.InputMonitorCompat;
/**
@@ -64,8 +65,9 @@ public class NavHandleLongPressInputConsumer extends DelegateInputConsumer {
super(delegate, inputMonitor);
mScreenWidth = DisplayController.INSTANCE.get(context).getInfo().currentSize.x;
mDeepPressEnabled = FeatureFlags.ENABLE_LPNH_DEEP_PRESS.get();
if (FeatureFlags.CUSTOM_LPNH_THRESHOLDS.get()) {
mLongPressTimeout = FeatureFlags.LPNH_TIMEOUT_MS.get();
AssistStateManager assistStateManager = AssistStateManager.INSTANCE.get(context);
if (assistStateManager.getLPNHDurationMillis().isPresent()) {
mLongPressTimeout = assistStateManager.getLPNHDurationMillis().get().intValue();
} else {
mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
}
@@ -153,7 +153,7 @@ public class AppPairsController {
IconCache iconCache = LauncherAppState.getInstance(mContext).getIconCache();
MODEL_EXECUTOR.execute(() -> {
newAppPair.getContents().forEach(member -> {
newAppPair.getAppContents().forEach(member -> {
member.title = "";
member.bitmap = iconCache.getDefaultIcon(newAppPair.user);
iconCache.getTitleAndIcon(member, member.usingLowResIcon());
@@ -52,6 +52,16 @@ public class AssistStateManager implements ResourceBasedOverride {
return Optional.empty();
}
/** Get the Launcher overridden long press duration to trigger Assistant. */
public Optional<Long> getLPNHDurationMillis() {
return Optional.empty();
}
/** Get the Launcher overridden long press touch slop multiplier to trigger Assistant. */
public Optional<Long> getLPNHCustomSlopMultiplier() {
return Optional.empty();
}
/** Return {@code true} if the Settings toggle is enabled. */
public boolean isSettingsAllEntrypointsEnabled() {
return false;
@@ -671,6 +671,7 @@ class SplitAnimationController(val splitSelectStateController: SplitSelectStateC
appIcon2,
dividerPos
)
floatingView.bringToFront()
// Launcher animation: animate the floating view, expanding to fill the display surface
progressUpdater.addUpdateListener(
@@ -35,7 +35,6 @@ import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_SINGLE_TASK
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_PENDINGINTENT;
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_SHORTCUT;
import static com.android.quickstep.util.SplitSelectDataHolder.SPLIT_TASK_TASK;
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import static com.android.wm.shell.common.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT;
import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
@@ -70,6 +69,7 @@ import android.window.RemoteTransition;
import android.window.TransitionInfo;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.launcher3.Launcher;
@@ -132,6 +132,7 @@ public class SplitSelectStateController {
private final StatsLogManager mStatsLogManager;
private final SystemUiProxy mSystemUiProxy;
private final StateManager mStateManager;
@Nullable
private SplitFromDesktopController mSplitFromDesktopController;
@Nullable
private DepthController mDepthController;
@@ -208,6 +209,9 @@ public class SplitSelectStateController {
mActivityBackCallback = null;
mAppPairsController.onDestroy();
mSplitSelectDataHolder.onDestroy();
if (mSplitFromDesktopController != null) {
mSplitFromDesktopController.onDestroy();
}
}
/**
@@ -643,7 +647,12 @@ public class SplitSelectStateController {
}
public void initSplitFromDesktopController(Launcher launcher) {
mSplitFromDesktopController = new SplitFromDesktopController(launcher);
initSplitFromDesktopController(new SplitFromDesktopController(launcher));
}
@VisibleForTesting
void initSplitFromDesktopController(SplitFromDesktopController controller) {
mSplitFromDesktopController = controller;
}
private RemoteTransition getShellRemoteTransition(int firstTaskId, int secondTaskId,
@@ -968,7 +977,6 @@ public class SplitSelectStateController {
@Override
public boolean onRequestSplitSelect(ActivityManager.RunningTaskInfo taskInfo,
int splitPosition, Rect taskBounds) {
if (!enableDesktopWindowingMode()) return false;
MAIN_EXECUTOR.execute(() -> enterSplitSelect(taskInfo, splitPosition,
taskBounds));
return true;
@@ -977,6 +985,11 @@ public class SplitSelectStateController {
SystemUiProxy.INSTANCE.get(mLauncher).registerSplitSelectListener(mSplitSelectListener);
}
void onDestroy() {
SystemUiProxy.INSTANCE.get(mLauncher).unregisterSplitSelectListener(
mSplitSelectListener);
}
/**
* Enter split select from desktop mode.
* @param taskInfo the desktop task to move to split stage
@@ -17,7 +17,6 @@
package com.android.quickstep.util;
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
@@ -197,8 +196,6 @@ public class SplitToWorkspaceController {
}
private boolean shouldIgnoreSecondSplitLaunch() {
return (!FeatureFlags.enableSplitContextually()
&& !enableDesktopWindowingMode())
|| !mController.isSplitSelectActive();
return !FeatureFlags.enableSplitContextually() || !mController.isSplitSelectActive();
}
}
@@ -26,7 +26,6 @@ import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
import static com.android.launcher3.LauncherState.SPRING_LOADED;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME;
import static com.android.window.flags.Flags.enableDesktopWindowingMode;
import android.annotation.TargetApi;
import android.content.Context;
@@ -265,11 +264,11 @@ public class LauncherRecentsView extends RecentsView<QuickstepLauncher, Launcher
@Override
public void onGestureAnimationEnd() {
DesktopVisibilityController desktopVisibilityController = null;
DesktopVisibilityController desktopVisibilityController =
mActivity.getDesktopVisibilityController();
boolean showDesktopApps = false;
GestureState.GestureEndTarget endTarget = null;
if (enableDesktopWindowingMode()) {
desktopVisibilityController = mActivity.getDesktopVisibilityController();
if (desktopVisibilityController != null) {
endTarget = mCurrentGestureEndTarget;
if (endTarget == GestureState.GestureEndTarget.LAST_TASK
&& desktopVisibilityController.areFreeformTasksVisible()) {
@@ -24,6 +24,7 @@ import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
@@ -107,6 +108,7 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
private MultiValueAlpha mMultiValueAlpha;
protected LinearLayout mActionButtons;
// The screenshot button is implemented as a Button in launcher3 and NexusLauncher, but is an
// ImageButton in go launcher (does not share a common class with Button). Take care when
// casting this.
@@ -151,7 +153,8 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mMultiValueAlpha = new MultiValueAlpha(findViewById(R.id.action_buttons), NUM_ALPHAS);
mActionButtons = findViewById(R.id.action_buttons);
mMultiValueAlpha = new MultiValueAlpha(mActionButtons, NUM_ALPHAS);
mMultiValueAlpha.setUpdateVisibility(true);
mScreenshotButton = findViewById(R.id.action_screenshot);
@@ -243,13 +246,13 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
/**
* Updates a batch of flags to hide and show actions buttons for tablet/non tablet case.
* @param isSmallScreen True if the current display is a small screen.
*/
public void updateForSmallScreen(boolean isSmallScreen) {
private void updateForIsTablet() {
assert mDp != null;
// Update flags to see if split button should be hidden.
updateSplitButtonHiddenFlags(FLAG_SMALL_SCREEN_HIDE_SPLIT, isSmallScreen);
updateSplitButtonHiddenFlags(FLAG_SMALL_SCREEN_HIDE_SPLIT, !mDp.isTablet);
// Update flags to see if save app pair button should be hidden.
updateAppPairButtonHiddenFlags(FLAG_SMALL_SCREEN_HIDE_APP_PAIR, isSmallScreen);
updateAppPairButtonHiddenFlags(FLAG_SMALL_SCREEN_HIDE_APP_PAIR, !mDp.isTablet);
}
/**
@@ -274,7 +277,10 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
mScreenshotButtonHiddenFlags &= ~flag;
}
int desiredVisibility = mScreenshotButtonHiddenFlags == 0 ? VISIBLE : GONE;
mScreenshotButton.setVisibility(desiredVisibility);
if (mScreenshotButton.getVisibility() != desiredVisibility) {
mScreenshotButton.setVisibility(desiredVisibility);
mActionButtons.requestLayout();
}
}
/**
@@ -292,8 +298,10 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
mSplitButtonHiddenFlags &= ~flag;
}
int desiredVisibility = mSplitButtonHiddenFlags == 0 ? VISIBLE : GONE;
mSplitButton.setVisibility(desiredVisibility);
findViewById(R.id.action_split_space).setVisibility(desiredVisibility);
if (mSplitButton.getVisibility() != desiredVisibility) {
mSplitButton.setVisibility(desiredVisibility);
mActionButtons.requestLayout();
}
}
/**
@@ -315,7 +323,10 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
mAppPairButtonHiddenFlags &= ~flag;
}
int desiredVisibility = mAppPairButtonHiddenFlags == 0 ? VISIBLE : GONE;
mSaveAppPairButton.setVisibility(desiredVisibility);
if (mSaveAppPairButton.getVisibility() != desiredVisibility) {
mSaveAppPairButton.setVisibility(desiredVisibility);
mActionButtons.requestLayout();
}
}
public MultiProperty getContentAlpha() {
@@ -342,7 +353,7 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
* Returns the visibility of the overview actions buttons.
*/
public @Visibility int getActionsButtonVisibility() {
return findViewById(R.id.action_buttons).getVisibility();
return mActionButtons.getVisibility();
}
/**
@@ -358,8 +369,7 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
if (mDp == null) {
return;
}
LayoutParams actionParams = (LayoutParams) findViewById(
R.id.action_buttons).getLayoutParams();
LayoutParams actionParams = (LayoutParams) mActionButtons.getLayoutParams();
actionParams.setMargins(
actionParams.leftMargin, mDp.overviewActionsTopMarginPx,
actionParams.rightMargin, getBottomMargin());
@@ -386,6 +396,7 @@ public class OverviewActionsView<T extends OverlayUICallbacks> extends FrameLayo
mDp = dp;
mTaskSize.set(taskSize);
updateVerticalMargin(DisplayController.getNavigationMode(getContext()));
updateForIsTablet();
requestLayout();
@@ -1061,6 +1061,8 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
@Nullable DesktopRecentsTransitionController desktopRecentsTransitionController) {
mActionsView = actionsView;
mActionsView.updateHiddenFlags(HIDDEN_NO_TASKS, getTaskViewCount() == 0);
// Update flags for 1p/3p launchers
mActionsView.updateFor3pLauncher(!supportsAppPairs());
mSplitSelectStateController = splitController;
mDesktopRecentsTransitionController = desktopRecentsTransitionController;
}
@@ -3962,15 +3964,9 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
// Update flags to see if actions bar should show buttons for a single task or a pair of
// tasks.
mActionsView.updateForGroupedTask(isCurrentSplit);
// Update flags to see if actions bar should show buttons for tablets or phones.
mActionsView.updateForSmallScreen(!mActivity.getDeviceProfile().isTablet);
// Update flags for 1p/3p launchers
mActionsView.updateFor3pLauncher(!supportsAppPairs());
if (enableDesktopWindowingMode()) {
boolean isCurrentDesktop = getCurrentPageTaskView() instanceof DesktopTaskView;
mActionsView.updateHiddenFlags(HIDDEN_DESKTOP, isCurrentDesktop);
}
boolean isCurrentDesktop = getCurrentPageTaskView() instanceof DesktopTaskView;
mActionsView.updateHiddenFlags(HIDDEN_DESKTOP, isCurrentDesktop);
}
/** Returns if app pairs are supported in this launcher. Overridden in subclasses. */
@@ -4692,9 +4688,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
mSplitSelectStateController.setAnimateCurrentTaskDismissal(
true /*animateCurrentTaskDismissal*/);
mSplitHiddenTaskViewIndex = indexOfChild(taskView);
if (enableDesktopWindowingMode()) {
updateDesktopTaskVisibility(false /* visible */);
}
updateDesktopTaskVisibility(false /* visible */);
}
/**
@@ -4716,9 +4710,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
mSplitSelectStateController.setInitialTaskSelect(splitSelectSource.intent,
splitSelectSource.position.stagePosition, splitSelectSource.itemInfo,
splitSelectSource.splitEvent, splitSelectSource.alreadyRunningTaskId);
if (enableDesktopWindowingMode()) {
updateDesktopTaskVisibility(false /* visible */);
}
updateDesktopTaskVisibility(false /* visible */);
}
private void updateDesktopTaskVisibility(boolean visible) {
@@ -4922,9 +4914,7 @@ public abstract class RecentsView<ACTIVITY_TYPE extends StatefulActivity<STATE_T
mSplitHiddenTaskView.setThumbnailVisibility(VISIBLE, INVALID_TASK_ID);
mSplitHiddenTaskView = null;
}
if (enableDesktopWindowingMode()) {
updateDesktopTaskVisibility(true /* visible */);
}
updateDesktopTaskVisibility(true /* visible */);
}
private void safeRemoveDragLayerView(@Nullable View viewToRemove) {
@@ -36,6 +36,7 @@ import com.android.launcher3.util.ComponentKey
import com.android.launcher3.util.SplitConfigurationOptions
import com.android.quickstep.RecentsModel
import com.android.quickstep.SystemUiProxy
import com.android.quickstep.util.SplitSelectStateController.SplitFromDesktopController
import com.android.systemui.shared.recents.model.Task
import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50
import java.util.function.Consumer
@@ -65,6 +66,7 @@ class SplitSelectStateControllerTest {
private val context: StatefulActivity<*> = mock()
private val recentsModel: RecentsModel = mock()
private val pendingIntent: PendingIntent = mock()
private val splitFromDesktopController: SplitFromDesktopController = mock()
private lateinit var splitSelectStateController: SplitSelectStateController
@@ -607,6 +609,18 @@ class SplitSelectStateControllerTest {
assertTrue(splitSelectStateController.isBothSplitAppsConfirmed)
}
@Test
fun splitSelectStateControllerDestroyed_SplitFromDesktopControllerAlsoDestroyed() {
// Initiate split from desktop controller
splitSelectStateController.initSplitFromDesktopController(splitFromDesktopController)
// Simulate default controller being destroyed
splitSelectStateController.onDestroy()
// Verify desktop controller is also destroyed
verify(splitFromDesktopController).onDestroy()
}
// Generate GroupTask with default userId.
private fun generateGroupTask(
task1ComponentName: ComponentName,
@@ -0,0 +1,221 @@
/*
* Copyright (C) 2024 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.model
import android.app.prediction.AppTarget
import android.app.prediction.AppTargetEvent
import android.app.prediction.AppTargetId
import android.appwidget.AppWidgetProviderInfo
import android.content.ComponentName
import android.content.Context
import android.os.Process.myUserHandle
import android.os.UserHandle
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.android.launcher3.DeviceProfile
import com.android.launcher3.InvariantDeviceProfile
import com.android.launcher3.LauncherAppState
import com.android.launcher3.icons.IconCache
import com.android.launcher3.model.WidgetPredictionsRequester.buildBundleForPredictionSession
import com.android.launcher3.model.WidgetPredictionsRequester.filterPredictions
import com.android.launcher3.model.WidgetPredictionsRequester.notOnUiSurfaceFilter
import com.android.launcher3.util.ActivityContextWrapper
import com.android.launcher3.util.PackageUserKey
import com.android.launcher3.util.WidgetUtils.createAppWidgetProviderInfo
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo
import com.google.common.truth.Truth.assertThat
import java.util.function.Predicate
import junit.framework.Assert.assertNotNull
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.MockitoAnnotations
@RunWith(AndroidJUnit4::class)
class WidgetsPredictionsRequesterTest {
private lateinit var mUserHandle: UserHandle
private lateinit var context: Context
private lateinit var deviceProfile: DeviceProfile
private lateinit var testInvariantProfile: InvariantDeviceProfile
private lateinit var widget1aInfo: AppWidgetProviderInfo
private lateinit var widget1bInfo: AppWidgetProviderInfo
private lateinit var widget2Info: AppWidgetProviderInfo
private lateinit var widgetItem1a: WidgetItem
private lateinit var widgetItem1b: WidgetItem
private lateinit var widgetItem2: WidgetItem
private lateinit var allWidgets: Map<PackageUserKey, List<WidgetItem>>
@Mock private lateinit var iconCache: IconCache
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
mUserHandle = myUserHandle()
context = ActivityContextWrapper(ApplicationProvider.getApplicationContext())
testInvariantProfile = LauncherAppState.getIDP(context)
deviceProfile = testInvariantProfile.getDeviceProfile(context).copy(context)
widget1aInfo =
createAppWidgetProviderInfo(
ComponentName.createRelative(APP_1_PACKAGE_NAME, APP_1_PROVIDER_A_CLASS_NAME)
)
widget1bInfo =
createAppWidgetProviderInfo(
ComponentName.createRelative(APP_1_PACKAGE_NAME, APP_1_PROVIDER_B_CLASS_NAME)
)
widgetItem1a = createWidgetItem(widget1aInfo)
widgetItem1b = createWidgetItem(widget1bInfo)
widget2Info =
createAppWidgetProviderInfo(
ComponentName.createRelative(APP_2_PACKAGE_NAME, APP_2_PROVIDER_1_CLASS_NAME)
)
widgetItem2 = createWidgetItem(widget2Info)
allWidgets =
mapOf(
PackageUserKey(APP_1_PACKAGE_NAME, mUserHandle) to
listOf(widgetItem1a, widgetItem1b),
PackageUserKey(APP_2_PACKAGE_NAME, mUserHandle) to listOf(widgetItem2),
)
}
@Test
fun buildBundleForPredictionSession_includesAddedAppWidgets() {
val existingWidgets = arrayListOf(widget1aInfo, widget1bInfo, widget2Info)
val bundle = buildBundleForPredictionSession(existingWidgets, TEST_UI_SURFACE)
val addedWidgetsBundleExtra =
bundle.getParcelableArrayList(BUNDLE_KEY_ADDED_APP_WIDGETS, AppTarget::class.java)
assertNotNull(addedWidgetsBundleExtra)
assertThat(addedWidgetsBundleExtra)
.containsExactly(
buildExpectedAppTargetEvent(
/*pkg=*/ APP_1_PACKAGE_NAME,
/*providerClassName=*/ APP_1_PROVIDER_A_CLASS_NAME,
/*user=*/ mUserHandle
),
buildExpectedAppTargetEvent(
/*pkg=*/ APP_1_PACKAGE_NAME,
/*providerClassName=*/ APP_1_PROVIDER_B_CLASS_NAME,
/*user=*/ mUserHandle
),
buildExpectedAppTargetEvent(
/*pkg=*/ APP_2_PACKAGE_NAME,
/*providerClassName=*/ APP_2_PROVIDER_1_CLASS_NAME,
/*user=*/ mUserHandle
)
)
}
@Test
fun filterPredictions_notOnUiSurfaceFilter_returnsOnlyEligiblePredictions() {
val widgetsAlreadyOnSurface = arrayListOf(widget1bInfo)
val filter: Predicate<WidgetItem> = notOnUiSurfaceFilter(widgetsAlreadyOnSurface)
val predictions =
listOf(
// already on surface
AppTarget(
AppTargetId(APP_1_PACKAGE_NAME),
APP_1_PACKAGE_NAME,
APP_1_PROVIDER_B_CLASS_NAME,
mUserHandle
),
// eligible
AppTarget(
AppTargetId(APP_2_PACKAGE_NAME),
APP_2_PACKAGE_NAME,
APP_2_PROVIDER_1_CLASS_NAME,
mUserHandle
)
)
// only 2 was eligible
assertThat(filterPredictions(predictions, allWidgets, filter)).containsExactly(widgetItem2)
}
@Test
fun filterPredictions_appPredictions_returnsWidgetFromPackage() {
val widgetsAlreadyOnSurface = arrayListOf(widget1bInfo)
val filter: Predicate<WidgetItem> = notOnUiSurfaceFilter(widgetsAlreadyOnSurface)
val predictions =
listOf(
AppTarget(
AppTargetId(APP_1_PACKAGE_NAME),
APP_1_PACKAGE_NAME,
"$APP_1_PACKAGE_NAME.SomeActivity",
mUserHandle
),
AppTarget(
AppTargetId(APP_2_PACKAGE_NAME),
APP_2_PACKAGE_NAME,
"$APP_2_PACKAGE_NAME.SomeActivity2",
mUserHandle
),
)
assertThat(filterPredictions(predictions, allWidgets, filter))
.containsExactly(widgetItem1a, widgetItem2)
}
private fun createWidgetItem(
providerInfo: AppWidgetProviderInfo,
): WidgetItem {
val widgetInfo = LauncherAppWidgetProviderInfo.fromProviderInfo(context, providerInfo)
return WidgetItem(widgetInfo, testInvariantProfile, iconCache, context)
}
companion object {
const val TEST_UI_SURFACE = "widgets_test"
const val BUNDLE_KEY_ADDED_APP_WIDGETS = "added_app_widgets"
const val APP_1_PACKAGE_NAME = "com.example.app1"
const val APP_1_PROVIDER_A_CLASS_NAME = "app1Provider1"
const val APP_1_PROVIDER_B_CLASS_NAME = "app1Provider2"
const val APP_2_PACKAGE_NAME = "com.example.app2"
const val APP_2_PROVIDER_1_CLASS_NAME = "app2Provider1"
const val TEST_PACKAGE = "pkg"
private fun buildExpectedAppTargetEvent(
pkg: String,
providerClassName: String,
userHandle: UserHandle
): AppTargetEvent {
val appTarget =
AppTarget.Builder(
/*id=*/ AppTargetId("widget:$pkg"),
/*packageName=*/ pkg,
/*user=*/ userHandle
)
.setClassName(providerClassName)
.build()
return AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_PIN)
.setLaunchLocation(TEST_UI_SURFACE)
.build()
}
}
}
@@ -15,6 +15,8 @@
*/
package com.android.quickstep;
import static com.android.launcher3.util.rule.TestStabilityRule.LOCAL;
import static com.android.launcher3.util.rule.TestStabilityRule.PLATFORM_POSTSUBMIT;
import static com.android.quickstep.TaskbarModeSwitchRule.Mode.PERSISTENT;
import android.graphics.Rect;
@@ -23,6 +25,7 @@ import androidx.test.filters.LargeTest;
import androidx.test.runner.AndroidJUnit4;
import com.android.launcher3.ui.PortraitLandscapeRunner.PortraitLandscape;
import com.android.launcher3.util.rule.TestStabilityRule;
import com.android.quickstep.NavigationModeSwitchRule.NavigationModeSwitch;
import com.android.quickstep.TaskbarModeSwitchRule.TaskbarModeSwitch;
@@ -45,6 +48,7 @@ public class TaplTestsPersistentTaskbar extends AbstractTaplTestsTaskbar {
@Test
@NavigationModeSwitch(mode = NavigationModeSwitchRule.Mode.THREE_BUTTON)
@TestStabilityRule.Stability(flavors = LOCAL | PLATFORM_POSTSUBMIT)
public void testThreeButtonsTaskbarBoundsAfterConfigChangeDuringIme() {
Rect taskbarBoundsBefore = getTaskbar().getVisibleBounds();
// Go home and to an IME activity (any configuration change would do, as long as it
+39
View File
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (C) 2024 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.apppairs.AppPairIcon
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:launcher="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:focusable="true"
launcher:iconDisplay="folder" >
<com.android.launcher3.apppairs.AppPairIconGraphic
android:id="@+id/app_pair_icon_graphic"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="false" />
<com.android.launcher3.BubbleTextView
style="@style/BaseIcon"
android:id="@+id/app_pair_icon_name"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:focusable="false"
android:layout_gravity="top"
android:textColor="?attr/folderTextColor"
launcher:iconDisplay="folder" />
</com.android.launcher3.apppairs.AppPairIcon>
-8
View File
@@ -35,14 +35,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>
<FrameLayout
-1
View File
@@ -277,7 +277,6 @@
<!-- Sizes for managed profile badges -->
<dimen name="profile_badge_size">24dp</dimen>
<dimen name="profile_badge_margin">5dp</dimen>
<dimen name="profile_badge_minimum_top">2dp</dimen>
<!-- Shadows and outlines -->
+5 -1
View File
@@ -680,7 +680,7 @@ public class Launcher extends StatefulActivity<LauncherState>
@Override
public void onBackCancelled() {
mStateManager.getState().onBackCancelled(Launcher.this);
Launcher.this.onBackCancelled();
}
};
}
@@ -2086,6 +2086,10 @@ public class Launcher extends StatefulActivity<LauncherState>
mStateManager.getState().onBackInvoked(this);
}
protected void onBackCancelled() {
mStateManager.getState().onBackCancelled(this);
}
protected void onScreenOnChanged(boolean isOn) {
// Reset AllApps to its initial state only if we are not in the middle of
// processing a multi-step drop
+10 -13
View File
@@ -17,7 +17,7 @@
package com.android.launcher3;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
import static com.android.launcher3.LauncherState.ALL_APPS;
import static com.android.launcher3.LauncherState.EDIT_MODE;
import static com.android.launcher3.LauncherState.FLAG_MULTI_PAGE;
@@ -1873,12 +1873,9 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
return false;
}
boolean aboveShortcut = (dropOverView.getTag() instanceof WorkspaceItemInfo
&& ((WorkspaceItemInfo) dropOverView.getTag()).container
!= LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION);
boolean willBecomeShortcut =
(info.itemType == ITEM_TYPE_APPLICATION ||
info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT);
boolean aboveShortcut = Folder.willAccept(dropOverView.getTag())
&& ((ItemInfo) dropOverView.getTag()).container != CONTAINER_HOTSEAT_PREDICTION;
boolean willBecomeShortcut = Folder.willAcceptItemType(info.itemType);
return (aboveShortcut && willBecomeShortcut);
}
@@ -1925,12 +1922,12 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
mCreateUserFolderOnDrop = false;
final int screenId = getCellLayoutId(target);
boolean aboveShortcut = (v.getTag() instanceof WorkspaceItemInfo);
boolean willBecomeShortcut = (newView.getTag() instanceof WorkspaceItemInfo);
boolean aboveShortcut = Folder.willAccept(v.getTag());
boolean willBecomeShortcut = Folder.willAccept(newView.getTag());
if (aboveShortcut && willBecomeShortcut) {
WorkspaceItemInfo sourceInfo = (WorkspaceItemInfo) newView.getTag();
WorkspaceItemInfo destInfo = (WorkspaceItemInfo) v.getTag();
ItemInfo sourceInfo = (ItemInfo) newView.getTag();
ItemInfo destInfo = (ItemInfo) v.getTag();
// if the drag started here, we need to remove it from the workspace
if (!external) {
getParentCellLayoutForView(mDragInfo.cell).removeView(mDragInfo.cell);
@@ -3314,7 +3311,7 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
}
} else if (child instanceof FolderIcon) {
FolderInfo folderInfo = (FolderInfo) info;
List<WorkspaceItemInfo> matches = folderInfo.getContents().stream()
List<ItemInfo> matches = folderInfo.getContents().stream()
.filter(matcher)
.collect(Collectors.toList());
if (!matches.isEmpty()) {
@@ -3381,7 +3378,7 @@ public class Workspace<T extends View & PageIndicator> extends PagedView<T>
FolderInfo fi = (FolderInfo) info;
if (fi.anyMatch(matcher)) {
FolderDotInfo folderDotInfo = new FolderDotInfo();
for (WorkspaceItemInfo si : fi.getContents()) {
for (ItemInfo si : fi.getContents()) {
folderDotInfo.addDotInfo(mLauncher.getDotInfoForItem(si));
}
((FolderIcon) v).setDotInfo(folderDotInfo);
@@ -23,6 +23,7 @@ import android.view.View;
import com.android.launcher3.CellLayout;
import com.android.launcher3.R;
import com.android.launcher3.accessibility.BaseAccessibilityDelegate.DragType;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
@@ -117,7 +118,7 @@ public class WorkspaceAccessibilityHelper extends DragAndDropAccessibilityDelega
return mContext.getString(R.string.item_moved);
} else {
ItemInfo info = (ItemInfo) child.getTag();
if (info instanceof AppInfo || info instanceof WorkspaceItemInfo) {
if (Folder.willAccept(info)) {
return mContext.getString(R.string.folder_created);
} else if (info instanceof FolderInfo) {
@@ -148,8 +149,8 @@ public class WorkspaceAccessibilityHelper extends DragAndDropAccessibilityDelega
if (TextUtils.isEmpty(info.title)) {
// Find the first item in the folder.
FolderInfo folder = (FolderInfo) info;
WorkspaceItemInfo firstItem = null;
for (WorkspaceItemInfo shortcut : folder.getContents()) {
ItemInfo firstItem = null;
for (ItemInfo shortcut : folder.getContents()) {
if (firstItem == null || firstItem.rank > shortcut.rank) {
firstItem = shortcut;
}
@@ -85,6 +85,7 @@ import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.pm.UserCache;
import com.android.launcher3.recyclerview.AllAppsRecyclerViewPool;
import com.android.launcher3.util.ItemInfoMatcher;
import com.android.launcher3.util.Preconditions;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
@@ -1366,6 +1367,18 @@ public class ActivityAllAppsContainerView<T extends Context & ActivityContext>
invalidateHeader();
}
/**
* Set {@link Animator.AnimatorListener} on {@link mAllAppsTransitionController} to observe
* animation of backing out of all apps search view to all apps view.
*/
public void setAllAppsSearchBackAnimatorListener(Animator.AnimatorListener listener) {
Preconditions.assertNotNull(mAllAppsTransitionController);
if (mAllAppsTransitionController == null) {
return;
}
mAllAppsTransitionController.setAllAppsSearchBackAnimationListener(listener);
}
public void setScrimView(ScrimView scrimView) {
mScrimView = scrimView;
}
@@ -44,6 +44,7 @@ import android.view.View;
import android.view.animation.Interpolator;
import androidx.annotation.FloatRange;
import androidx.annotation.Nullable;
import com.android.app.animation.Interpolators;
import com.android.launcher3.DeviceProfile;
@@ -167,6 +168,8 @@ public class AllAppsTransitionController
private final AnimatedFloat mAllAppScale = new AnimatedFloat(this::onScaleProgressChanged);
private final int mNavScrimFlag;
@Nullable private Animator.AnimatorListener mAllAppsSearchBackAnimationListener;
private boolean mIsVerticalLayout;
// Animation in this class is controlled by a single variable {@link mProgress}.
@@ -312,11 +315,25 @@ public class AllAppsTransitionController
}
}
/** Animate all apps view to 1f scale. */
/** Set {@link Animator.AnimatorListener} for scaling all apps scale to 1 animation. */
public void setAllAppsSearchBackAnimationListener(Animator.AnimatorListener listener) {
mAllAppsSearchBackAnimationListener = listener;
}
/**
* Animate all apps view to 1f scale. This is called when backing (exiting) from all apps
* search view to all apps view.
*/
public void animateAllAppsToNoScale() {
mAllAppScale.animateToValue(1f)
.setDuration(REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS)
.start();
if (mAllAppScale.isAnimating()) {
return;
}
Animator animator = mAllAppScale.animateToValue(1f)
.setDuration(REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS);
if (mAllAppsSearchBackAnimationListener != null) {
animator.addListener(mAllAppsSearchBackAnimationListener);
}
animator.start();
}
/**
@@ -30,11 +30,13 @@ import androidx.annotation.Nullable;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.LauncherAppState;
import com.android.launcher3.R;
import com.android.launcher3.Reorderable;
import com.android.launcher3.dragndrop.DraggableView;
import com.android.launcher3.icons.IconCache;
import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.util.MultiTranslateDelegate;
import com.android.launcher3.views.ActivityContext;
@@ -179,10 +181,18 @@ public class AppPairIcon extends FrameLayout implements DraggableView, Reorderab
return mIconGraphic;
}
/**
* Ensures that both app icons in the pair are loaded in high resolution.
*/
public void verifyHighRes() {
IconCache iconCache = LauncherAppState.getInstance(getContext()).getIconCache();
getInfo().fetchHiResIconsIfNeeded(iconCache);
}
/**
* Called when WorkspaceItemInfos get updated, and the app pair icon may need to be redrawn.
*/
public void maybeRedrawForWorkspaceUpdate(Predicate<WorkspaceItemInfo> itemCheck) {
public void maybeRedrawForWorkspaceUpdate(Predicate<ItemInfo> itemCheck) {
// If either of the app pair icons return true on the predicate (i.e. in the list of
// updated apps), redraw the icon graphic (icon background and both icons).
if (getInfo().anyMatch(itemCheck)) {
@@ -32,7 +32,7 @@ import com.android.launcher3.icons.FastBitmapDrawable;
* A composed Drawable consisting of the two app pair icons and the background behind them (looks
* like two rectangles).
*/
class AppPairIconDrawable extends Drawable {
public class AppPairIconDrawable extends Drawable {
private final Paint mBackgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private final AppPairIconDrawingParams mP;
private final FastBitmapDrawable mIcon1;
@@ -102,6 +102,7 @@ class AppPairIconDrawable extends Drawable {
}
mIcon2.draw(canvas);
canvas.restore();
}
/**
@@ -205,4 +206,14 @@ class AppPairIconDrawable extends Drawable {
public void setColorFilter(ColorFilter colorFilter) {
mBackgroundPaint.setColorFilter(colorFilter);
}
@Override
public int getIntrinsicWidth() {
return mP.getIconSize();
}
@Override
public int getIntrinsicHeight() {
return mP.getIconSize();
}
}
@@ -77,22 +77,29 @@ class AppPairIconDrawingParams(val context: Context, container: Int) {
innerPadding = iconSize * INNER_PADDING_SCALE
memberIconSize = iconSize * MEMBER_ICON_SCALE
updateOrientation(dp)
if (container == DISPLAY_FOLDER) {
val ta =
context.theme.obtainStyledAttributes(
intArrayOf(R.attr.materialColorSurfaceContainerLowest)
)
bgColor = ta.getColor(0, 0)
ta.recycle()
} else {
val ta = context.theme.obtainStyledAttributes(R.styleable.FolderIconPreview)
bgColor = ta.getColor(R.styleable.FolderIconPreview_folderPreviewColor, 0)
ta.recycle()
}
bgColor = getBgColorForContainer(container)
}
/** Checks the device orientation and updates isLeftRightSplit accordingly. */
fun updateOrientation(dp: DeviceProfile) {
isLeftRightSplit = dp.isLeftRightSplit
}
private fun getBgColorForContainer(container: Int): Int {
val color: Int
if (container == DISPLAY_FOLDER) {
val ta =
context.theme.obtainStyledAttributes(
intArrayOf(R.attr.materialColorSurfaceContainerLowest)
)
color = ta.getColor(0, 0)
ta.recycle()
} else {
val ta = context.theme.obtainStyledAttributes(R.styleable.FolderIconPreview)
color = ta.getColor(R.styleable.FolderIconPreview_folderPreviewColor, 0)
ta.recycle()
}
return color
}
}
@@ -19,7 +19,6 @@ package com.android.launcher3.apppairs
import android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.Gravity
import android.widget.FrameLayout
@@ -52,7 +51,10 @@ constructor(context: Context, attrs: AttributeSet? = null) :
* 2) One of the member apps can't be launched due to screen size requirements.
*/
@JvmStatic
fun composeDrawable(appPairInfo: AppPairInfo, p: AppPairIconDrawingParams): Drawable {
fun composeDrawable(
appPairInfo: AppPairInfo,
p: AppPairIconDrawingParams
): AppPairIconDrawable {
// Generate new icons, using themed flag if needed.
val flags = if (Themes.isThemedIconEnabled(p.context)) BitmapInfo.FLAG_THEMED else 0
val appIcon1 = appPairInfo.getFirstApp().newIcon(p.context, flags)
@@ -81,7 +83,7 @@ constructor(context: Context, attrs: AttributeSet? = null) :
private lateinit var parentIcon: AppPairIcon
private lateinit var drawParams: AppPairIconDrawingParams
private lateinit var drawable: Drawable
lateinit var drawable: AppPairIconDrawable
fun init(icon: AppPairIcon, container: Int) {
parentIcon = icon
@@ -116,12 +118,6 @@ constructor(context: Context, attrs: AttributeSet? = null) :
redraw()
}
/** Updates the icon drawable and redraws it */
fun redraw() {
drawable = composeDrawable(parentIcon.info, drawParams)
invalidate()
}
/**
* Gets this icon graphic's visual bounds, with respect to the parent icon's coordinate system.
*/
@@ -136,6 +132,12 @@ constructor(context: Context, attrs: AttributeSet? = null) :
)
}
/** Updates the icon drawable and redraws it */
fun redraw() {
drawable = composeDrawable(parentIcon.info, drawParams)
invalidate()
}
override fun dispatchDraw(canvas: Canvas) {
super.dispatchDraw(canvas)
drawable.draw(canvas)
@@ -132,7 +132,16 @@ public final class FeatureFlags {
public static final BooleanFlag CUSTOM_LPNH_THRESHOLDS =
getReleaseFlag(301680992, "CUSTOM_LPNH_THRESHOLDS", ENABLED,
"Add dev options to customize the LPNH trigger slop and milliseconds");
"Add dev options and server side control to customize the LPNH "
+ "trigger slop and milliseconds");
public static final BooleanFlag CUSTOM_LPH_THRESHOLDS = getReleaseFlag(331800576,
"CUSTOM_LPH_THRESHOLDS", DISABLED,
"Server side control to customize LPH timeout and touch slop");
public static final BooleanFlag OVERRIDE_LPNH_LPH_THRESHOLDS = getReleaseFlag(331799727,
"OVERRIDE_LPNH_LPH_THRESHOLDS", DISABLED,
"Enable AGSA override for LPNH and LPH timeout and touch slop");
public static final BooleanFlag ANIMATE_LPNH =
getReleaseFlag(308693847, "ANIMATE_LPNH", TEAMFOOD,
+49 -33
View File
@@ -19,6 +19,9 @@ package com.android.launcher3.folder;
import static android.text.TextUtils.isEmpty;
import static com.android.launcher3.LauncherAnimUtils.SPRING_LOADED_EXIT_DELAY;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR;
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
import static com.android.launcher3.LauncherState.EDIT_MODE;
import static com.android.launcher3.LauncherState.NORMAL;
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
@@ -66,14 +69,12 @@ import androidx.core.content.res.ResourcesCompat;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.Alarm;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.CellLayout;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.DragSource;
import com.android.launcher3.DropTarget;
import com.android.launcher3.ExtendedEditText;
import com.android.launcher3.Launcher;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.OnAlarmListener;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
@@ -166,6 +167,22 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
private static final Rect sTempRect = new Rect();
private static final int MIN_FOLDERS_FOR_HARDWARE_OPTIMIZATION = 10;
/**
* Checks if {@code o} is an {@link ItemInfo} type that can be placed in folders.
*/
public static boolean willAccept(Object o) {
return o instanceof ItemInfo info && willAcceptItemType(info.itemType);
}
/**
* Checks if {@code itemType} is a type that can be placed in folders.
*/
public static boolean willAcceptItemType(int itemType) {
return itemType == ITEM_TYPE_APPLICATION
|| itemType == ITEM_TYPE_DEEP_SHORTCUT
|| itemType == ITEM_TYPE_APP_PAIR;
}
private final Alarm mReorderAlarm = new Alarm(Looper.getMainLooper());
private final Alarm mOnExitAlarm = new Alarm(Looper.getMainLooper());
private final Alarm mOnScrollHintAlarm = new Alarm(Looper.getMainLooper());
@@ -313,9 +330,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
public boolean startDrag(View v, DragOptions options) {
Object tag = v.getTag();
if (tag instanceof WorkspaceItemInfo) {
WorkspaceItemInfo item = (WorkspaceItemInfo) tag;
if (tag instanceof ItemInfo item) {
mEmptyCellRank = item.rank;
mCurrentDragView = v;
@@ -346,14 +361,12 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
}
mContent.removeItem(mCurrentDragView);
if (dragObject.dragInfo instanceof WorkspaceItemInfo) {
mItemsInvalidated = true;
mItemsInvalidated = true;
// We do not want to get events for the item being removed, as they will get handled
// when the drop completes
try (SuppressInfoChanges s = new SuppressInfoChanges()) {
mInfo.remove((WorkspaceItemInfo) dragObject.dragInfo, true);
}
// We do not want to get events for the item being removed, as they will get handled
// when the drop completes
try (SuppressInfoChanges s = new SuppressInfoChanges()) {
mInfo.remove(dragObject.dragInfo, true);
}
mDragInProgress = true;
mItemAddedBackToSelfViaIcon = false;
@@ -493,7 +506,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
mInfo = info;
mFromTitle = info.title;
mFromLabelState = info.getFromLabelState();
ArrayList<WorkspaceItemInfo> children = info.getContents();
ArrayList<ItemInfo> children = info.getContents();
Collections.sort(children, ITEM_POS_COMPARATOR);
updateItemLocationsInDatabaseBatch(true);
@@ -626,7 +639,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
// onDropComplete. Perform cleanup once drag-n-drop ends.
mDragController.addDragListener(this);
ArrayList<WorkspaceItemInfo> items = new ArrayList<>(mInfo.getContents());
ArrayList<ItemInfo> items = new ArrayList<>(mInfo.getContents());
mEmptyCellRank = items.size();
items.add(null); // Add an empty spot at the end
@@ -647,7 +660,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
* is animated relative to the specified View. If the View is null, no animation
* is played.
*/
private void animateOpen(List<WorkspaceItemInfo> items, int pageNo) {
private void animateOpen(List<ItemInfo> items, int pageNo) {
if (items == null || items.size() <= 1) {
Log.d(TAG, "Couldn't animate folder open because items is: " + items);
return;
@@ -896,8 +909,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
public boolean acceptDrop(DragObject d) {
final ItemInfo item = d.dragInfo;
final int itemType = item.itemType;
return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT));
return Folder.willAcceptItemType(itemType);
}
public void onDragEnter(DragObject d) {
@@ -1050,7 +1062,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
}
} else {
// The drag failed, we need to return the item to the folder
WorkspaceItemInfo info = (WorkspaceItemInfo) d.dragInfo;
ItemInfo info = d.dragInfo;
View icon = (mCurrentDragView != null && mCurrentDragView.getTag() == info)
? mCurrentDragView : mContent.createNewView(info);
ArrayList<View> views = getIconsInReadingOrder();
@@ -1099,7 +1111,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
ArrayList<ItemInfo> items = new ArrayList<>();
int total = mInfo.getContents().size();
for (int i = 0; i < total; i++) {
WorkspaceItemInfo itemInfo = mInfo.getContents().get(i);
ItemInfo itemInfo = mInfo.getContents().get(i);
if (verifier.updateRankAndPos(itemInfo, i)) {
items.add(itemInfo);
}
@@ -1112,8 +1124,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
Executors.MODEL_EXECUTOR.post(() -> {
FolderNameInfos nameInfos = new FolderNameInfos();
FolderNameProvider fnp = FolderNameProvider.newInstance(getContext());
fnp.getSuggestedFolderName(
getContext(), mInfo.getContents(), nameInfos);
fnp.getSuggestedFolderName(getContext(), mInfo.getAppContents(), nameInfos);
mInfo.suggestedFolderNames = nameInfos;
});
}
@@ -1298,15 +1309,15 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
d.deferDragViewCleanupPostAnimation = false;
mRearrangeOnClose = true;
} else {
final WorkspaceItemInfo si;
final ItemInfo si;
if (pasiSi != null) {
si = pasiSi;
} else if (d.dragInfo instanceof WorkspaceItemFactory) {
// Came from all apps -- make a copy.
si = ((WorkspaceItemFactory) d.dragInfo).makeWorkspaceItem(launcher);
} else {
// WorkspaceItemInfo
si = (WorkspaceItemInfo) d.dragInfo;
// WorkspaceItemInfo or AppPairInfo
si = d.dragInfo;
}
View currentDragView;
@@ -1314,7 +1325,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
currentDragView = mContent.createAndAddViewForRank(si, mEmptyCellRank);
// Actually move the item in the database if it was an external drag. Call this
// before creating the view, so that WorkspaceItemInfo is updated appropriately.
// before creating the view, so that the ItemInfo is updated appropriately.
mLauncherDelegate.getModelWriter().addOrMoveItemInDatabase(
si, mInfo.id, 0, si.cellX, si.cellY);
mIsExternalDrag = false;
@@ -1376,14 +1387,14 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
// This is used so the item doesn't immediately appear in the folder when added. In one case
// we need to create the illusion that the item isn't added back to the folder yet, to
// to correspond to the animation of the icon back into the folder. This is
public void hideItem(WorkspaceItemInfo info) {
public void hideItem(ItemInfo info) {
View v = getViewForInfo(info);
if (v != null) {
v.setVisibility(INVISIBLE);
}
}
public void showItem(WorkspaceItemInfo info) {
public void showItem(ItemInfo info) {
View v = getViewForInfo(info);
if (v != null) {
v.setVisibility(VISIBLE);
@@ -1391,7 +1402,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
}
@Override
public void onAdd(WorkspaceItemInfo item, int rank) {
public void onAdd(ItemInfo item, int rank) {
FolderGridOrganizer verifier = new FolderGridOrganizer(
mActivityContext.getDeviceProfile()).setFolderInfo(mInfo);
verifier.updateRankAndPos(item, rank);
@@ -1406,7 +1417,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
}
@Override
public void onRemove(List<WorkspaceItemInfo> items) {
public void onRemove(List<ItemInfo> items) {
mItemsInvalidated = true;
items.stream().map(this::getViewForInfo).forEach(mContent::removeItem);
if (mState == STATE_ANIMATING) {
@@ -1423,7 +1434,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
}
}
private View getViewForInfo(final WorkspaceItemInfo item) {
private View getViewForInfo(final ItemInfo item) {
return mContent.iterateOverItems((info, view) -> info == item);
}
@@ -1432,6 +1443,11 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
updateTextViewFocus();
}
@Override
public void onTitleChanged(CharSequence title) {
mFolderName.setText(title);
}
/**
* Utility methods to iterate over items of the view
*/
@@ -1451,7 +1467,7 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
return mItemsInReadingOrder;
}
public List<BubbleTextView> getItemsOnPage(int page) {
public List<View> getItemsOnPage(int page) {
ArrayList<View> allItems = getIconsInReadingOrder();
int lastPage = mContent.getPageCount() - 1;
int totalItemsInFolder = allItems.size();
@@ -1463,9 +1479,9 @@ public class Folder extends AbstractFloatingView implements ClipPathView, DragSo
int startIndex = page * itemsPerPage;
int endIndex = Math.min(startIndex + numItemsOnCurrentPage, allItems.size());
List<BubbleTextView> itemsOnCurrentPage = new ArrayList<>(numItemsOnCurrentPage);
List<View> itemsOnCurrentPage = new ArrayList<>(numItemsOnCurrentPage);
for (int i = startIndex; i < endIndex; ++i) {
itemsOnCurrentPage.add((BubbleTextView) allItems.get(i));
itemsOnCurrentPage.add(allItems.get(i));
}
return itemsOnCurrentPage;
}
@@ -43,6 +43,7 @@ import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
import com.android.launcher3.anim.PropertyResetListener;
import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.util.Themes;
import com.android.launcher3.views.BaseDragLayer;
@@ -127,7 +128,7 @@ public class FolderAnimationManager {
(BaseDragLayer.LayoutParams) mFolder.getLayoutParams();
mFolderIcon.getPreviewItemManager().recomputePreviewDrawingParams();
ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule();
final List<BubbleTextView> itemsInPreview = getPreviewIconsOnPage(0);
final List<View> itemsInPreview = getPreviewIconsOnPage(0);
// Match position of the FolderIcon
final Rect folderIconPos = new Rect();
@@ -139,8 +140,8 @@ public class FolderAnimationManager {
// Match size/scale of icons in the preview
float previewScale = rule.scaleForItem(itemsInPreview.size());
float previewSize = rule.getIconSize() * previewScale;
float initialScale = previewSize / itemsInPreview.get(0).getIconSize()
* scaleRelativeToDragLayer;
float baseIconSize = getBubbleTextView(itemsInPreview.get(0)).getIconSize();
float initialScale = previewSize / baseIconSize * scaleRelativeToDragLayer;
final float finalScale = 1f;
float scale = mIsOpening ? initialScale : finalScale;
mFolder.setPivotX(0);
@@ -198,11 +199,12 @@ public class FolderAnimationManager {
// Initialize the Folder items' text.
PropertyResetListener colorResetListener =
new PropertyResetListener<>(TEXT_ALPHA_PROPERTY, 1f);
for (BubbleTextView icon : mFolder.getItemsOnPage(mFolder.mContent.getCurrentPage())) {
for (View icon : mFolder.getItemsOnPage(mFolder.mContent.getCurrentPage())) {
BubbleTextView titleText = getBubbleTextView(icon);
if (mIsOpening) {
icon.setTextVisibility(false);
titleText.setTextVisibility(false);
}
ObjectAnimator anim = icon.createTextAlphaAnimator(mIsOpening);
ObjectAnimator anim = titleText.createTextAlphaAnimator(mIsOpening);
anim.addListener(colorResetListener);
play(a, anim);
}
@@ -339,7 +341,7 @@ public class FolderAnimationManager {
/**
* Returns the list of "preview items" on {@param page}.
*/
private List<BubbleTextView> getPreviewIconsOnPage(int page) {
private List<View> getPreviewIconsOnPage(int page) {
return mPreviewVerifier.setFolderInfo(mFolder.mInfo)
.previewItemsForPage(page, mFolder.getIconsInReadingOrder());
}
@@ -351,7 +353,7 @@ public class FolderAnimationManager {
int previewItemOffsetX, int previewItemOffsetY) {
ClippedFolderIconLayoutRule rule = mFolderIcon.getLayoutRule();
boolean isOnFirstPage = mFolder.mContent.getCurrentPage() == 0;
final List<BubbleTextView> itemsInPreview = getPreviewIconsOnPage(
final List<View> itemsInPreview = getPreviewIconsOnPage(
isOnFirstPage ? 0 : mFolder.mContent.getCurrentPage());
final int numItemsInPreview = itemsInPreview.size();
final int numItemsInFirstPagePreview = isOnFirstPage
@@ -361,48 +363,49 @@ public class FolderAnimationManager {
ShortcutAndWidgetContainer cwc = mContent.getPageAt(0).getShortcutsAndWidgets();
for (int i = 0; i < numItemsInPreview; ++i) {
final BubbleTextView btv = itemsInPreview.get(i);
CellLayoutLayoutParams btvLp = (CellLayoutLayoutParams) btv.getLayoutParams();
final View v = itemsInPreview.get(i);
CellLayoutLayoutParams vLp = (CellLayoutLayoutParams) v.getLayoutParams();
// Calculate the final values in the LayoutParams.
btvLp.isLockedToGrid = true;
cwc.setupLp(btv);
vLp.isLockedToGrid = true;
cwc.setupLp(v);
// Match scale of icons in the preview of the items on the first page.
float previewScale = rule.scaleForItem(numItemsInFirstPagePreview);
float previewSize = rule.getIconSize() * previewScale;
float iconScale = previewSize / itemsInPreview.get(i).getIconSize();
float baseIconSize = getBubbleTextView(v).getIconSize();
float iconScale = previewSize / baseIconSize;
final float initialScale = iconScale / folderScale;
final float finalScale = 1f;
float scale = mIsOpening ? initialScale : finalScale;
btv.setScaleX(scale);
btv.setScaleY(scale);
v.setScaleX(scale);
v.setScaleY(scale);
// Match positions of the icons in the folder with their positions in the preview
rule.computePreviewItemDrawingParams(i, numItemsInFirstPagePreview, mTmpParams);
// The PreviewLayoutRule assumes that the icon size takes up the entire width so we
// offset by the actual size.
int iconOffsetX = (int) ((btvLp.width - btv.getIconSize()) * iconScale) / 2;
int iconOffsetX = (int) ((vLp.width - baseIconSize) * iconScale) / 2;
final int previewPosX =
(int) ((mTmpParams.transX - iconOffsetX + previewItemOffsetX) / folderScale);
final float paddingTop = btv.getPaddingTop() * iconScale;
final float paddingTop = v.getPaddingTop() * iconScale;
final int previewPosY = (int) ((mTmpParams.transY + previewItemOffsetY - paddingTop)
/ folderScale);
final float xDistance = previewPosX - btvLp.x;
final float yDistance = previewPosY - btvLp.y;
final float xDistance = previewPosX - vLp.x;
final float yDistance = previewPosY - vLp.y;
Animator translationX = getAnimator(btv, View.TRANSLATION_X, xDistance, 0f);
Animator translationX = getAnimator(v, View.TRANSLATION_X, xDistance, 0f);
translationX.setInterpolator(previewItemInterpolator);
play(animatorSet, translationX);
Animator translationY = getAnimator(btv, View.TRANSLATION_Y, yDistance, 0f);
Animator translationY = getAnimator(v, View.TRANSLATION_Y, yDistance, 0f);
translationY.setInterpolator(previewItemInterpolator);
play(animatorSet, translationY);
Animator scaleAnimator = getAnimator(btv, SCALE_PROPERTY, initialScale, finalScale);
Animator scaleAnimator = getAnimator(v, SCALE_PROPERTY, initialScale, finalScale);
scaleAnimator.setInterpolator(previewItemInterpolator);
play(animatorSet, scaleAnimator);
@@ -426,20 +429,20 @@ public class FolderAnimationManager {
super.onAnimationStart(animation);
// Necessary to initialize values here because of the start delay.
if (mIsOpening) {
btv.setTranslationX(xDistance);
btv.setTranslationY(yDistance);
btv.setScaleX(initialScale);
btv.setScaleY(initialScale);
v.setTranslationX(xDistance);
v.setTranslationY(yDistance);
v.setScaleX(initialScale);
v.setScaleY(initialScale);
}
}
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
btv.setTranslationX(0.0f);
btv.setTranslationY(0.0f);
btv.setScaleX(1f);
btv.setScaleY(1f);
v.setTranslationX(0.0f);
v.setTranslationY(0.0f);
v.setScaleX(1f);
v.setScaleY(1f);
}
});
}
@@ -482,4 +485,15 @@ public class FolderAnimationManager {
? ObjectAnimator.ofArgb(drawable, property, v1, v2)
: ObjectAnimator.ofArgb(drawable, property, v2, v1);
}
/**
* Gets the {@link com.android.launcher3.BubbleTextView} from an icon. In some cases the
* BubbleTextView is the whole icon itself, while in others it is contained within the view and
* only serves to store the title text.
*/
private BubbleTextView getBubbleTextView(View v) {
return v instanceof AppPairIcon
? ((AppPairIcon) v).getTitleTextView()
: (BubbleTextView) v;
}
}
@@ -71,6 +71,7 @@ import com.android.launcher3.logger.LauncherAtom.FromState;
import com.android.launcher3.logger.LauncherAtom.ToState;
import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager;
import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.FolderInfo.FolderListener;
import com.android.launcher3.model.data.FolderInfo.LabelState;
@@ -118,7 +119,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
ClippedFolderIconLayoutRule mPreviewLayoutRule;
private PreviewItemManager mPreviewItemManager;
private PreviewItemDrawingParams mTmpParams = new PreviewItemDrawingParams(0, 0, 0);
private List<WorkspaceItemInfo> mCurrentPreviewItems = new ArrayList<>();
private List<ItemInfo> mCurrentPreviewItems = new ArrayList<>();
boolean mAnimating = false;
@@ -215,7 +216,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
// Keep the notification dot up to date with the sum of all the content's dots.
FolderDotInfo folderDotInfo = new FolderDotInfo();
for (WorkspaceItemInfo si : folderInfo.getContents()) {
for (ItemInfo si : folderInfo.getContents()) {
folderDotInfo.addDotInfo(activity.getDotInfoForItem(si));
}
icon.setDotInfo(folderDotInfo);
@@ -261,20 +262,18 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
private boolean willAcceptItem(ItemInfo item) {
final int itemType = item.itemType;
return ((itemType == LauncherSettings.Favorites.ITEM_TYPE_APPLICATION ||
itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT) &&
item != mInfo && !mFolder.isOpen());
return (Folder.willAcceptItemType(itemType) && item != mInfo && !mFolder.isOpen());
}
public boolean acceptDrop(ItemInfo dragInfo) {
return !mFolder.isDestroyed() && willAcceptItem(dragInfo);
}
public void addItem(WorkspaceItemInfo item) {
public void addItem(ItemInfo item) {
mInfo.add(item, true);
}
public void removeItem(WorkspaceItemInfo item, boolean animate) {
public void removeItem(ItemInfo item, boolean animate) {
mInfo.remove(item, animate);
}
@@ -287,8 +286,8 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
mOpenAlarm.setOnAlarmListener(mOnOpenListener);
if (SPRING_LOADING_ENABLED &&
((dragInfo instanceof WorkspaceItemFactory)
|| (dragInfo instanceof WorkspaceItemInfo)
|| (dragInfo instanceof PendingAddShortcutInfo))) {
|| (dragInfo instanceof PendingAddShortcutInfo)
|| Folder.willAccept(dragInfo))) {
mOpenAlarm.setAlarm(ON_OPEN_DELAY);
}
}
@@ -303,8 +302,8 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
return mPreviewItemManager.prepareCreateAnimation(destView);
}
public void performCreateAnimation(final WorkspaceItemInfo destInfo, final View destView,
final WorkspaceItemInfo srcInfo, final DragObject d, Rect dstRect,
public void performCreateAnimation(final ItemInfo destInfo, final View destView,
final ItemInfo srcInfo, final DragObject d, Rect dstRect,
float scaleRelativeToDragLayer) {
final DragView srcView = d.dragView;
prepareCreateAnimation(destView);
@@ -330,7 +329,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
mOpenAlarm.cancelAlarm();
}
private void onDrop(final WorkspaceItemInfo item, DragObject d, Rect finalRect,
private void onDrop(final ItemInfo item, DragObject d, Rect finalRect,
float scaleRelativeToDragLayer, int index, boolean itemReturnedOnFailedDrop) {
item.cellX = -1;
item.cellY = -1;
@@ -361,7 +360,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
int numItemsInPreview = Math.min(MAX_NUM_ITEMS_IN_PREVIEW, index + 1);
boolean itemAdded = false;
if (itemReturnedOnFailedDrop || index >= MAX_NUM_ITEMS_IN_PREVIEW) {
List<WorkspaceItemInfo> oldPreviewItems = new ArrayList<>(mCurrentPreviewItems);
List<ItemInfo> oldPreviewItems = new ArrayList<>(mCurrentPreviewItems);
mInfo.add(item, index, false);
mCurrentPreviewItems.clear();
mCurrentPreviewItems.addAll(getPreviewItemsOnPage(0));
@@ -422,7 +421,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
FolderNameInfos nameInfos = new FolderNameInfos();
Executors.MODEL_EXECUTOR.post(() -> {
d.folderNameProvider.getSuggestedFolderName(
getContext(), mInfo.getContents(), nameInfos);
getContext(), mInfo.getAppContents(), nameInfos);
postDelayed(() -> {
setLabelSuggestion(nameInfos, d.logInstanceId);
invalidate();
@@ -475,15 +474,21 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
public void onDrop(DragObject d, boolean itemReturnedOnFailedDrop) {
WorkspaceItemInfo item;
ItemInfo item;
if (d.dragInfo instanceof WorkspaceItemFactory) {
// Came from all apps -- make a copy
item = ((WorkspaceItemFactory) d.dragInfo).makeWorkspaceItem(getContext());
} else if (d.dragSource instanceof BaseItemDragListener){
// Came from a different window -- make a copy
item = new WorkspaceItemInfo((WorkspaceItemInfo) d.dragInfo);
if (d.dragInfo instanceof AppPairInfo) {
// dragged item is app pair
item = new AppPairInfo((AppPairInfo) d.dragInfo);
} else {
// dragged item is WorkspaceItemInfo
item = new WorkspaceItemInfo((WorkspaceItemInfo) d.dragInfo);
}
} else {
item = (WorkspaceItemInfo) d.dragInfo;
item = d.dragInfo;
}
mFolder.notifyDrop();
onDrop(item, d, null, 1.0f,
@@ -665,7 +670,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
/**
* Returns the list of items which should be visible in the preview
*/
public List<WorkspaceItemInfo> getPreviewItemsOnPage(int page) {
public List<ItemInfo> getPreviewItemsOnPage(int page) {
return mPreviewVerifier.setFolderInfo(mInfo).previewItemsForPage(page, mInfo.getContents());
}
@@ -690,12 +695,12 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
/**
* Updates the preview items which match the provided condition
*/
public void updatePreviewItems(Predicate<WorkspaceItemInfo> itemCheck) {
public void updatePreviewItems(Predicate<ItemInfo> itemCheck) {
mPreviewItemManager.updatePreviewItems(itemCheck);
}
@Override
public void onAdd(WorkspaceItemInfo item, int rank) {
public void onAdd(ItemInfo item, int rank) {
updatePreviewItems(false);
boolean wasDotted = mDotInfo.hasDot();
mDotInfo.addDotInfo(mActivity.getDotInfoForItem(item));
@@ -707,7 +712,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
}
@Override
public void onRemove(List<WorkspaceItemInfo> items) {
public void onRemove(List<ItemInfo> items) {
updatePreviewItems(false);
boolean wasDotted = mDotInfo.hasDot();
items.stream().map(mActivity::getDotInfoForItem).forEach(mDotInfo::subtractDotInfo);
@@ -718,6 +723,7 @@ public class FolderIcon extends FrameLayout implements FolderListener, IconLabel
requestLayout();
}
@Override
public void onTitleChanged(CharSequence title) {
mFolderName.setText(title);
setContentDescription(getAccessiblityTitle(title));
@@ -41,8 +41,10 @@ import com.android.launcher3.PagedView;
import com.android.launcher3.R;
import com.android.launcher3.ShortcutAndWidgetContainer;
import com.android.launcher3.Utilities;
import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
import com.android.launcher3.keyboard.ViewGroupFocusHelper;
import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pageindicators.PageIndicatorDots;
@@ -148,7 +150,7 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> implements Cli
/**
* Binds items to the layout.
*/
public void bindItems(List<WorkspaceItemInfo> items) {
public void bindItems(List<ItemInfo> items) {
if (mViewsBound) {
unbindItems();
}
@@ -164,8 +166,11 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> implements Cli
CellLayout page = (CellLayout) getChildAt(i);
ShortcutAndWidgetContainer container = page.getShortcutsAndWidgets();
for (int j = container.getChildCount() - 1; j >= 0; j--) {
container.getChildAt(j).setVisibility(View.VISIBLE);
mViewCache.recycleView(R.layout.folder_application, container.getChildAt(j));
View iconView = container.getChildAt(j);
iconView.setVisibility(View.VISIBLE);
if (iconView instanceof BubbleTextView) {
mViewCache.recycleView(R.layout.folder_application, iconView);
}
}
page.removeAllViews();
mViewCache.recycleView(R.layout.folder_page, page);
@@ -185,7 +190,7 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> implements Cli
* Creates and adds an icon corresponding to the provided rank
* @return the created icon
*/
public View createAndAddViewForRank(WorkspaceItemInfo item, int rank) {
public View createAndAddViewForRank(ItemInfo item, int rank) {
View icon = createNewView(item);
if (!mViewsBound) {
return icon;
@@ -200,7 +205,7 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> implements Cli
* Adds the {@param view} to the layout based on {@param rank} and updated the position
* related attributes. It assumes that {@param item} is already attached to the view.
*/
public void addViewForRank(View view, WorkspaceItemInfo item, int rank) {
public void addViewForRank(View view, ItemInfo item, int rank) {
int pageNo = rank / mOrganizer.getMaxItemsPerPage();
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) view.getLayoutParams();
@@ -209,26 +214,36 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> implements Cli
}
@SuppressLint("InflateParams")
public View createNewView(WorkspaceItemInfo item) {
public View createNewView(ItemInfo item) {
if (item == null) {
return null;
}
final BubbleTextView textView = mViewCache.getView(
R.layout.folder_application, getContext(), null);
textView.applyFromWorkspaceItem(item);
textView.setOnClickListener(mFolder.mActivityContext.getItemOnClickListener());
textView.setOnLongClickListener(mFolder);
textView.setOnFocusChangeListener(mFocusIndicatorHelper);
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) textView.getLayoutParams();
final View icon;
if (item instanceof AppPairInfo api) {
// TODO (b/332607759): Make view cache work with app pair icons
icon = AppPairIcon.inflateIcon(R.layout.folder_app_pair, ActivityContext.lookupContext(
getContext()), null , api, BubbleTextView.DISPLAY_FOLDER);
} else {
icon = mViewCache.getView(R.layout.folder_application, getContext(), null);
((BubbleTextView) icon).applyFromWorkspaceItem((WorkspaceItemInfo) item);
}
icon.setOnClickListener(mFolder.mActivityContext.getItemOnClickListener());
icon.setOnLongClickListener(mFolder);
icon.setOnFocusChangeListener(mFocusIndicatorHelper);
CellLayoutLayoutParams lp = (CellLayoutLayoutParams) icon.getLayoutParams();
if (lp == null) {
textView.setLayoutParams(new CellLayoutLayoutParams(
icon.setLayoutParams(new CellLayoutLayoutParams(
item.cellX, item.cellY, item.spanX, item.spanY));
} else {
lp.setCellX(item.cellX);
lp.setCellY(item.cellY);
lp.cellHSpan = lp.cellVSpan = 1;
}
return textView;
return icon;
}
@Nullable
@@ -497,13 +512,20 @@ public class FolderPagedView extends PagedView<PageIndicatorDots> implements Cli
if (page != null) {
ShortcutAndWidgetContainer parent = page.getShortcutsAndWidgets();
for (int i = parent.getChildCount() - 1; i >= 0; i--) {
BubbleTextView icon = ((BubbleTextView) parent.getChildAt(i));
icon.verifyHighRes();
View iconView = parent.getChildAt(i);
Drawable d = null;
if (iconView instanceof BubbleTextView btv) {
btv.verifyHighRes();
d = btv.getIcon();
} else if (iconView instanceof AppPairIcon api) {
api.verifyHighRes();
d = api.getIconDrawableArea().getDrawable();
}
// Set the callback back to the actual icon, in case
// it was captured by the FolderIcon
Drawable d = icon.getIcon();
if (d != null) {
d.setCallback(icon);
d.setCallback(iconView);
}
}
}
@@ -33,7 +33,7 @@ import com.android.launcher3.logging.InstanceId;
import com.android.launcher3.logging.StatsLogManager.StatsLogger;
import com.android.launcher3.model.ModelWriter;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.views.ActivityContext;
import com.android.launcher3.views.BaseDragLayer;
@@ -86,7 +86,7 @@ public class LauncherDelegate {
FolderInfo info = folder.mInfo;
if (itemCount <= 1) {
View newIcon = null;
WorkspaceItemInfo finalItem = null;
ItemInfo finalItem = null;
if (itemCount == 1) {
// Move the item from the folder to the workspace, in the position of the
@@ -17,7 +17,7 @@ package com.android.launcher3.folder;
import android.graphics.drawable.Drawable;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.model.data.ItemInfo;
/**
* Manages the parameters used to draw a Folder preview item.
@@ -30,7 +30,7 @@ class PreviewItemDrawingParams {
public FolderPreviewItemAnim anim;
public boolean hidden;
public Drawable drawable;
public WorkspaceItemInfo item;
public ItemInfo item;
PreviewItemDrawingParams(float transX, float transY, float scale) {
this.transX = transX;
@@ -16,6 +16,7 @@
package com.android.launcher3.folder;
import static com.android.launcher3.BubbleTextView.DISPLAY_FOLDER;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.ENTER_INDEX;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.EXIT_INDEX;
import static com.android.launcher3.folder.ClippedFolderIconLayoutRule.MAX_NUM_ITEMS_IN_PREVIEW;
@@ -41,7 +42,12 @@ import androidx.annotation.VisibleForTesting;
import com.android.launcher3.BubbleTextView;
import com.android.launcher3.Utilities;
import com.android.launcher3.apppairs.AppPairIcon;
import com.android.launcher3.apppairs.AppPairIconDrawingParams;
import com.android.launcher3.apppairs.AppPairIconGraphic;
import com.android.launcher3.graphics.PreloadIconDrawable;
import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.util.Themes;
@@ -125,7 +131,9 @@ public class PreviewItemManager {
}
Drawable prepareCreateAnimation(final View destView) {
Drawable animateDrawable = ((BubbleTextView) destView).getIcon();
Drawable animateDrawable = destView instanceof AppPairIcon
? ((AppPairIcon) destView).getIconDrawableArea().getDrawable()
: ((BubbleTextView) destView).getIcon();
computePreviewDrawingParams(animateDrawable.getIntrinsicWidth(),
destView.getMeasuredWidth());
mReferenceDrawable = animateDrawable;
@@ -258,7 +266,7 @@ public class PreviewItemManager {
}
void buildParamsForPage(int page, ArrayList<PreviewItemDrawingParams> params, boolean animate) {
List<WorkspaceItemInfo> items = mIcon.getPreviewItemsOnPage(page);
List<ItemInfo> items = mIcon.getPreviewItemsOnPage(page);
// We adjust the size of the list to match the number of items in the preview.
while (items.size() < params.size()) {
@@ -328,16 +336,18 @@ public class PreviewItemManager {
mNumOfPrevItems = numOfPrevItemsAux;
}
void updatePreviewItems(Predicate<WorkspaceItemInfo> itemCheck) {
void updatePreviewItems(Predicate<ItemInfo> itemCheck) {
boolean modified = false;
for (PreviewItemDrawingParams param : mFirstPageParams) {
if (itemCheck.test(param.item)) {
if (itemCheck.test(param.item)
|| (param.item instanceof AppPairInfo api && api.anyMatch(itemCheck))) {
setDrawable(param, param.item);
modified = true;
}
}
for (PreviewItemDrawingParams param : mCurrentPageParams) {
if (itemCheck.test(param.item)) {
if (itemCheck.test(param.item)
|| (param.item instanceof AppPairInfo api && api.anyMatch(itemCheck))) {
setDrawable(param, param.item);
modified = true;
}
@@ -370,15 +380,14 @@ public class PreviewItemManager {
* @param newItems The list of items in the new preview.
* @param dropped The item that was dropped onto the FolderIcon.
*/
public void onDrop(List<WorkspaceItemInfo> oldItems, List<WorkspaceItemInfo> newItems,
WorkspaceItemInfo dropped) {
public void onDrop(List<ItemInfo> oldItems, List<ItemInfo> newItems, ItemInfo dropped) {
int numItems = newItems.size();
final ArrayList<PreviewItemDrawingParams> params = mFirstPageParams;
buildParamsForPage(0, params, false);
// New preview items for items that are moving in (except for the dropped item).
List<WorkspaceItemInfo> moveIn = new ArrayList<>();
for (WorkspaceItemInfo newItem : newItems) {
List<ItemInfo> moveIn = new ArrayList<>();
for (ItemInfo newItem : newItems) {
if (!oldItems.contains(newItem) && !newItem.equals(dropped)) {
moveIn.add(newItem);
}
@@ -401,10 +410,10 @@ public class PreviewItemManager {
}
// Old preview items that need to be moved out.
List<WorkspaceItemInfo> moveOut = new ArrayList<>(oldItems);
List<ItemInfo> moveOut = new ArrayList<>(oldItems);
moveOut.removeAll(newItems);
for (int i = 0; i < moveOut.size(); ++i) {
WorkspaceItemInfo item = moveOut.get(i);
ItemInfo item = moveOut.get(i);
int oldIndex = oldItems.indexOf(item);
PreviewItemDrawingParams p = computePreviewItemDrawingParams(oldIndex, numItems, null);
updateTransitionParam(p, item, oldIndex, EXIT_INDEX, numItems);
@@ -418,7 +427,7 @@ public class PreviewItemManager {
}
}
private void updateTransitionParam(final PreviewItemDrawingParams p, WorkspaceItemInfo item,
private void updateTransitionParam(final PreviewItemDrawingParams p, ItemInfo item,
int prevIndex, int newIndex, int numItems) {
setDrawable(p, item);
@@ -431,16 +440,24 @@ public class PreviewItemManager {
}
@VisibleForTesting
public void setDrawable(PreviewItemDrawingParams p, WorkspaceItemInfo item) {
if (item.hasPromiseIconUi() || (item.runtimeStatusFlags
& ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) {
PreloadIconDrawable drawable = newPendingIcon(mContext, item);
p.drawable = drawable;
} else {
p.drawable = item.newIcon(mContext,
Themes.isThemedIconEnabled(mContext) ? FLAG_THEMED : 0);
public void setDrawable(PreviewItemDrawingParams p, ItemInfo item) {
if (item instanceof WorkspaceItemInfo wii) {
if (wii.hasPromiseIconUi() || (wii.runtimeStatusFlags
& ItemInfoWithIcon.FLAG_SHOW_DOWNLOAD_PROGRESS_MASK) != 0) {
PreloadIconDrawable drawable = newPendingIcon(mContext, wii);
p.drawable = drawable;
} else {
p.drawable = wii.newIcon(mContext,
Themes.isThemedIconEnabled(mContext) ? FLAG_THEMED : 0);
}
p.drawable.setBounds(0, 0, mIconSize, mIconSize);
} else if (item instanceof AppPairInfo api) {
AppPairIconDrawingParams appPairParams =
new AppPairIconDrawingParams(mContext, DISPLAY_FOLDER);
p.drawable = AppPairIconGraphic.composeDrawable(api, appPairParams);
p.drawable.setBounds(0, 0, mIconSize, mIconSize);
}
p.drawable.setBounds(0, 0, mIconSize, mIconSize);
p.item = item;
// Set the callback to FolderIcon as it is responsible to drawing the icon. The
// callback will be released when the folder is opened.
@@ -44,6 +44,7 @@ import com.android.launcher3.LauncherSettings.Favorites;
import com.android.launcher3.Workspace;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.AppPairInfo;
import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.ItemInfo;
@@ -259,10 +260,15 @@ public class BgDataModel {
itemsIdMap.put(item.id, item);
switch (item.itemType) {
case LauncherSettings.Favorites.ITEM_TYPE_FOLDER:
case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
collections.put(item.id, (CollectionInfo) item);
collections.put(item.id, (FolderInfo) item);
workspaceItems.add(item);
break;
case LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR:
collections.put(item.id, (AppPairInfo) item);
// Fall through here. App pairs are both containers (like folders) and containable
// items (can be placed in folders). So we need to add app pairs to the folders
// array (above) but also verify the existence of their container, like regular
// apps (below).
case LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT:
case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
if (item.container == LauncherSettings.Favorites.CONTAINER_DESKTOP ||
@@ -277,7 +283,7 @@ public class BgDataModel {
Log.e(TAG, msg);
}
} else {
findOrMakeFolder(item.container).add((WorkspaceItemInfo) item);
findOrMakeFolder(item.container).add(item);
}
}
break;
@@ -115,7 +115,7 @@ public class FirstScreenBroadcast {
for (ItemInfo info : firstScreenItems) {
if (info instanceof CollectionInfo ci) {
String collectionItemInfoPackage;
for (ItemInfo collectionItemInfo : cloneOnMainThread(ci.getContents())) {
for (ItemInfo collectionItemInfo : cloneOnMainThread(ci.getAppContents())) {
collectionItemInfoPackage = getPackageName(collectionItemInfo);
if (collectionItemInfoPackage != null
&& packages.contains(collectionItemInfoPackage)) {
@@ -81,7 +81,6 @@ import com.android.launcher3.model.data.CollectionInfo;
import com.android.launcher3.model.data.FolderInfo;
import com.android.launcher3.model.data.IconRequestInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.model.data.ItemInfoWithIcon;
import com.android.launcher3.model.data.LauncherAppWidgetInfo;
import com.android.launcher3.model.data.WorkspaceItemInfo;
import com.android.launcher3.pm.InstallSessionHelper;
@@ -494,9 +493,7 @@ public class LoaderTask implements Runnable {
}
appPair.getContents().sort(Folder.ITEM_POS_COMPARATOR);
// Fetch hi-res icons if needed.
appPair.getContents().stream().filter(ItemInfoWithIcon::usingLowResIcon)
.forEach(member -> mIconCache.getTitleAndIcon(member, false));
appPair.fetchHiResIconsIfNeeded(mIconCache);
}
}
@@ -566,12 +563,16 @@ public class LoaderTask implements Runnable {
// Ranks are the source of truth for folder items, so cellX and cellY can be
// ignored for now. Database will be updated once user manually modifies folder.
for (int rank = 0; rank < size; ++rank) {
WorkspaceItemInfo info = folder.getContents().get(rank);
ItemInfo info = folder.getContents().get(rank);
info.rank = rank;
if (info.usingLowResIcon() && info.itemType == Favorites.ITEM_TYPE_APPLICATION
if (info instanceof WorkspaceItemInfo wii
&& wii.usingLowResIcon()
&& wii.itemType == Favorites.ITEM_TYPE_APPLICATION
&& verifiers.stream().anyMatch(it -> it.isItemInPreview(info.rank))) {
mIconCache.getTitleAndIcon(info, false);
mIconCache.getTitleAndIcon(wii, false);
} else if (info instanceof AppPairInfo api) {
api.fetchHiResIconsIfNeeded(mIconCache);
}
}
}
@@ -788,7 +789,7 @@ public class LoaderTask implements Runnable {
FolderNameInfos suggestionInfos = new FolderNameInfos();
CollectionInfo info = mBgDataModel.collections.valueAt(i);
if (info instanceof FolderInfo fi && fi.suggestedFolderNames == null) {
provider.getSuggestedFolderName(mApp.getContext(), fi.getContents(),
provider.getSuggestedFolderName(mApp.getContext(), fi.getAppContents(),
suggestionInfos);
fi.suggestedFolderNames = suggestionInfos;
}
@@ -375,7 +375,7 @@ class WorkspaceItemProcessor(
val folderInfo: FolderInfo = collection
val newAppPair = AppPairInfo()
// Move the placeholder's contents over to the new app pair.
folderInfo.contents.forEach(newAppPair::add)
folderInfo.getContents().forEach(newAppPair::add)
collection = newAppPair
// Remove the placeholder and add the app pair into the data model.
bgDataModel.collections.remove(c.id)
@@ -18,11 +18,14 @@ package com.android.launcher3.model.data
import android.content.Context
import com.android.launcher3.LauncherSettings
import com.android.launcher3.icons.IconCache
import com.android.launcher3.logger.LauncherAtom
import com.android.launcher3.views.ActivityContext
/** A type of app collection that launches multiple apps into split screen. */
class AppPairInfo() : CollectionInfo() {
private var contents: ArrayList<WorkspaceItemInfo> = ArrayList()
init {
itemType = LauncherSettings.Favorites.ITEM_TYPE_APP_PAIR
}
@@ -33,11 +36,28 @@ class AppPairInfo() : CollectionInfo() {
add(app2)
}
/** Adds an element to the contents array. */
override fun add(item: WorkspaceItemInfo) {
/** Creates a new AppPairInfo that is a copy of the provided one. */
constructor(appPairInfo: AppPairInfo) : this() {
contents = appPairInfo.contents.clone() as ArrayList<WorkspaceItemInfo>
copyFrom(appPairInfo)
}
/** Adds an element to the contents ArrayList. */
override fun add(item: ItemInfo) {
if (item !is WorkspaceItemInfo) {
throw RuntimeException("tried to add an illegal type into an app pair")
}
contents.add(item)
}
/** Returns the app pair's member apps as an ArrayList of [ItemInfo]. */
override fun getContents(): ArrayList<ItemInfo> =
ArrayList(contents.stream().map { it as ItemInfo }.toList())
/** Returns the app pair's member apps as an ArrayList of [WorkspaceItemInfo]. */
override fun getAppContents(): ArrayList<WorkspaceItemInfo> = contents
/** Returns the first app in the pair. */
fun getFirstApp() = contents[0]
@@ -50,7 +70,9 @@ class AppPairInfo() : CollectionInfo() {
/** Checks if the app pair is launchable at the current screen size. */
fun isLaunchable(context: Context) =
(ActivityContext.lookupContext(context) as ActivityContext).getDeviceProfile().isTablet ||
noneMatch { it.hasStatusFlag(WorkspaceItemInfo.FLAG_NON_RESIZEABLE) }
getAppContents().stream().noneMatch {
it.hasStatusFlag(WorkspaceItemInfo.FLAG_NON_RESIZEABLE)
}
/** Generates an ItemInfo for logging. */
override fun buildProto(cInfo: CollectionInfo?): LauncherAtom.ItemInfo {
@@ -62,4 +84,11 @@ class AppPairInfo() : CollectionInfo() {
.setContainerInfo(getContainerInfo())
.build()
}
/** Fetches high-res icons for member apps if needed. */
fun fetchHiResIconsIfNeeded(iconCache: IconCache) {
getAppContents().stream().filter(ItemInfoWithIcon::usingLowResIcon).forEach { member ->
iconCache.getTitleAndIcon(member, false)
}
}
}
@@ -22,19 +22,21 @@ import com.android.launcher3.util.ContentWriter
import java.util.function.Predicate
abstract class CollectionInfo : ItemInfo() {
var contents: ArrayList<WorkspaceItemInfo> = ArrayList()
/** Adds an ItemInfo to the collection. Throws if given an illegal type. */
abstract fun add(item: ItemInfo)
abstract fun add(item: WorkspaceItemInfo)
/** Returns the collection's contents as an ArrayList of [ItemInfo]. */
abstract fun getContents(): ArrayList<ItemInfo>
/**
* Returns the collection's contents as an ArrayList of [WorkspaceItemInfo]. Does not include
* other collection [ItemInfo]s that are inside this collection; rather, it should collect
* *their* contents and adds them to the ArrayList.
*/
abstract fun getAppContents(): ArrayList<WorkspaceItemInfo>
/** Convenience function. Checks contents to see if any match a given predicate. */
fun anyMatch(matcher: Predicate<in WorkspaceItemInfo>): Boolean {
return contents.stream().anyMatch(matcher)
}
/** Convenience function. Returns true if none of the contents match a given predicate. */
fun noneMatch(matcher: Predicate<in WorkspaceItemInfo>): Boolean {
return contents.stream().noneMatch(matcher)
}
fun anyMatch(matcher: Predicate<ItemInfo>) = getContents().stream().anyMatch(matcher)
override fun onAddToDatabase(writer: ContentWriter) {
super.onAddToDatabase(writer)
@@ -29,6 +29,7 @@ import androidx.annotation.Nullable;
import com.android.launcher3.LauncherSettings;
import com.android.launcher3.Utilities;
import com.android.launcher3.folder.Folder;
import com.android.launcher3.folder.FolderNameInfos;
import com.android.launcher3.logger.LauncherAtom;
import com.android.launcher3.logger.LauncherAtom.Attribute;
@@ -98,14 +99,20 @@ public class FolderInfo extends CollectionInfo {
public FolderNameInfos suggestedFolderNames;
/**
* The apps and shortcuts
*/
private final ArrayList<ItemInfo> contents = new ArrayList<>();
private ArrayList<FolderListener> mListeners = new ArrayList<>();
public FolderInfo() {
itemType = LauncherSettings.Favorites.ITEM_TYPE_FOLDER;
}
/** Adds a app or shortcut to the contents array without animation. */
public void add(@NonNull WorkspaceItemInfo item) {
/** Adds a app or shortcut to the contents ArrayList without animation. */
@Override
public void add(@NonNull ItemInfo item) {
add(item, false /* animate */);
}
@@ -114,14 +121,18 @@ public class FolderInfo extends CollectionInfo {
*
* @param item
*/
public void add(WorkspaceItemInfo item, boolean animate) {
public void add(ItemInfo item, boolean animate) {
add(item, getContents().size(), animate);
}
/**
* Add an app or shortcut for a specified rank.
*/
public void add(WorkspaceItemInfo item, int rank, boolean animate) {
public void add(ItemInfo item, int rank, boolean animate) {
if (!Folder.willAccept(item)) {
throw new RuntimeException("tried to add an illegal type into a folder");
}
rank = Utilities.boundToRange(rank, 0, getContents().size());
getContents().add(rank, item);
for (int i = 0; i < mListeners.size(); i++) {
@@ -135,21 +146,49 @@ public class FolderInfo extends CollectionInfo {
*
* @param item
*/
public void remove(WorkspaceItemInfo item, boolean animate) {
public void remove(ItemInfo item, boolean animate) {
removeAll(Collections.singletonList(item), animate);
}
/**
* Remove all matching app or shortcut. Does not change the DB.
*/
public void removeAll(List<WorkspaceItemInfo> items, boolean animate) {
getContents().removeAll(items);
public void removeAll(List<ItemInfo> items, boolean animate) {
contents.removeAll(items);
for (int i = 0; i < mListeners.size(); i++) {
mListeners.get(i).onRemove(items);
}
itemsChanged(animate);
}
/**
* Returns the folder's contents as an ArrayList of {@link ItemInfo}. Includes
* {@link WorkspaceItemInfo} and {@link AppPairInfo}s.
*/
@NonNull
@Override
public ArrayList<ItemInfo> getContents() {
return contents;
}
/**
* Returns the folder's contents as an ArrayList of {@link WorkspaceItemInfo}. Note: Does not
* return any {@link AppPairInfo}s contained in the folder, instead collects *their* contents
* and adds them to the ArrayList.
*/
@Override
public ArrayList<WorkspaceItemInfo> getAppContents() {
ArrayList<WorkspaceItemInfo> workspaceItemInfos = new ArrayList<>();
for (ItemInfo item : contents) {
if (item instanceof WorkspaceItemInfo wii) {
workspaceItemInfos.add(wii);
} else if (item instanceof AppPairInfo api) {
workspaceItemInfos.addAll(api.getAppContents());
}
}
return workspaceItemInfos;
}
@Override
public void onAddToDatabase(@NonNull ContentWriter writer) {
super.onAddToDatabase(writer);
@@ -171,9 +210,11 @@ public class FolderInfo extends CollectionInfo {
}
public interface FolderListener {
void onAdd(WorkspaceItemInfo item, int rank);
void onRemove(List<WorkspaceItemInfo> item);
void onAdd(ItemInfo item, int rank);
void onRemove(List<ItemInfo> item);
void onItemsChanged(boolean animate);
void onTitleChanged(CharSequence title);
}
public boolean hasOption(int optionFlag) {
@@ -246,6 +287,10 @@ public class FolderInfo extends CollectionInfo {
if (modelWriter != null) {
modelWriter.updateItemInDatabase(this);
}
for (int i = 0; i < mListeners.size(); i++) {
mListeners.get(i).onTitleChanged(title);
}
}
/**
@@ -263,10 +308,17 @@ public class FolderInfo extends CollectionInfo {
public ItemInfo makeShallowCopy() {
FolderInfo folderInfo = new FolderInfo();
folderInfo.copyFrom(this);
folderInfo.setContents(this.getContents());
return folderInfo;
}
@Override
public void copyFrom(@NonNull ItemInfo info) {
super.copyFrom(info);
if (info instanceof FolderInfo fi) {
contents.addAll(fi.getContents());
}
}
/**
* Returns index of the accepted suggestion.
*/
@@ -82,7 +82,6 @@ public abstract class AbstractSlideInView<T extends Context & ActivityContext>
};
protected static final float TRANSLATION_SHIFT_CLOSED = 1f;
protected static final float TRANSLATION_SHIFT_OPENED = 0f;
private static final float VIEW_NO_SCALE = 1f;
private static final int DEFAULT_DURATION = 300;
protected final T mActivityContext;
@@ -129,9 +128,13 @@ public abstract class AbstractSlideInView<T extends Context & ActivityContext>
protected @Nullable OnCloseListener mOnCloseBeginListener;
protected List<OnCloseListener> mOnCloseListeners = new ArrayList<>();
protected final AnimatedFloat mSlideInViewScale =
new AnimatedFloat(this::onScaleProgressChanged, VIEW_NO_SCALE);
protected boolean mIsBackProgressing;
/**
* How far through a "user initiated dismissal" the UI is. e.g. Predictive back, swipe to home,
* 0 is regular state, 1 is fully dismissed.
*/
protected final AnimatedFloat mSwipeToDismissProgress =
new AnimatedFloat(this::onUserSwipeToDismissProgressChanged, 0f);
protected boolean mIsDismissInProgress;
private @Nullable Drawable mContentBackground;
private @Nullable View mContentBackgroundParentView;
@@ -287,29 +290,30 @@ public abstract class AbstractSlideInView<T extends Context & ActivityContext>
final float progress = backEvent.getProgress();
float deceleratedProgress =
Interpolators.PREDICTIVE_BACK_DECELERATED_EASE.getInterpolation(progress);
mIsBackProgressing = progress > 0f;
mSlideInViewScale.updateValue(PREDICTIVE_BACK_MIN_SCALE
+ (1 - PREDICTIVE_BACK_MIN_SCALE) * (1 - deceleratedProgress));
mSwipeToDismissProgress.updateValue(deceleratedProgress);
}
protected void onScaleProgressChanged() {
float scaleProgress = mSlideInViewScale.value;
SCALE_PROPERTY.set(this, scaleProgress);
setClipChildren(!mIsBackProgressing);
setClipToPadding(!mIsBackProgressing);
mContent.setClipChildren(!mIsBackProgressing);
mContent.setClipToPadding(!mIsBackProgressing);
protected void onUserSwipeToDismissProgressChanged() {
float progress = mSwipeToDismissProgress.value;
mIsDismissInProgress = progress > 0f;
float scale = PREDICTIVE_BACK_MIN_SCALE + (1 - PREDICTIVE_BACK_MIN_SCALE) * (1f - progress);
SCALE_PROPERTY.set(this, scale);
setClipChildren(!mIsDismissInProgress);
setClipToPadding(!mIsDismissInProgress);
mContent.setClipChildren(!mIsDismissInProgress);
mContent.setClipToPadding(!mIsDismissInProgress);
invalidate();
}
@Override
public void onBackCancelled() {
super.onBackCancelled();
animateSlideInViewToNoScale();
animateSwipeToDismissProgressToStart();
}
protected void animateSlideInViewToNoScale() {
mSlideInViewScale.animateToValue(1f)
protected void animateSwipeToDismissProgressToStart() {
mSwipeToDismissProgress.animateToValue(0f)
.setDuration(REVERT_SWIPE_ALL_APPS_TO_HOME_ANIMATION_DURATION_MS)
.start();
}
@@ -340,7 +344,7 @@ public abstract class AbstractSlideInView<T extends Context & ActivityContext>
mContentBackgroundParentView.getTop() + (int) mContent.getTranslationY(),
mContentBackgroundParentView.getRight(),
mContentBackgroundParentView.getBottom()
+ (mIsBackProgressing ? getBottomOffsetPx() : 0));
+ (mIsDismissInProgress ? getBottomOffsetPx() : 0));
mContentBackground.draw(canvas);
}
@@ -70,9 +70,9 @@ public class WidgetsEduView extends AbstractSlideInView<BaseActivity> implements
}
@Override
protected void onScaleProgressChanged() {
super.onScaleProgressChanged();
setTranslationY(getMeasuredHeight() * (1 - mSlideInViewScale.value) / 2);
protected void onUserSwipeToDismissProgressChanged() {
super.onUserSwipeToDismissProgressChanged();
setTranslationY(getMeasuredHeight() * (mSwipeToDismissProgress.value / 2));
}
private void show() {
@@ -26,7 +26,6 @@ import static com.android.launcher3.widget.util.WidgetSizes.getWidgetItemSizePx;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.os.Process;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.util.Log;
@@ -39,7 +38,6 @@ import android.view.ViewPropertyAnimator;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RemoteViews;
import android.widget.TextView;
@@ -88,7 +86,6 @@ public class WidgetCell extends LinearLayout {
private Size mPreviewContainerSize = new Size(0, 0);
private FrameLayout mWidgetImageContainer;
private WidgetImageView mWidgetImage;
private ImageView mWidgetBadge;
private TextView mWidgetName;
private TextView mWidgetDims;
private TextView mWidgetDescription;
@@ -142,7 +139,6 @@ public class WidgetCell extends LinearLayout {
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);
@@ -182,8 +178,6 @@ public class WidgetCell extends LinearLayout {
mWidgetImage.animate().cancel();
mWidgetImage.setDrawable(null);
mWidgetImage.setVisibility(View.VISIBLE);
mWidgetBadge.setImageDrawable(null);
mWidgetBadge.setVisibility(View.GONE);
mWidgetName.setText(null);
mWidgetDims.setText(null);
mWidgetDescription.setText(null);
@@ -397,17 +391,6 @@ public class WidgetCell extends LinearLayout {
}
}
/** Used to show the badge when the widget is in the recommended section
*/
public void showBadge() {
if (Process.myUserHandle().equals(mItem.user)) {
mWidgetBadge.setVisibility(View.GONE);
} else {
mWidgetBadge.setVisibility(View.VISIBLE);
mWidgetBadge.setImageResource(R.drawable.ic_work_app_badge);
}
}
/**
* Shows or hides the long description displayed below each widget.
*
@@ -24,8 +24,6 @@ import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.View;
import com.android.launcher3.R;
/**
* View that draws a bitmap horizontally centered. If the image width is greater than the view
* width, the image is scaled down appropriately.
@@ -33,8 +31,6 @@ import com.android.launcher3.R;
public class WidgetImageView extends View {
private final RectF mDstRectF = new RectF();
private final int mBadgeMargin;
private Drawable mDrawable;
public WidgetImageView(Context context) {
@@ -47,9 +43,6 @@ public class WidgetImageView extends View {
public WidgetImageView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mBadgeMargin = context.getResources()
.getDimensionPixelSize(R.dimen.profile_badge_margin);
}
/** Set the drawable to use for this view. */
@@ -280,6 +280,6 @@ public class WidgetsBottomSheet extends BaseWidgetSheet {
@Override
public void addHintCloseAnim(
float distanceToMove, Interpolator interpolator, PendingAnimation target) {
target.setInt(this, PADDING_BOTTOM, (int) (distanceToMove + mInsets.bottom), interpolator);
target.addAnimatedFloat(mSwipeToDismissProgress, 0f, 1f, interpolator);
}
}
@@ -17,7 +17,6 @@ package com.android.launcher3.widget.picker;
import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
import static com.android.launcher3.Flags.enableUnfoldedTwoPanePicker;
import static com.android.launcher3.LauncherAnimUtils.VIEW_TRANSLATE_Y;
import static com.android.launcher3.LauncherPrefs.WIDGETS_EDUCATION_DIALOG_SEEN;
import static com.android.launcher3.allapps.ActivityAllAppsContainerView.AdapterHolder.SEARCH;
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_WIDGETSTRAY_SEARCHED;
@@ -814,8 +813,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
@Override
public void addHintCloseAnim(
float distanceToMove, Interpolator interpolator, PendingAnimation target) {
target.setFloat(getRecyclerView(), VIEW_TRANSLATE_Y, -distanceToMove, interpolator);
target.setViewAlpha(getRecyclerView(), 0.5f, interpolator);
target.addAnimatedFloat(mSwipeToDismissProgress, 0f, 1f, interpolator);
}
@Override
@@ -911,7 +909,7 @@ public class WidgetsFullSheet extends BaseWidgetSheet
public void onBackInvoked() {
if (mIsInSearchMode) {
mSearchBar.reset();
animateSlideInViewToNoScale();
animateSwipeToDismissProgressToStart();
} else {
super.onBackInvoked();
}
@@ -74,7 +74,7 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet {
private ScrollView mRightPaneScrollView;
private WidgetsListTableViewHolderBinder mWidgetsListTableViewHolderBinder;
private boolean mOldIsBackSwipeProgressing;
private boolean mOldIsSwipeToDismissInProgress;
private int mActivePage = -1;
private PackageUserKey mSelectedHeader;
@@ -154,14 +154,14 @@ public class WidgetsTwoPaneSheet extends WidgetsFullSheet {
}
@Override
protected void onScaleProgressChanged() {
super.onScaleProgressChanged();
boolean isBackSwipeProgressing = mSlideInViewScale.value > 0;
if (isBackSwipeProgressing == mOldIsBackSwipeProgressing) {
protected void onUserSwipeToDismissProgressChanged() {
super.onUserSwipeToDismissProgressChanged();
boolean isSwipeToDismissInProgress = mSwipeToDismissProgress.value > 0;
if (isSwipeToDismissInProgress == mOldIsSwipeToDismissInProgress) {
return;
}
mOldIsBackSwipeProgressing = isBackSwipeProgressing;
if (isBackSwipeProgressing) {
mOldIsSwipeToDismissInProgress = isSwipeToDismissInProgress;
if (isSwipeToDismissInProgress) {
modifyAttributesOnViewTree(mPrimaryWidgetListView, (ViewParent) mContent,
CLIP_CHILDREN_FALSE_MODIFIER);
modifyAttributesOnViewTree(mRightPaneScrollView, (ViewParent) mContent,
@@ -29,7 +29,6 @@ import com.android.launcher3.allapps.ActivityAllAppsContainerView;
import com.android.launcher3.allapps.SearchRecyclerView;
import com.android.launcher3.ui.AbstractLauncherUiTest;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -40,7 +39,6 @@ public class LauncherIntentTest extends AbstractLauncherUiTest {
public final Intent allAppsIntent = new Intent(Intent.ACTION_ALL_APPS);
@Test
@Ignore("b/329152799")
public void testAllAppsIntent() {
// setup by moving to home
mLauncher.goHome();
@@ -66,8 +64,6 @@ public class LauncherIntentTest extends AbstractLauncherUiTest {
// Highlights the search bar, then fills text to display the SearchView.
private void moveToSearchView() {
mLauncher.goHome().switchToAllApps();
// All Apps view should be loaded
assertTrue("Launcher internal state is not All Apps",
isInState(() -> LauncherState.ALL_APPS));
@@ -29,7 +29,7 @@ import com.android.launcher3.icons.BaseIconFactory
import com.android.launcher3.icons.FastBitmapDrawable
import com.android.launcher3.icons.UserBadgeDrawable
import com.android.launcher3.model.data.FolderInfo
import com.android.launcher3.model.data.WorkspaceItemInfo
import com.android.launcher3.model.data.ItemInfo
import com.android.launcher3.util.ActivityContextWrapper
import com.android.launcher3.util.FlagOp
import com.android.launcher3.util.LauncherLayoutBuilder
@@ -47,7 +47,7 @@ class PreviewItemManagerTest {
private lateinit var previewItemManager: PreviewItemManager
private lateinit var context: Context
private lateinit var folderItems: ArrayList<WorkspaceItemInfo>
private lateinit var folderItems: ArrayList<ItemInfo>
private lateinit var modelHelper: LauncherModelHelper
private lateinit var folderIcon: FolderIcon
@@ -72,15 +72,17 @@ class PreviewItemManagerTest {
.build()
)
.loadModelSync()
folderItems = modelHelper.bgDataModel.collections.valueAt(0).contents
folderItems = modelHelper.bgDataModel.collections.valueAt(0).getContents()
folderIcon.mInfo = modelHelper.bgDataModel.collections.valueAt(0) as FolderInfo
folderIcon.mInfo.contents = folderItems
folderIcon.mInfo.getContents().addAll(folderItems)
// Use getAppContents() to "cast" contents to WorkspaceItemInfo so we can set bitmaps
val folderApps = modelHelper.bgDataModel.collections.valueAt(0).getAppContents()
// Set first icon to be themed.
folderItems[0]
folderApps[0]
.bitmap
.setMonoIcon(
folderItems[0].bitmap.icon,
folderApps[0].bitmap.icon,
BaseIconFactory(
context,
context.resources.configuration.densityDpi,
@@ -89,7 +91,7 @@ class PreviewItemManagerTest {
)
// Set second icon to be non-themed.
folderItems[1]
folderApps[1]
.bitmap
.setMonoIcon(
null,
@@ -101,23 +103,21 @@ class PreviewItemManagerTest {
)
// Set third icon to be themed with badge.
folderItems[2]
folderApps[2]
.bitmap
.setMonoIcon(
folderItems[2].bitmap.icon,
folderApps[2].bitmap.icon,
BaseIconFactory(
context,
context.resources.configuration.densityDpi,
previewItemManager.mIconSize
)
)
folderItems[2].bitmap =
folderItems[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
folderApps[2].bitmap = folderApps[2].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
// Set fourth icon to be non-themed with badge.
folderItems[3].bitmap =
folderItems[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
folderItems[3]
folderApps[3].bitmap = folderApps[3].bitmap.withFlags(profileFlagOp(UserIconInfo.TYPE_WORK))
folderApps[3]
.bitmap
.setMonoIcon(
null,
@@ -160,6 +160,6 @@ public class CacheDataUpdatedTaskTest {
}
private List<WorkspaceItemInfo> allItems() {
return ((FolderInfo) mModelHelper.getBgDataModel().itemsIdMap.get(1)).getContents();
return ((FolderInfo) mModelHelper.getBgDataModel().itemsIdMap.get(1)).getAppContents();
}
}
@@ -168,8 +168,8 @@ class FolderIconLoadTest {
val collections = modelHelper.getBgDataModel().collections
assertThat(collections.size()).isEqualTo(1)
assertThat(collections.valueAt(0).contents.size).isEqualTo(itemCount)
return collections.valueAt(0).contents
assertThat(collections.valueAt(0).getAppContents().size).isEqualTo(itemCount)
return collections.valueAt(0).getAppContents()
}
private fun verifyHighRes(items: ArrayList<WorkspaceItemInfo>, vararg indices: Int) {
@@ -139,9 +139,9 @@ class ItemInflaterTest {
@Test
fun test_folder_inflated_on_UI() {
val itemInfo = FolderInfo()
itemInfo.contents.add(workspaceItemInfo())
itemInfo.contents.add(workspaceItemInfo())
itemInfo.contents.add(workspaceItemInfo())
itemInfo.add(workspaceItemInfo())
itemInfo.add(workspaceItemInfo())
itemInfo.add(workspaceItemInfo())
val view =
MAIN_EXECUTOR.submit(Callable { underTest.inflateItem(itemInfo, modelWriter) }).get()
@@ -155,9 +155,9 @@ class ItemInflaterTest {
setFlagsRule.enableFlags(Flags.FLAG_ENABLE_WORKSPACE_INFLATION)
val itemInfo = FolderInfo()
itemInfo.contents.add(workspaceItemInfo())
itemInfo.contents.add(workspaceItemInfo())
itemInfo.contents.add(workspaceItemInfo())
itemInfo.add(workspaceItemInfo())
itemInfo.add(workspaceItemInfo())
itemInfo.add(workspaceItemInfo())
val view =
VIEW_PREINFLATION_EXECUTOR.submit(
@@ -173,8 +173,8 @@ class ItemInflaterTest {
fun test_app_pair_inflated_on_UI() {
val itemInfo = AppPairInfo()
itemInfo.itemType = ITEM_TYPE_APP_PAIR
itemInfo.contents.add(workspaceItemInfo())
itemInfo.contents.add(workspaceItemInfo())
itemInfo.add(workspaceItemInfo())
itemInfo.add(workspaceItemInfo())
val view =
MAIN_EXECUTOR.submit(Callable { underTest.inflateItem(itemInfo, modelWriter) }).get()
@@ -189,8 +189,8 @@ class ItemInflaterTest {
val itemInfo = AppPairInfo()
itemInfo.itemType = ITEM_TYPE_APP_PAIR
itemInfo.contents.add(workspaceItemInfo())
itemInfo.contents.add(workspaceItemInfo())
itemInfo.add(workspaceItemInfo())
itemInfo.add(workspaceItemInfo())
val view =
VIEW_PREINFLATION_EXECUTOR.submit(
@@ -398,8 +398,9 @@ public class BaseOverview extends LauncherInstrumentation.VisibleContainer {
if (isTablet && Math.abs(task.getExactCenterX() - mLauncher.getExactScreenCenterX()) >= 1) {
return false;
}
if (!mLauncher.isAppPairsEnabled() && task.isTaskSplit()) {
// Overview actions aren't visible for split screen tasks.
if (task.isTaskSplit() && (!mLauncher.isAppPairsEnabled() || !isTablet)) {
// Overview actions aren't visible for split screen tasks, except for save app pair
// button on tablets.
return false;
}
return true;