feat: Add option to clear home screen in settings (#6125)
Signed-off-by: abhixv <abhi.sharma1@hotmail.com>
This commit is contained in:
committed by
Pun Butrach
parent
9898749619
commit
5f3a03f4fb
@@ -27,7 +27,7 @@ import android.animation.AnimatorSet;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
import android.os.RemoteException;
|
||||
import android.view.IRemoteAnimationFinishedCallback;
|
||||
import android.view.RemoteAnimationTarget;
|
||||
import android.view.SurfaceControl;
|
||||
@@ -223,7 +223,6 @@ public class LauncherAnimationRunner extends RemoteAnimationRunnerCompat {
|
||||
if (skipFirstFrame) {
|
||||
// Because t=0 has the app icon in its original spot, we can skip the
|
||||
// first frame and have the same movement one frame earlier.
|
||||
Log.d("b/311077782", "LauncherAnimationRunner.setAnimation");
|
||||
mAnimator.setCurrentPlayTime(
|
||||
Math.min(getSingleFrameMs(context), mAnimator.getTotalDuration()));
|
||||
}
|
||||
@@ -238,7 +237,7 @@ public class LauncherAnimationRunner extends RemoteAnimationRunnerCompat {
|
||||
* animation finished runnable.
|
||||
*/
|
||||
@Override
|
||||
public void onAnimationFinished() {
|
||||
public void onAnimationFinished() throws RemoteException {
|
||||
mASyncFinishRunnable.run();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@
|
||||
*/
|
||||
package com.android.launcher3;
|
||||
|
||||
import com.android.quickstep.util.ContextInitListener;
|
||||
import com.android.quickstep.util.ActivityInitListener;
|
||||
|
||||
import java.util.function.BiPredicate;
|
||||
|
||||
public class LauncherInitListener extends ContextInitListener<Launcher> {
|
||||
public class LauncherInitListener extends ActivityInitListener<Launcher> {
|
||||
|
||||
/**
|
||||
* @param onInitListener a callback made when the activity is initialized. The callback should
|
||||
@@ -31,8 +31,8 @@ public class LauncherInitListener extends ContextInitListener<Launcher> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleInit(Launcher launcher, boolean isHomeStarted) {
|
||||
public boolean handleInit(Launcher launcher, boolean alreadyOnHome) {
|
||||
launcher.deferOverlayCallbacksUntilNextResumeOrStop();
|
||||
return super.handleInit(launcher, isHomeStarted);
|
||||
return super.handleInit(launcher, alreadyOnHome);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,21 +15,10 @@
|
||||
*/
|
||||
package com.android.launcher3;
|
||||
|
||||
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED;
|
||||
|
||||
import static androidx.recyclerview.widget.RecyclerView.NO_POSITION;
|
||||
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
import androidx.recyclerview.widget.LinearSmoothScroller;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
|
||||
import com.android.launcher3.allapps.AllAppsRecyclerView;
|
||||
import com.android.launcher3.allapps.SearchRecyclerView;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.uioverrides.PredictedAppIcon;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
@@ -37,64 +26,13 @@ import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import java.util.List;
|
||||
|
||||
public class QuickstepAccessibilityDelegate extends LauncherAccessibilityDelegate {
|
||||
private QuickstepLauncher mLauncher;
|
||||
|
||||
public QuickstepAccessibilityDelegate(QuickstepLauncher launcher) {
|
||||
super(launcher);
|
||||
mLauncher = launcher;
|
||||
mActions.put(PIN_PREDICTION, new LauncherAction(
|
||||
PIN_PREDICTION, R.string.pin_prediction, KeyEvent.KEYCODE_P));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPopulateAccessibilityEvent(View view, AccessibilityEvent event) {
|
||||
super.onPopulateAccessibilityEvent(view, event);
|
||||
// Scroll to the position if focused view in main allapps list and not completely visible.
|
||||
// Gate based on TYPE_VIEW_ACCESSIBILITY_FOCUSED for unintended scrolling with external
|
||||
// mouse.
|
||||
if (event.getEventType() == TYPE_VIEW_ACCESSIBILITY_FOCUSED) {
|
||||
scrollToPositionIfNeeded(view);
|
||||
}
|
||||
}
|
||||
|
||||
private void scrollToPositionIfNeeded(View view) {
|
||||
if (!Flags.accessibilityScrollOnAllapps()) {
|
||||
return;
|
||||
}
|
||||
AllAppsRecyclerView contentView = mLauncher.getAppsView().getActiveRecyclerView();
|
||||
if (contentView instanceof SearchRecyclerView) {
|
||||
return;
|
||||
}
|
||||
LinearLayoutManager layoutManager = (LinearLayoutManager) contentView.getLayoutManager();
|
||||
if (layoutManager == null) {
|
||||
return;
|
||||
}
|
||||
RecyclerView.ViewHolder vh = contentView.findContainingViewHolder(view);
|
||||
if (vh == null) {
|
||||
return;
|
||||
}
|
||||
int itemPosition = vh.getBindingAdapterPosition();
|
||||
if (itemPosition == NO_POSITION) {
|
||||
return;
|
||||
}
|
||||
int firstCompletelyVisible = layoutManager.findFirstCompletelyVisibleItemPosition();
|
||||
int lastCompletelyVisible = layoutManager.findLastCompletelyVisibleItemPosition();
|
||||
boolean itemCompletelyVisible = firstCompletelyVisible <= itemPosition
|
||||
&& lastCompletelyVisible >= itemPosition;
|
||||
if (itemCompletelyVisible) {
|
||||
return;
|
||||
}
|
||||
RecyclerView.SmoothScroller smoothScroller =
|
||||
new LinearSmoothScroller(mLauncher.asContext()) {
|
||||
@Override
|
||||
protected int getVerticalSnapPreference() {
|
||||
return LinearSmoothScroller.SNAP_TO_ANY;
|
||||
}
|
||||
};
|
||||
smoothScroller.setTargetPosition(itemPosition);
|
||||
layoutManager.startSmoothScroll(smoothScroller);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void getSupportedActions(View host, ItemInfo item, List<LauncherAction> out) {
|
||||
if (host instanceof PredictedAppIcon && !((PredictedAppIcon) host).isPinned()) {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -23,9 +23,6 @@ import static android.view.WindowInsets.Type.statusBars;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
import android.appwidget.AppWidgetManager;
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.ClipData;
|
||||
@@ -36,40 +33,32 @@ import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsetsController;
|
||||
import android.view.WindowManager;
|
||||
import android.window.BackEvent;
|
||||
import android.window.OnBackAnimationCallback;
|
||||
import android.window.OnBackInvokedDispatcher;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.dragndrop.SimpleDragLayer;
|
||||
import com.android.launcher3.model.StringCache;
|
||||
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.model.data.PackageItemInfo;
|
||||
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.WidgetsListBaseEntriesBuilder;
|
||||
import com.android.launcher3.widget.model.WidgetsListBaseEntry;
|
||||
import com.android.launcher3.widget.picker.WidgetCategoryFilter;
|
||||
import com.android.launcher3.widget.model.WidgetsListHeaderEntry;
|
||||
import com.android.launcher3.widget.picker.WidgetsFullSheet;
|
||||
import com.android.launcher3.widget.picker.model.WidgetPickerDataProvider;
|
||||
import com.android.systemui.animation.back.FlingOnBackAnimationCallback;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** An Activity that can host Launcher's widget picker. */
|
||||
public class WidgetPickerActivity extends BaseActivity implements
|
||||
WidgetPredictionsRequester.WidgetPredictionsListener {
|
||||
public class WidgetPickerActivity extends BaseActivity {
|
||||
private static final String TAG = "WidgetPickerActivity";
|
||||
/**
|
||||
* Name of the extra that indicates that a widget being dragged.
|
||||
@@ -83,24 +72,11 @@ public class WidgetPickerActivity extends BaseActivity implements
|
||||
// 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";
|
||||
// Unlike the AppWidgetManager.EXTRA_CATEGORY_FILTER, this filter removes certain categories.
|
||||
// Filter is ignore if it is not a negative value.
|
||||
// Example usage: WIDGET_CATEGORY_HOME_SCREEN.inv() and WIDGET_CATEGORY_NOT_KEYGUARD.inv()
|
||||
private static final String EXTRA_CATEGORY_EXCLUSION_FILTER = "category_exclusion_filter";
|
||||
/**
|
||||
* 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";
|
||||
/**
|
||||
* Intent extra for the string representing the title displayed within the picker header.
|
||||
*/
|
||||
private static final String EXTRA_PICKER_TITLE = "picker_title";
|
||||
/**
|
||||
* Intent extra for the string representing the description displayed within the picker header.
|
||||
*/
|
||||
private static final String EXTRA_PICKER_DESCRIPTION = "picker_description";
|
||||
|
||||
/**
|
||||
* A unique identifier of the surface hosting the widgets;
|
||||
* <p>"widgets" is reserved for home screen surface.</p>
|
||||
@@ -109,52 +85,20 @@ public class WidgetPickerActivity extends BaseActivity implements
|
||||
private static final String EXTRA_UI_SURFACE = "ui_surface";
|
||||
private static final Pattern UI_SURFACE_PATTERN =
|
||||
Pattern.compile("^(widgets|widgets_hub)$");
|
||||
|
||||
/**
|
||||
* User ids that should be filtered out of the widget lists created by this activity.
|
||||
*/
|
||||
private static final String EXTRA_USER_ID_FILTER = "filtered_user_ids";
|
||||
|
||||
private SimpleDragLayer<WidgetPickerActivity> mDragLayer;
|
||||
private WidgetsModel mModel;
|
||||
private LauncherAppState mApp;
|
||||
private StringCache mStringCache;
|
||||
private WidgetPredictionsRequester mWidgetPredictionsRequester;
|
||||
private WidgetPickerDataProvider mWidgetPickerDataProvider;
|
||||
private final PopupDataProvider mPopupDataProvider = new PopupDataProvider(i -> {});
|
||||
|
||||
private int mDesiredWidgetWidth;
|
||||
private int mDesiredWidgetHeight;
|
||||
private WidgetCategoryFilter mWidgetCategoryInclusionFilter;
|
||||
private WidgetCategoryFilter mWidgetCategoryExclusionFilter;
|
||||
private int mWidgetCategoryFilter;
|
||||
@Nullable
|
||||
private String mUiSurface;
|
||||
// Widgets existing on the host surface.
|
||||
@NonNull
|
||||
private List<AppWidgetProviderInfo> mAddedWidgets = new ArrayList<>();
|
||||
@Nullable
|
||||
private String mTitle;
|
||||
@Nullable
|
||||
private String mDescription;
|
||||
|
||||
/** A set of user ids that should be filtered out from the selected widgets. */
|
||||
@NonNull
|
||||
Set<Integer> mFilteredUserIds = new HashSet<>();
|
||||
|
||||
@Nullable
|
||||
private WidgetsFullSheet mWidgetSheet;
|
||||
|
||||
private final Predicate<WidgetItem> mNoShortcutsFilter = widget -> {
|
||||
final WidgetAcceptabilityVerdict verdict =
|
||||
isWidgetAcceptable(widget, /* applySizeFilter=*/ false);
|
||||
verdict.maybeLogVerdict();
|
||||
return verdict.isAcceptable;
|
||||
};
|
||||
private final Predicate<WidgetItem> mHostSizeAndNoShortcutsFilter = widget -> {
|
||||
final WidgetAcceptabilityVerdict verdict =
|
||||
isWidgetAcceptable(widget, /* applySizeFilter=*/ true);
|
||||
verdict.maybeLogVerdict();
|
||||
return verdict.isAcceptable;
|
||||
};
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
@@ -166,8 +110,7 @@ public class WidgetPickerActivity extends BaseActivity implements
|
||||
mApp = LauncherAppState.getInstance(this);
|
||||
InvariantDeviceProfile idp = mApp.getInvariantDeviceProfile();
|
||||
mDeviceProfile = idp.getDeviceProfile(this);
|
||||
mModel = new WidgetsModel(mApp.getContext());
|
||||
mWidgetPickerDataProvider = new WidgetPickerDataProvider(this);
|
||||
mModel = new WidgetsModel();
|
||||
|
||||
setContentView(R.layout.widget_picker_activity);
|
||||
mDragLayer = findViewById(R.id.drag_layer);
|
||||
@@ -176,23 +119,15 @@ public class WidgetPickerActivity extends BaseActivity implements
|
||||
WindowInsetsController wc = mDragLayer.getWindowInsetsController();
|
||||
wc.hide(navigationBars() + statusBars());
|
||||
|
||||
BaseWidgetSheet widgetSheet = WidgetsFullSheet.show(this, true);
|
||||
widgetSheet.disableNavBarScrim(true);
|
||||
widgetSheet.addOnCloseListener(this::finish);
|
||||
|
||||
parseIntentExtras();
|
||||
refreshAndBindWidgets();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerBackDispatcher() {
|
||||
if (Utilities.ATLEAST_T) {
|
||||
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
|
||||
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
|
||||
new BackAnimationCallback());
|
||||
}
|
||||
}
|
||||
|
||||
private void parseIntentExtras() {
|
||||
mTitle = getIntent().getStringExtra(EXTRA_PICKER_TITLE);
|
||||
mDescription = getIntent().getStringExtra(EXTRA_PICKER_DESCRIPTION);
|
||||
|
||||
// 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 =
|
||||
@@ -201,19 +136,8 @@ public class WidgetPickerActivity extends BaseActivity implements
|
||||
getIntent().getIntExtra(EXTRA_DESIRED_WIDGET_HEIGHT, 0);
|
||||
|
||||
// Defaults to '0' to indicate that there isn't a category filter.
|
||||
// Negative value indicates it's an exclusion filter (e.g. NOT_KEYGUARD_CATEGORY.inv())
|
||||
// Positive value indicates it's inclusion filter (e.g. HOME_SCREEN or KEYGUARD)
|
||||
// Note: A filter can either be inclusion or exclusion filter; not both.
|
||||
int inclusionFilter = getIntent().getIntExtra(AppWidgetManager.EXTRA_CATEGORY_FILTER, 0);
|
||||
if (inclusionFilter < 0) {
|
||||
Log.w(TAG, "Invalid EXTRA_CATEGORY_FILTER: " + inclusionFilter);
|
||||
}
|
||||
mWidgetCategoryInclusionFilter = new WidgetCategoryFilter(max(0, inclusionFilter));
|
||||
int exclusionFilter = getIntent().getIntExtra(EXTRA_CATEGORY_EXCLUSION_FILTER, 0);
|
||||
if (exclusionFilter > 0) {
|
||||
Log.w(TAG, "Invalid EXTRA_CATEGORY_EXCLUSION_FILTER: " + exclusionFilter);
|
||||
}
|
||||
mWidgetCategoryExclusionFilter = new WidgetCategoryFilter(min(0 , exclusionFilter));
|
||||
mWidgetCategoryFilter =
|
||||
getIntent().getIntExtra(AppWidgetManager.EXTRA_CATEGORY_FILTER, 0);
|
||||
|
||||
String uiSurfaceParam = getIntent().getStringExtra(EXTRA_UI_SURFACE);
|
||||
if (uiSurfaceParam != null && UI_SURFACE_PATTERN.matcher(uiSurfaceParam).matches()) {
|
||||
@@ -226,18 +150,12 @@ public class WidgetPickerActivity extends BaseActivity implements
|
||||
mAddedWidgets = addedWidgets;
|
||||
}
|
||||
}
|
||||
ArrayList<Integer> filteredUsers = getIntent().getIntegerArrayListExtra(
|
||||
EXTRA_USER_ID_FILTER);
|
||||
mFilteredUserIds.clear();
|
||||
if (filteredUsers != null) {
|
||||
mFilteredUserIds.addAll(filteredUsers);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public WidgetPickerDataProvider getWidgetPickerDataProvider() {
|
||||
return mWidgetPickerDataProvider;
|
||||
public PopupDataProvider getPopupDataProvider() {
|
||||
return mPopupDataProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -307,213 +225,121 @@ public class WidgetPickerActivity extends BaseActivity implements
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the model with widgets, applies filters and launches the widgets sheet once
|
||||
* widgets are available
|
||||
*/
|
||||
/** Updates the model with widgets and provides them after applying the provided filter. */
|
||||
private void refreshAndBindWidgets() {
|
||||
MODEL_EXECUTOR.execute(() -> {
|
||||
mModel.update(null);
|
||||
|
||||
StringCache stringCache = new StringCache();
|
||||
stringCache.loadStrings(this);
|
||||
|
||||
bindStringCache(stringCache);
|
||||
bindWidgets(mModel.getWidgetsByPackageItemForPicker());
|
||||
// Open sheet once widgets are available, so that it doesn't interrupt the open
|
||||
// animation.
|
||||
openWidgetsSheet();
|
||||
LauncherAppState app = LauncherAppState.getInstance(this);
|
||||
mModel.update(app, null);
|
||||
final List<WidgetsListBaseEntry> allWidgets =
|
||||
mModel.getFilteredWidgetsListForPicker(
|
||||
app.getContext(),
|
||||
/*widgetItemFilter=*/ widget -> {
|
||||
final WidgetAcceptabilityVerdict verdict =
|
||||
isWidgetAcceptable(widget);
|
||||
verdict.maybeLogVerdict();
|
||||
return verdict.isAcceptable;
|
||||
}
|
||||
);
|
||||
bindWidgets(allWidgets);
|
||||
if (mUiSurface != null) {
|
||||
mWidgetPredictionsRequester = new WidgetPredictionsRequester(
|
||||
getApplicationContext(), mUiSurface,
|
||||
mModel.getWidgetsByComponentKeyForPicker());
|
||||
mWidgetPredictionsRequester.request(mAddedWidgets, this);
|
||||
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 bindStringCache(final StringCache stringCache) {
|
||||
MAIN_EXECUTOR.execute(() -> mStringCache = stringCache);
|
||||
private void bindWidgets(List<WidgetsListBaseEntry> widgets) {
|
||||
MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setAllWidgets(widgets));
|
||||
}
|
||||
|
||||
private void bindWidgets(Map<PackageItemInfo, List<WidgetItem>> widgets) {
|
||||
WidgetsListBaseEntriesBuilder builder = new WidgetsListBaseEntriesBuilder(
|
||||
mApp.getContext());
|
||||
final List<WidgetsListBaseEntry> allWidgets = builder.build(widgets, mNoShortcutsFilter);
|
||||
|
||||
// Default list is shown if host has additionally enforced size filtering.
|
||||
@Nullable Predicate<WidgetItem> defaultListFilter =
|
||||
hasHostSizeFilters() ? mHostSizeAndNoShortcutsFilter : null;
|
||||
|
||||
MAIN_EXECUTOR.execute(() -> {
|
||||
mWidgetPickerDataProvider.setHostSpecifiedDefaultWidgetsFilter(defaultListFilter);
|
||||
mWidgetPickerDataProvider.setWidgets(allWidgets);
|
||||
});
|
||||
}
|
||||
|
||||
private void openWidgetsSheet() {
|
||||
MAIN_EXECUTOR.execute(() -> {
|
||||
mWidgetSheet = WidgetsFullSheet.show(this, true);
|
||||
mWidgetSheet.mayUpdateTitleAndDescription(mTitle, mDescription);
|
||||
mWidgetSheet.disableNavBarScrim(true);
|
||||
mWidgetSheet.addOnCloseListener(this::finish);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPredictionsAvailable(List<ItemInfo> recommendedWidgets) {
|
||||
// Bind recommendations once picker has finished open animation.
|
||||
MAIN_EXECUTOR.getHandler().postDelayed(
|
||||
() -> mWidgetPickerDataProvider.setWidgetRecommendations(recommendedWidgets),
|
||||
mDeviceProfile.bottomSheetOpenDuration);
|
||||
private void bindRecommendedWidgets(List<ItemInfo> recommendedWidgets) {
|
||||
MAIN_EXECUTOR.execute(() -> mPopupDataProvider.setRecommendedWidgets(recommendedWidgets));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
mWidgetPickerDataProvider.destroy();
|
||||
if (mWidgetPredictionsRequester != null) {
|
||||
mWidgetPredictionsRequester.clear();
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public StringCache getStringCache() {
|
||||
return mStringCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Animation callback for different predictive back animation states for the widget picker.
|
||||
*/
|
||||
private class BackAnimationCallback extends FlingOnBackAnimationCallback {
|
||||
@Nullable
|
||||
OnBackAnimationCallback mActiveOnBackAnimationCallback;
|
||||
|
||||
@Override
|
||||
public void onBackStartedCompat(@NonNull BackEvent backEvent) {
|
||||
if (mActiveOnBackAnimationCallback != null) {
|
||||
mActiveOnBackAnimationCallback.onBackCancelled();
|
||||
}
|
||||
if (mWidgetSheet != null) {
|
||||
mActiveOnBackAnimationCallback = mWidgetSheet;
|
||||
mActiveOnBackAnimationCallback.onBackStarted(backEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackInvokedCompat() {
|
||||
if (mActiveOnBackAnimationCallback == null) {
|
||||
return;
|
||||
}
|
||||
mActiveOnBackAnimationCallback.onBackInvoked();
|
||||
mActiveOnBackAnimationCallback = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackProgressedCompat(@NonNull BackEvent backEvent) {
|
||||
if (mActiveOnBackAnimationCallback == null) {
|
||||
return;
|
||||
}
|
||||
mActiveOnBackAnimationCallback.onBackProgressed(backEvent);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackCancelledCompat() {
|
||||
if (mActiveOnBackAnimationCallback == null) {
|
||||
return;
|
||||
}
|
||||
mActiveOnBackAnimationCallback.onBackCancelled();
|
||||
mActiveOnBackAnimationCallback = null;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasHostSizeFilters() {
|
||||
// If optional filters such as size filter are present, we display them as default widgets.
|
||||
return mDesiredWidgetWidth != 0 || mDesiredWidgetHeight != 0;
|
||||
}
|
||||
|
||||
private WidgetAcceptabilityVerdict isWidgetAcceptable(WidgetItem widget,
|
||||
boolean applySizeFilter) {
|
||||
private WidgetAcceptabilityVerdict isWidgetAcceptable(WidgetItem widget) {
|
||||
final AppWidgetProviderInfo info = widget.widgetInfo;
|
||||
if (info == null) {
|
||||
return rejectWidget(widget, "shortcut");
|
||||
}
|
||||
|
||||
if (mFilteredUserIds.contains(widget.user.getIdentifier())) {
|
||||
if (mWidgetCategoryFilter > 0 && (info.widgetCategory & mWidgetCategoryFilter) == 0) {
|
||||
return rejectWidget(
|
||||
widget,
|
||||
"widget user: %d is being filtered",
|
||||
widget.user.getIdentifier());
|
||||
}
|
||||
|
||||
if (!mWidgetCategoryInclusionFilter.matches(info.widgetCategory)
|
||||
|| !mWidgetCategoryExclusionFilter.matches(info.widgetCategory)) {
|
||||
return rejectWidget(
|
||||
widget,
|
||||
"doesn't match category filter [inclusion=%d, exclusion=%d, widget=%d]",
|
||||
mWidgetCategoryInclusionFilter.getCategoryMask(),
|
||||
mWidgetCategoryExclusionFilter.getCategoryMask(),
|
||||
"doesn't match category filter [filter=%d, widget=%d]",
|
||||
mWidgetCategoryFilter,
|
||||
info.widgetCategory);
|
||||
}
|
||||
|
||||
if (applySizeFilter) {
|
||||
if (mDesiredWidgetWidth == 0 && mDesiredWidgetHeight == 0) {
|
||||
// Accept the widget if the desired dimensions are unspecified.
|
||||
return acceptWidget(widget);
|
||||
if (mDesiredWidgetWidth == 0 && mDesiredWidgetHeight == 0) {
|
||||
// Accept the widget if the desired dimensions are unspecified.
|
||||
return acceptWidget(widget);
|
||||
}
|
||||
|
||||
final boolean isHorizontallyResizable =
|
||||
(info.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
|
||||
if (mDesiredWidgetWidth > 0 && isHorizontallyResizable) {
|
||||
if (info.maxResizeWidth > 0
|
||||
&& info.maxResizeWidth >= info.minWidth
|
||||
&& info.maxResizeWidth < mDesiredWidgetWidth) {
|
||||
return rejectWidget(
|
||||
widget,
|
||||
"maxResizeWidth[%d] < mDesiredWidgetWidth[%d]",
|
||||
info.maxResizeWidth,
|
||||
mDesiredWidgetWidth);
|
||||
}
|
||||
|
||||
final boolean isHorizontallyResizable =
|
||||
(info.resizeMode & AppWidgetProviderInfo.RESIZE_HORIZONTAL) != 0;
|
||||
if (mDesiredWidgetWidth > 0 && isHorizontallyResizable) {
|
||||
if (info.maxResizeWidth > 0
|
||||
&& info.maxResizeWidth >= info.minWidth
|
||||
&& info.maxResizeWidth < mDesiredWidgetWidth) {
|
||||
return rejectWidget(
|
||||
widget,
|
||||
"maxResizeWidth[%d] < mDesiredWidgetWidth[%d]",
|
||||
info.maxResizeWidth,
|
||||
mDesiredWidgetWidth);
|
||||
}
|
||||
final int minWidth = Math.min(info.minResizeWidth, info.minWidth);
|
||||
if (minWidth > mDesiredWidgetWidth) {
|
||||
return rejectWidget(
|
||||
widget,
|
||||
"min(minWidth[%d], minResizeWidth[%d]) > mDesiredWidgetWidth[%d]",
|
||||
info.minWidth,
|
||||
info.minResizeWidth,
|
||||
mDesiredWidgetWidth);
|
||||
}
|
||||
}
|
||||
|
||||
final int minWidth = min(info.minResizeWidth, info.minWidth);
|
||||
if (minWidth > mDesiredWidgetWidth) {
|
||||
return rejectWidget(
|
||||
widget,
|
||||
"min(minWidth[%d], minResizeWidth[%d]) > mDesiredWidgetWidth[%d]",
|
||||
info.minWidth,
|
||||
info.minResizeWidth,
|
||||
mDesiredWidgetWidth);
|
||||
}
|
||||
final boolean isVerticallyResizable =
|
||||
(info.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
|
||||
if (mDesiredWidgetHeight > 0 && isVerticallyResizable) {
|
||||
if (info.maxResizeHeight > 0
|
||||
&& info.maxResizeHeight >= info.minHeight
|
||||
&& info.maxResizeHeight < mDesiredWidgetHeight) {
|
||||
return rejectWidget(
|
||||
widget,
|
||||
"maxResizeHeight[%d] < mDesiredWidgetHeight[%d]",
|
||||
info.maxResizeHeight,
|
||||
mDesiredWidgetHeight);
|
||||
}
|
||||
|
||||
final boolean isVerticallyResizable =
|
||||
(info.resizeMode & AppWidgetProviderInfo.RESIZE_VERTICAL) != 0;
|
||||
if (mDesiredWidgetHeight > 0 && isVerticallyResizable) {
|
||||
if (info.maxResizeHeight > 0
|
||||
&& info.maxResizeHeight >= info.minHeight
|
||||
&& info.maxResizeHeight < mDesiredWidgetHeight) {
|
||||
return rejectWidget(
|
||||
widget,
|
||||
"maxResizeHeight[%d] < mDesiredWidgetHeight[%d]",
|
||||
info.maxResizeHeight,
|
||||
mDesiredWidgetHeight);
|
||||
}
|
||||
|
||||
final int minHeight = min(info.minResizeHeight, info.minHeight);
|
||||
if (minHeight > mDesiredWidgetHeight) {
|
||||
return rejectWidget(
|
||||
widget,
|
||||
"min(minHeight[%d], minResizeHeight[%d]) > mDesiredWidgetHeight[%d]",
|
||||
info.minHeight,
|
||||
info.minResizeHeight,
|
||||
mDesiredWidgetHeight);
|
||||
}
|
||||
final int minHeight = Math.min(info.minResizeHeight, info.minHeight);
|
||||
if (minHeight > mDesiredWidgetHeight) {
|
||||
return rejectWidget(
|
||||
widget,
|
||||
"min(minHeight[%d], minResizeHeight[%d]) > mDesiredWidgetHeight[%d]",
|
||||
info.minHeight,
|
||||
info.minResizeHeight,
|
||||
mDesiredWidgetHeight);
|
||||
}
|
||||
}
|
||||
|
||||
if (!isHorizontallyResizable || !isVerticallyResizable) {
|
||||
return rejectWidget(widget, "not resizeable");
|
||||
}
|
||||
if (!isHorizontallyResizable || !isVerticallyResizable) {
|
||||
return rejectWidget(widget, "not resizeable");
|
||||
}
|
||||
|
||||
return acceptWidget(widget);
|
||||
|
||||
@@ -31,7 +31,7 @@ import android.view.View;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
@@ -84,9 +84,10 @@ public class AppsDividerView extends View implements FloatingHeaderRow {
|
||||
getResources().getDimensionPixelSize(R.dimen.all_apps_divider_height)
|
||||
};
|
||||
|
||||
mStrokeColor = context.getColor(R.color.materialColorOutlineVariant);
|
||||
mStrokeColor = ContextCompat.getColor(context, R.color.material_color_outline_variant);
|
||||
|
||||
mAllAppsLabelTextColor = context.getColor(R.color.materialColorOnSurfaceVariant);
|
||||
mAllAppsLabelTextColor = ContextCompat.getColor(context,
|
||||
R.color.material_color_on_surface_variant);
|
||||
|
||||
mAccessibilityManager = AccessibilityManager.getInstance(context);
|
||||
setShowAllAppsLabel(!ALL_APPS_VISITED_COUNT.hasReachedMax(context));
|
||||
@@ -253,9 +254,4 @@ public class AppsDividerView extends View implements FloatingHeaderRow {
|
||||
public View getFocusedChild() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public DividerType getDividerType() {
|
||||
return mDividerType;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,16 +16,12 @@
|
||||
|
||||
package com.android.launcher3.appprediction;
|
||||
|
||||
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Canvas;
|
||||
import android.os.Build;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.widget.LinearLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -35,6 +31,7 @@ import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
|
||||
import com.android.launcher3.Flags;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.allapps.FloatingHeaderRow;
|
||||
@@ -60,7 +57,7 @@ public class PredictionRowView<T extends Context & ActivityContext>
|
||||
// Vertical padding of the icon that contributes to the expected cell height.
|
||||
private final int mVerticalPadding;
|
||||
// Extra padding that is used in the top app rows (prediction and search) that is not used in
|
||||
// the regular A-Z list.
|
||||
// the regular A-Z list. This only applies to single line label.
|
||||
private final int mTopRowExtraHeight;
|
||||
|
||||
// Helper to drawing the focus indicator.
|
||||
@@ -93,14 +90,6 @@ public class PredictionRowView<T extends Context & ActivityContext>
|
||||
updateVisibility();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
|
||||
super.onInitializeAccessibilityNodeInfo(info);
|
||||
if (Build.VERSION.SDK_INT >= UPSIDE_DOWN_CAKE) {
|
||||
info.setContainerTitle(mActivityContext.getString(R.string.title_app_suggestions));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
@@ -149,8 +138,9 @@ public class PredictionRowView<T extends Context & ActivityContext>
|
||||
int totalHeight = iconHeight + iconPadding + textHeight + mVerticalPadding * 2;
|
||||
// Prediction row height will be 4dp bigger than the regular apps in A-Z list when two line
|
||||
// is not enabled. Otherwise, the extra height will increase by just the textHeight.
|
||||
int extraHeight = deviceProfile.inv.enableTwoLinesInAllApps
|
||||
? (textHeight + mTopRowExtraHeight) : mTopRowExtraHeight;
|
||||
int extraHeight = (Flags.enableTwolineToggle() &&
|
||||
LauncherPrefs.ENABLE_TWOLINE_ALLAPPS_TOGGLE.get(getContext()))
|
||||
? textHeight : mTopRowExtraHeight;
|
||||
totalHeight += extraHeight;
|
||||
return getVisibility() == GONE ? 0 : totalHeight + getPaddingTop() + getPaddingBottom();
|
||||
}
|
||||
@@ -300,9 +290,6 @@ public class PredictionRowView<T extends Context & ActivityContext>
|
||||
writer.println(prefix + "\tmPredictionsEnabled: " + mPredictionsEnabled);
|
||||
writer.println(prefix + "\tmPredictionUiUpdatePaused: " + mPredictionUiUpdatePaused);
|
||||
writer.println(prefix + "\tmNumPredictedAppsPerRow: " + mNumPredictedAppsPerRow);
|
||||
writer.println(prefix + "\tmPredictedApps: " + mPredictedApps.size());
|
||||
for (WorkspaceItemInfo info : mPredictedApps) {
|
||||
writer.println(prefix + "\t\t" + info);
|
||||
}
|
||||
writer.println(prefix + "\tmPredictedApps: " + mPredictedApps);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import android.os.IBinder
|
||||
import android.os.RemoteException
|
||||
import android.util.Log
|
||||
import android.view.SurfaceControl
|
||||
import android.view.WindowManager.TRANSIT_TO_FRONT
|
||||
import android.window.IRemoteTransitionFinishedCallback
|
||||
import android.window.RemoteTransition
|
||||
import android.window.RemoteTransitionStub
|
||||
@@ -30,12 +29,8 @@ import com.android.launcher3.statemanager.StateManager
|
||||
import com.android.launcher3.util.Executors.MAIN_EXECUTOR
|
||||
import com.android.quickstep.SystemUiProxy
|
||||
import com.android.quickstep.TaskViewUtils
|
||||
import com.android.quickstep.util.DesksUtils.Companion.areMultiDesksFlagsEnabled
|
||||
import com.android.quickstep.views.DesktopTaskView
|
||||
import com.android.quickstep.views.TaskContainer
|
||||
import com.android.quickstep.views.TaskView
|
||||
import com.android.window.flags.Flags
|
||||
import com.android.wm.shell.shared.desktopmode.DesktopModeTransitionSource
|
||||
import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
|
||||
import java.util.function.Consumer
|
||||
|
||||
/** Manage recents related operations with desktop tasks */
|
||||
@@ -43,63 +38,42 @@ class DesktopRecentsTransitionController(
|
||||
private val stateManager: StateManager<*, *>,
|
||||
private val systemUiProxy: SystemUiProxy,
|
||||
private val appThread: IApplicationThread,
|
||||
private val depthController: DepthController?,
|
||||
private val depthController: DepthController?
|
||||
) {
|
||||
|
||||
/** Launch desktop tasks from recents view */
|
||||
fun launchDesktopFromRecents(
|
||||
desktopTaskView: DesktopTaskView,
|
||||
animated: Boolean,
|
||||
callback: Consumer<Boolean>? = null,
|
||||
callback: Consumer<Boolean>? = null
|
||||
) {
|
||||
val animRunner =
|
||||
RemoteDesktopLaunchTransitionRunner(
|
||||
desktopTaskView,
|
||||
animated,
|
||||
stateManager,
|
||||
depthController,
|
||||
callback,
|
||||
callback
|
||||
)
|
||||
val transition = RemoteTransition(animRunner, appThread, "RecentsToDesktop")
|
||||
if (areMultiDesksFlagsEnabled()) {
|
||||
systemUiProxy.activateDesk(desktopTaskView.deskId, transition)
|
||||
} else {
|
||||
systemUiProxy.showDesktopApps(desktopTaskView.displayId, transition)
|
||||
}
|
||||
systemUiProxy.showDesktopApps(desktopTaskView.display.displayId, transition)
|
||||
}
|
||||
|
||||
/** Launch desktop tasks from recents view */
|
||||
fun moveToDesktop(
|
||||
taskContainer: TaskContainer,
|
||||
transitionSource: DesktopModeTransitionSource,
|
||||
successCallback: Runnable,
|
||||
) {
|
||||
systemUiProxy.moveToDesktop(
|
||||
taskContainer.task.key.id,
|
||||
transitionSource,
|
||||
/* transition = */ null,
|
||||
successCallback,
|
||||
)
|
||||
}
|
||||
|
||||
/** Move task to external display from recents view */
|
||||
fun moveToExternalDisplay(taskId: Int) {
|
||||
systemUiProxy.moveToExternalDisplay(taskId)
|
||||
fun moveToDesktop(taskId: Int, transitionSource: DesktopModeTransitionSource) {
|
||||
systemUiProxy.moveToDesktop(taskId, transitionSource)
|
||||
}
|
||||
|
||||
private class RemoteDesktopLaunchTransitionRunner(
|
||||
private val taskView: TaskView,
|
||||
private val animated: Boolean,
|
||||
private val desktopTaskView: DesktopTaskView,
|
||||
private val stateManager: StateManager<*, *>,
|
||||
private val depthController: DepthController?,
|
||||
private val successCallback: Consumer<Boolean>?,
|
||||
private val successCallback: Consumer<Boolean>?
|
||||
) : RemoteTransitionStub() {
|
||||
|
||||
override fun startAnimation(
|
||||
token: IBinder,
|
||||
info: TransitionInfo,
|
||||
t: SurfaceControl.Transaction,
|
||||
finishCallback: IRemoteTransitionFinishedCallback,
|
||||
finishCallback: IRemoteTransitionFinishedCallback
|
||||
) {
|
||||
val errorHandlingFinishCallback = Runnable {
|
||||
try {
|
||||
@@ -109,44 +83,16 @@ class DesktopRecentsTransitionController(
|
||||
}
|
||||
}
|
||||
|
||||
if (Flags.enableDesktopWindowingPersistence()) {
|
||||
handleAnimationAfterReboot(info)
|
||||
}
|
||||
MAIN_EXECUTOR.execute {
|
||||
val animator =
|
||||
TaskViewUtils.composeRecentsDesktopLaunchAnimator(
|
||||
taskView,
|
||||
stateManager,
|
||||
depthController,
|
||||
info,
|
||||
t,
|
||||
) {
|
||||
errorHandlingFinishCallback.run()
|
||||
successCallback?.accept(true)
|
||||
}
|
||||
if (!animated) {
|
||||
animator.setDuration(0)
|
||||
}
|
||||
animator.start()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Upon reboot the start bounds of a task is set to fullscreen with the recents transition.
|
||||
* Check this case and set the start bounds to the end bounds so that the window doesn't
|
||||
* jump from start bounds to end bounds during the animation. Tasks in desktop cannot
|
||||
* normally have top bound as 0 due to status bar so this is a good indicator to identify
|
||||
* reboot case.
|
||||
*/
|
||||
private fun handleAnimationAfterReboot(info: TransitionInfo) {
|
||||
info.changes.forEach { change ->
|
||||
if (
|
||||
change.mode == TRANSIT_TO_FRONT &&
|
||||
change.taskInfo?.isFreeform == true &&
|
||||
change.startAbsBounds.top == 0 &&
|
||||
change.startAbsBounds.left == 0
|
||||
TaskViewUtils.composeRecentsDesktopLaunchAnimator(
|
||||
desktopTaskView,
|
||||
stateManager,
|
||||
depthController,
|
||||
info,
|
||||
t
|
||||
) {
|
||||
change.setStartAbsBounds(change.endAbsBounds)
|
||||
errorHandlingFinishCallback.run()
|
||||
successCallback?.accept(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +229,9 @@ public class HotseatPredictionController implements DragController.DragListener,
|
||||
(WorkspaceItemInfo) mPredictedItems.get(predictionIndex++);
|
||||
if (isPredictedIcon(child) && child.isEnabled()) {
|
||||
PredictedAppIcon icon = (PredictedAppIcon) child;
|
||||
if (icon.applyFromWorkspaceItemWithAnimation(predictedItem, numViewsAnimated)) {
|
||||
boolean animateIconChange = icon.shouldAnimateIconChange(predictedItem);
|
||||
icon.applyFromWorkspaceItem(predictedItem, animateIconChange, numViewsAnimated);
|
||||
if (animateIconChange) {
|
||||
numViewsAnimated++;
|
||||
}
|
||||
icon.finishBinding(mPredictionLongClickListener);
|
||||
@@ -534,9 +536,6 @@ public class HotseatPredictionController implements DragController.DragListener,
|
||||
writer.println(prefix + "HotseatPredictionController");
|
||||
writer.println(prefix + "\tFlags: " + getStateString(mPauseFlags));
|
||||
writer.println(prefix + "\tmHotSeatItemsCount: " + mHotSeatItemsCount);
|
||||
writer.println(prefix + "\tmPredictedItems: " + mPredictedItems.size());
|
||||
for (ItemInfo info : mPredictedItems) {
|
||||
writer.println(prefix + "\t\t" + info);
|
||||
}
|
||||
writer.println(prefix + "\tmPredictedItems: " + mPredictedItems);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ package com.android.launcher3.hybridhotseat;
|
||||
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
|
||||
import static com.android.launcher3.model.PredictionHelper.getAppTargetFromItemInfo;
|
||||
import static com.android.launcher3.model.PredictionHelper.isTrackedForHotseatPrediction;
|
||||
import static com.android.launcher3.model.PredictionHelper.wrapAppTargetWithItemLocation;
|
||||
|
||||
import android.app.prediction.AppTarget;
|
||||
@@ -26,12 +27,9 @@ import android.os.Bundle;
|
||||
|
||||
import com.android.launcher3.model.BgDataModel;
|
||||
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
|
||||
import com.android.launcher3.model.PredictionHelper;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Model helper for app predictions in workspace
|
||||
@@ -45,18 +43,13 @@ public class HotseatPredictionModel {
|
||||
*/
|
||||
public static Bundle convertDataModelToAppTargetBundle(Context context, BgDataModel dataModel) {
|
||||
Bundle bundle = new Bundle();
|
||||
ArrayList<AppTargetEvent> events = dataModel.itemsIdMap
|
||||
.stream()
|
||||
.filter(PredictionHelper::isTrackedForHotseatPrediction)
|
||||
.map(item -> {
|
||||
AppTarget target = getAppTargetFromItemInfo(context, item);
|
||||
return target != null
|
||||
? wrapAppTargetWithItemLocation(target, AppTargetEvent.ACTION_PIN, item)
|
||||
: null;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
|
||||
ArrayList<AppTargetEvent> events = new ArrayList<>();
|
||||
ArrayList<ItemInfo> workspaceItems = dataModel.getAllWorkspaceItems();
|
||||
for (ItemInfo item : workspaceItems) {
|
||||
AppTarget target = getAppTargetFromItemInfo(context, item);
|
||||
if (target != null && !isTrackedForHotseatPrediction(item)) continue;
|
||||
events.add(wrapAppTargetWithItemLocation(target, AppTargetEvent.ACTION_PIN, item));
|
||||
}
|
||||
ArrayList<AppTarget> currentTargets = new ArrayList<>();
|
||||
FixedContainerItems hotseatItems = dataModel.extraItems.get(CONTAINER_HOTSEAT_PREDICTION);
|
||||
if (hotseatItems != null) {
|
||||
|
||||
@@ -18,7 +18,6 @@ package com.android.launcher3.model;
|
||||
import static com.android.launcher3.EncryptionType.ENCRYPTED;
|
||||
import static com.android.launcher3.LauncherPrefs.nonRestorableItem;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
|
||||
import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
|
||||
import static com.android.quickstep.InstantAppResolverImpl.COMPONENT_CLASS_MARKER;
|
||||
|
||||
import android.app.prediction.AppTarget;
|
||||
@@ -32,9 +31,9 @@ import android.os.UserHandle;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.ConstantItem;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.LauncherModel.ModelUpdateTask;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.icons.IconCache;
|
||||
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
|
||||
import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
@@ -65,8 +64,8 @@ public class PredictionUpdateTask implements ModelUpdateTask {
|
||||
@Override
|
||||
public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
|
||||
@NonNull AllAppsList apps) {
|
||||
IconCache iconCache = taskController.getIconCache();
|
||||
Context context = taskController.getContext();
|
||||
LauncherAppState app = taskController.getApp();
|
||||
Context context = app.getContext();
|
||||
|
||||
// TODO: remove this
|
||||
LauncherPrefs.get(context).put(LAST_PREDICTION_ENABLED, !mTargets.isEmpty());
|
||||
@@ -84,7 +83,7 @@ public class PredictionUpdateTask implements ModelUpdateTask {
|
||||
if (si != null) {
|
||||
usersForChangedShortcuts.add(si.getUserHandle());
|
||||
itemInfo = new WorkspaceItemInfo(si, context);
|
||||
iconCache.getShortcutIcon(itemInfo, si);
|
||||
app.getIconCache().getShortcutIcon(itemInfo, si);
|
||||
} else {
|
||||
String className = target.getClassName();
|
||||
if (COMPONENT_CLASS_MARKER.equals(className)) {
|
||||
@@ -96,7 +95,7 @@ public class PredictionUpdateTask implements ModelUpdateTask {
|
||||
itemInfo = apps.data.stream()
|
||||
.filter(info -> user.equals(info.user) && cn.equals(info.componentName))
|
||||
.map(ai -> {
|
||||
iconCache.getTitleAndIcon(ai, mPredictorState.lookupFlag);
|
||||
app.getIconCache().getTitleAndIcon(ai, false);
|
||||
return ai.makeWorkspaceItem(context);
|
||||
})
|
||||
.findAny()
|
||||
@@ -107,7 +106,7 @@ public class PredictionUpdateTask implements ModelUpdateTask {
|
||||
return null;
|
||||
}
|
||||
AppInfo ai = new AppInfo(context, lai, user);
|
||||
iconCache.getTitleAndIcon(ai, lai, DEFAULT_LOOKUP_FLAG);
|
||||
app.getIconCache().getTitleAndIcon(ai, lai, false);
|
||||
return ai.makeWorkspaceItem(context);
|
||||
});
|
||||
|
||||
@@ -123,7 +122,8 @@ public class PredictionUpdateTask implements ModelUpdateTask {
|
||||
FixedContainerItems fci = new FixedContainerItems(mPredictorState.containerId, items);
|
||||
dataModel.extraItems.put(fci.containerId, fci);
|
||||
taskController.bindExtraContainerItems(fci);
|
||||
usersForChangedShortcuts.forEach(u -> dataModel.updateShortcutPinnedState(context, u));
|
||||
usersForChangedShortcuts.forEach(
|
||||
u -> dataModel.updateShortcutPinnedState(app.getContext(), u));
|
||||
|
||||
// Save to disk
|
||||
mPredictorState.storage.write(context, fci.items);
|
||||
|
||||
@@ -23,11 +23,9 @@ import static com.android.launcher3.LauncherPrefs.nonRestorableItem;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_PREDICTION;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.DESKTOP_ICON_FLAG;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_APPLICATION;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
|
||||
import static com.android.launcher3.hybridhotseat.HotseatPredictionModel.convertDataModelToAppTargetBundle;
|
||||
import static com.android.launcher3.icons.cache.CacheLookupFlag.DEFAULT_LOOKUP_FLAG;
|
||||
import static com.android.launcher3.model.PredictionHelper.getAppTargetFromItemInfo;
|
||||
import static com.android.launcher3.model.PredictionHelper.wrapAppTargetWithItemLocation;
|
||||
import static com.android.launcher3.util.Executors.MODEL_EXECUTOR;
|
||||
@@ -49,12 +47,12 @@ import android.content.pm.PackageManager;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.os.Bundle;
|
||||
import android.os.UserHandle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Log;
|
||||
import android.util.StatsEvent;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.Keep;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
@@ -65,8 +63,7 @@ import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.LauncherPrefs;
|
||||
import com.android.launcher3.dagger.ApplicationContext;
|
||||
import com.android.launcher3.icons.cache.CacheLookupFlag;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.logger.LauncherAtom;
|
||||
import com.android.launcher3.logging.InstanceId;
|
||||
import com.android.launcher3.logging.InstanceIdSequence;
|
||||
@@ -84,7 +81,6 @@ import com.android.launcher3.util.PackageManagerHelper;
|
||||
import com.android.launcher3.util.PersistedItemArray;
|
||||
import com.android.quickstep.logging.SettingsChangeLogger;
|
||||
import com.android.quickstep.logging.StatsLogCompatManager;
|
||||
import com.android.quickstep.util.ContextualSearchStateManager;
|
||||
import com.android.systemui.shared.system.SysUiStatsLog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -94,9 +90,6 @@ import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.stream.IntStream;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Named;
|
||||
|
||||
import app.lawnchair.LawnchairApp;
|
||||
import app.lawnchair.compat.LawnchairQuickstepCompat;
|
||||
|
||||
@@ -115,39 +108,30 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
nonRestorableItem("LAST_SNAPSHOT_TIME_MILLIS", 0L, ENCRYPTED);
|
||||
|
||||
@VisibleForTesting
|
||||
final PredictorState mAllAppsState = new PredictorState(
|
||||
CONTAINER_PREDICTION, "all_apps_predictions", DEFAULT_LOOKUP_FLAG);
|
||||
final PredictorState mAllAppsState = new PredictorState(CONTAINER_PREDICTION, "all_apps_predictions");
|
||||
@VisibleForTesting
|
||||
final PredictorState mHotseatState = new PredictorState(
|
||||
CONTAINER_HOTSEAT_PREDICTION, "hotseat_predictions", DESKTOP_ICON_FLAG);
|
||||
final PredictorState mHotseatState = new PredictorState(CONTAINER_HOTSEAT_PREDICTION, "hotseat_predictions");
|
||||
@VisibleForTesting
|
||||
final PredictorState mWidgetsRecommendationState = new PredictorState(
|
||||
CONTAINER_WIDGETS_PREDICTION, "widgets_prediction", DESKTOP_ICON_FLAG);
|
||||
final PredictorState mWidgetsRecommendationState = new PredictorState(CONTAINER_WIDGETS_PREDICTION,
|
||||
"widgets_prediction");
|
||||
|
||||
private final InvariantDeviceProfile mIDP;
|
||||
private final PackageManagerHelper mPmHelper;
|
||||
private final AppEventProducer mAppEventProducer;
|
||||
|
||||
private final StatsManager mStatsManager;
|
||||
private StatsManager mStatsManager;
|
||||
|
||||
protected boolean mActive = false;
|
||||
|
||||
@Inject
|
||||
public QuickstepModelDelegate(@ApplicationContext Context context,
|
||||
InvariantDeviceProfile idp,
|
||||
PackageManagerHelper pmHelper,
|
||||
@Nullable @Named("ICONS_DB") String dbFileName) {
|
||||
public QuickstepModelDelegate(Context context) {
|
||||
super(context);
|
||||
mIDP = idp;
|
||||
mPmHelper = pmHelper;
|
||||
|
||||
mAppEventProducer = new AppEventProducer(context, this::onAppTargetEvent);
|
||||
StatsLogCompatManager.LOGS_CONSUMER.add(mAppEventProducer);
|
||||
|
||||
// Only register for launcher snapshot logging if this is the primary ModelDelegate
|
||||
// instance, as there will be additional instances that may be destroyed at any time.
|
||||
mStatsManager = TextUtils.isEmpty(dbFileName)
|
||||
? null : context.getSystemService(StatsManager.class);
|
||||
mIDP = InvariantDeviceProfile.INSTANCE.get(context);
|
||||
StatsLogCompatManager.LOGS_CONSUMER.add(mAppEventProducer);
|
||||
try {
|
||||
mStatsManager = context.getSystemService(StatsManager.class);
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "Failed to get StatsManager", e);
|
||||
}
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@@ -176,10 +160,13 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
// TODO: Implement caching and preloading
|
||||
|
||||
WorkspaceItemFactory factory =
|
||||
new WorkspaceItemFactory(mContext, ums, mPmHelper, pinnedShortcuts, numColumns,
|
||||
state.containerId, state.lookupFlag);
|
||||
new WorkspaceItemFactory(mApp, ums, mPmHelper, pinnedShortcuts, numColumns,
|
||||
state.containerId);
|
||||
FixedContainerItems fci = new FixedContainerItems(state.containerId,
|
||||
state.storage.read(mContext, factory, ums.allUsers::get));
|
||||
state.storage.read(mApp.getContext(), factory, ums.allUsers::get));
|
||||
if (FeatureFlags.CHANGE_MODEL_DELEGATE_LOADING_ORDER.get()) {
|
||||
bindPredictionItems(callbacks, fci);
|
||||
}
|
||||
mDataModel.extraItems.put(state.containerId, fci);
|
||||
}
|
||||
|
||||
@@ -189,7 +176,8 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
FixedContainerItems widgetPredictionFCI = new FixedContainerItems(
|
||||
mWidgetsRecommendationState.containerId, new ArrayList<>());
|
||||
|
||||
// Widgets prediction isn't used frequently. And thus, it is not persisted on disk.
|
||||
// Widgets prediction isn't used frequently. And thus, it is not persisted on
|
||||
// disk.
|
||||
mDataModel.extraItems.put(mWidgetsRecommendationState.containerId, widgetPredictionFCI);
|
||||
|
||||
bindPredictionItems(callbacks, widgetPredictionFCI);
|
||||
@@ -206,6 +194,7 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
});
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@Override
|
||||
@WorkerThread
|
||||
public void bindAllModelExtras(@NonNull BgDataModel.Callbacks[] callbacks) {
|
||||
@@ -227,12 +216,9 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
mActive = true;
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
@Override
|
||||
public void workspaceLoadComplete() {
|
||||
super.workspaceLoadComplete();
|
||||
// Initialize ContextualSearchStateManager.
|
||||
ContextualSearchStateManager.INSTANCE.get(mContext);
|
||||
recreatePredictors();
|
||||
}
|
||||
|
||||
@@ -242,7 +228,7 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
super.modelLoadComplete();
|
||||
|
||||
// Log snapshot of the model
|
||||
LauncherPrefs prefs = LauncherPrefs.get(mContext);
|
||||
LauncherPrefs prefs = LauncherPrefs.get(mApp.getContext());
|
||||
long lastSnapshotTimeMillis = prefs.get(LAST_SNAPSHOT_TIME_MILLIS);
|
||||
// Log snapshot only if previous snapshot was older than a day
|
||||
long now = System.currentTimeMillis();
|
||||
@@ -261,23 +247,32 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
InstanceId instanceId = new InstanceIdSequence().newInstanceId();
|
||||
for (ItemInfo info : itemsIdMap) {
|
||||
CollectionInfo parent = getContainer(info, itemsIdMap);
|
||||
StatsLogCompatManager.writeSnapshot(info.buildProto(parent, mContext), instanceId);
|
||||
StatsLogCompatManager.writeSnapshot(info.buildProto(parent), instanceId);
|
||||
}
|
||||
additionalSnapshotEvents(instanceId);
|
||||
prefs.put(LAST_SNAPSHOT_TIME_MILLIS, now);
|
||||
}
|
||||
|
||||
registerSnapshotLoggingCallback();
|
||||
// Only register for launcher snapshot logging if this is the primary
|
||||
// ModelDelegate
|
||||
// instance, as there will be additional instances that may be destroyed at any
|
||||
// time.
|
||||
if (mIsPrimaryInstance && LawnchairApp.isRecentsEnabled()) {
|
||||
registerSnapshotLoggingCallback();
|
||||
}
|
||||
}
|
||||
|
||||
protected void additionalSnapshotEvents(InstanceId snapshotInstanceId){}
|
||||
protected void additionalSnapshotEvents(InstanceId snapshotInstanceId) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a callback to log launcher workspace layout using Statsd pulled atom.
|
||||
* Registers a callback to log launcher workspace layout using Statsd pulled
|
||||
* atom.
|
||||
*/
|
||||
private void registerSnapshotLoggingCallback() {
|
||||
protected void registerSnapshotLoggingCallback() {
|
||||
if (mStatsManager == null || !LawnchairQuickstepCompat.ATLEAST_R) {
|
||||
Log.d(TAG, "Skipping snapshot logging");
|
||||
Log.d(TAG, "Failed to get StatsManager");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -294,7 +289,7 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
|
||||
for (ItemInfo info : itemsIdMap) {
|
||||
CollectionInfo parent = getContainer(info, itemsIdMap);
|
||||
LauncherAtom.ItemInfo itemInfo = info.buildProto(parent, mContext);
|
||||
LauncherAtom.ItemInfo itemInfo = info.buildProto(parent);
|
||||
Log.d(TAG, itemInfo.toString());
|
||||
StatsEvent statsEvent = StatsLogCompatManager.buildStatsEvent(itemInfo,
|
||||
instanceId);
|
||||
@@ -307,8 +302,7 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
additionalSnapshotEvents(instanceId);
|
||||
SettingsChangeLogger.INSTANCE.get(mContext).logSnapshot(instanceId);
|
||||
return StatsManager.PULL_SUCCESS;
|
||||
}
|
||||
);
|
||||
});
|
||||
Log.d(TAG, "Successfully registered for launcher snapshot logging!");
|
||||
} catch (Throwable e) {
|
||||
Log.e(TAG, "Failed to register launcher snapshot logging callback with StatsManager",
|
||||
@@ -344,13 +338,12 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
@Override
|
||||
public void destroy() {
|
||||
super.destroy();
|
||||
mActive = false;
|
||||
StatsLogCompatManager.LOGS_CONSUMER.remove(mAppEventProducer);
|
||||
if (mStatsManager != null && LawnchairQuickstepCompat.ATLEAST_R) {
|
||||
if (mIsPrimaryInstance && mStatsManager != null && LawnchairQuickstepCompat.ATLEAST_R) {
|
||||
try {
|
||||
mStatsManager.clearPullAtomCallback(SysUiStatsLog.LAUNCHER_LAYOUT_SNAPSHOT);
|
||||
} catch (Throwable e) {
|
||||
@@ -372,28 +365,29 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
if (!Utilities.ATLEAST_Q || !mActive) {
|
||||
return;
|
||||
}
|
||||
AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class);
|
||||
Context context = mApp.getContext();
|
||||
AppPredictionManager apm = context.getSystemService(AppPredictionManager.class);
|
||||
if (apm == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
int usagePerm = mContext.checkCallingOrSelfPermission(Manifest.permission.PACKAGE_USAGE_STATS);
|
||||
int usagePerm = mApp.getContext().checkCallingOrSelfPermission(Manifest.permission.PACKAGE_USAGE_STATS);
|
||||
if (usagePerm != PackageManager.PERMISSION_GRANTED)
|
||||
return;
|
||||
|
||||
registerPredictor(mAllAppsState, apm.createAppPredictionSession(
|
||||
new AppPredictionContext.Builder(mContext)
|
||||
new AppPredictionContext.Builder(context)
|
||||
.setUiSurface("home")
|
||||
.setPredictedTargetCount(mIDP.numDatabaseAllAppsColumns)
|
||||
.build()));
|
||||
|
||||
// TODO: get bundle
|
||||
registerHotseatPredictor(apm, mContext);
|
||||
registerHotseatPredictor(apm, context);
|
||||
|
||||
registerWidgetsPredictor(apm.createAppPredictionSession(
|
||||
new AppPredictionContext.Builder(mContext)
|
||||
new AppPredictionContext.Builder(context)
|
||||
.setUiSurface("widgets")
|
||||
.setExtras(getBundleForWidgetsOnWorkspace(mContext, mDataModel))
|
||||
.setExtras(getBundleForWidgetsOnWorkspace(context, mDataModel))
|
||||
.setPredictedTargetCount(NUM_OF_RECOMMENDED_WIDGETS_PREDICATION)
|
||||
.build()));
|
||||
}
|
||||
@@ -404,11 +398,12 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
if (!mActive) {
|
||||
return;
|
||||
}
|
||||
AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class);
|
||||
Context context = mApp.getContext();
|
||||
AppPredictionManager apm = context.getSystemService(AppPredictionManager.class);
|
||||
if (apm == null) {
|
||||
return;
|
||||
}
|
||||
registerHotseatPredictor(apm, mContext);
|
||||
registerHotseatPredictor(apm, context);
|
||||
}
|
||||
|
||||
private void registerHotseatPredictor(AppPredictionManager apm, Context context) {
|
||||
@@ -424,7 +419,13 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
state.setTargets(Collections.emptyList());
|
||||
state.predictor = predictor;
|
||||
state.predictor.registerPredictionUpdates(
|
||||
MODEL_EXECUTOR, t -> handleUpdate(state, t));
|
||||
MODEL_EXECUTOR, new AppPredictor.Callback() {
|
||||
@Keep
|
||||
@Override
|
||||
public void onTargetsAvailable(@NonNull List<AppTarget> targets) {
|
||||
handleUpdate(state, targets);
|
||||
}
|
||||
});
|
||||
state.predictor.requestPredictionUpdate();
|
||||
}
|
||||
|
||||
@@ -433,19 +434,23 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
// No diff, skip
|
||||
return;
|
||||
}
|
||||
mModel.enqueueModelUpdateTask(new PredictionUpdateTask(state, targets));
|
||||
mApp.getModel().enqueueModelUpdateTask(new PredictionUpdateTask(state, targets));
|
||||
}
|
||||
|
||||
private void registerWidgetsPredictor(AppPredictor predictor) {
|
||||
mWidgetsRecommendationState.predictor = predictor;
|
||||
mWidgetsRecommendationState.predictor.registerPredictionUpdates(
|
||||
MODEL_EXECUTOR, targets -> {
|
||||
if (mWidgetsRecommendationState.setTargets(targets)) {
|
||||
// No diff, skip
|
||||
return;
|
||||
MODEL_EXECUTOR, new AppPredictor.Callback() {
|
||||
@Keep
|
||||
@Override
|
||||
public void onTargetsAvailable(@NonNull List<AppTarget> targets) {
|
||||
if (mWidgetsRecommendationState.setTargets(targets)) {
|
||||
// No diff, skip
|
||||
return;
|
||||
}
|
||||
mApp.getModel().enqueueModelUpdateTask(
|
||||
new WidgetsPredictionUpdateTask(mWidgetsRecommendationState, targets));
|
||||
}
|
||||
mModel.enqueueModelUpdateTask(
|
||||
new WidgetsPredictionUpdateTask(mWidgetsRecommendationState, targets));
|
||||
});
|
||||
mWidgetsRecommendationState.predictor.requestPredictionUpdate();
|
||||
}
|
||||
@@ -453,7 +458,7 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
@VisibleForTesting
|
||||
void onAppTargetEvent(AppTargetEvent event, int client) {
|
||||
PredictorState state;
|
||||
switch(client) {
|
||||
switch (client) {
|
||||
case CONTAINER_PREDICTION:
|
||||
state = mAllAppsState;
|
||||
break;
|
||||
@@ -481,17 +486,17 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
|
||||
private Bundle getBundleForWidgetsOnWorkspace(Context context, BgDataModel dataModel) {
|
||||
Bundle bundle = new Bundle();
|
||||
ArrayList<AppTargetEvent> widgetEvents =
|
||||
dataModel.itemsIdMap.stream()
|
||||
.filter(PredictionHelper::isTrackedForWidgetPrediction)
|
||||
.map(item -> {
|
||||
AppTarget target = getAppTargetFromItemInfo(context, item);
|
||||
if (target == null) return null;
|
||||
return wrapAppTargetWithItemLocation(
|
||||
target, AppTargetEvent.ACTION_PIN, item);
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(toCollection(ArrayList::new));
|
||||
ArrayList<AppTargetEvent> widgetEvents = dataModel.getAllWorkspaceItems().stream()
|
||||
.filter(PredictionHelper::isTrackedForWidgetPrediction)
|
||||
.map(item -> {
|
||||
AppTarget target = getAppTargetFromItemInfo(context, item);
|
||||
if (target == null)
|
||||
return null;
|
||||
return wrapAppTargetWithItemLocation(
|
||||
target, AppTargetEvent.ACTION_PIN, item);
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(toCollection(ArrayList::new));
|
||||
bundle.putParcelableArrayList(BUNDLE_KEY_ADDED_APP_WIDGETS, widgetEvents);
|
||||
return bundle;
|
||||
}
|
||||
@@ -501,15 +506,13 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
public final int containerId;
|
||||
public final PersistedItemArray<ItemInfo> storage;
|
||||
public AppPredictor predictor;
|
||||
public CacheLookupFlag lookupFlag;
|
||||
|
||||
private List<AppTarget> mLastTargets;
|
||||
|
||||
PredictorState(int containerId, String storageName, CacheLookupFlag lookupFlag) {
|
||||
PredictorState(int containerId, String storageName) {
|
||||
this.containerId = containerId;
|
||||
storage = new PersistedItemArray<>(storageName);
|
||||
mLastTargets = Collections.emptyList();
|
||||
this.lookupFlag = lookupFlag;
|
||||
}
|
||||
|
||||
public void destroyPredictor() {
|
||||
@@ -556,27 +559,24 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
|
||||
private static class WorkspaceItemFactory implements PersistedItemArray.ItemFactory<ItemInfo> {
|
||||
|
||||
private final Context mContext;
|
||||
private final LauncherAppState mAppState;
|
||||
private final UserManagerState mUMS;
|
||||
private final PackageManagerHelper mPmHelper;
|
||||
private final Map<ShortcutKey, ShortcutInfo> mPinnedShortcuts;
|
||||
private final int mMaxCount;
|
||||
private final int mContainer;
|
||||
private final CacheLookupFlag mLookupFlag;
|
||||
|
||||
private int mReadCount = 0;
|
||||
|
||||
protected WorkspaceItemFactory(
|
||||
Context context, UserManagerState ums,
|
||||
protected WorkspaceItemFactory(LauncherAppState appState, UserManagerState ums,
|
||||
PackageManagerHelper pmHelper, Map<ShortcutKey, ShortcutInfo> pinnedShortcuts,
|
||||
int maxCount, int container, CacheLookupFlag lookupFlag) {
|
||||
mContext = context;
|
||||
int maxCount, int container) {
|
||||
mAppState = appState;
|
||||
mUMS = ums;
|
||||
mPmHelper = pmHelper;
|
||||
mPinnedShortcuts = pinnedShortcuts;
|
||||
mMaxCount = maxCount;
|
||||
mContainer = container;
|
||||
mLookupFlag = lookupFlag;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -587,7 +587,7 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
}
|
||||
switch (itemType) {
|
||||
case ITEM_TYPE_APPLICATION: {
|
||||
LauncherActivityInfo lai = mContext
|
||||
LauncherActivityInfo lai = mAppState.getContext()
|
||||
.getSystemService(LauncherApps.class)
|
||||
.resolveActivity(intent, user);
|
||||
if (lai == null) {
|
||||
@@ -595,15 +595,14 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
}
|
||||
AppInfo info = new AppInfo(
|
||||
lai,
|
||||
UserCache.INSTANCE.get(mContext).getUserInfo(user),
|
||||
ApiWrapper.INSTANCE.get(mContext),
|
||||
UserCache.INSTANCE.get(mAppState.getContext()).getUserInfo(user),
|
||||
ApiWrapper.INSTANCE.get(mAppState.getContext()),
|
||||
mPmHelper,
|
||||
mUMS.isUserQuiet(user));
|
||||
info.container = mContainer;
|
||||
LauncherAppState.getInstance(mContext).getIconCache()
|
||||
.getTitleAndIcon(info, lai, mLookupFlag);
|
||||
mAppState.getIconCache().getTitleAndIcon(info, lai, false);
|
||||
mReadCount++;
|
||||
return info.makeWorkspaceItem(mContext);
|
||||
return info.makeWorkspaceItem(mAppState.getContext());
|
||||
}
|
||||
case ITEM_TYPE_DEEP_SHORTCUT: {
|
||||
ShortcutKey key = ShortcutKey.fromIntent(intent, user);
|
||||
@@ -614,9 +613,9 @@ public class QuickstepModelDelegate extends ModelDelegate {
|
||||
if (si == null) {
|
||||
return null;
|
||||
}
|
||||
WorkspaceItemInfo wii = new WorkspaceItemInfo(si, mContext);
|
||||
WorkspaceItemInfo wii = new WorkspaceItemInfo(si, mAppState.getContext());
|
||||
wii.container = mContainer;
|
||||
LauncherAppState.getInstance(mContext).getIconCache().getShortcutIcon(wii, si);
|
||||
mAppState.getIconCache().getShortcutIcon(wii, si);
|
||||
mReadCount++;
|
||||
return wii;
|
||||
}
|
||||
|
||||
@@ -44,30 +44,23 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.WorkerThread;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.dagger.ApplicationContext;
|
||||
import com.android.launcher3.dagger.LauncherAppSingleton;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.popup.RemoteActionShortcut;
|
||||
import com.android.launcher3.popup.SystemShortcut;
|
||||
import com.android.launcher3.util.DaggerSingletonObject;
|
||||
import com.android.launcher3.util.DaggerSingletonTracker;
|
||||
import com.android.launcher3.util.Executors;
|
||||
import com.android.launcher3.util.MainThreadInitializedObject;
|
||||
import com.android.launcher3.util.Preconditions;
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.launcher3.util.SimpleBroadcastReceiver;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
import com.android.quickstep.dagger.QuickstepBaseAppComponent;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
/**
|
||||
* Data model for digital wellbeing status of apps.
|
||||
*/
|
||||
@LauncherAppSingleton
|
||||
public final class WellbeingModel implements SafeCloseable {
|
||||
private static final String TAG = "WellbeingModel";
|
||||
private static final int[] RETRY_TIMES_MS = {5000, 15000, 30000};
|
||||
@@ -82,16 +75,18 @@ public final class WellbeingModel implements SafeCloseable {
|
||||
private static final String EXTRA_PACKAGES = "packages";
|
||||
private static final String EXTRA_SUCCESS = "success";
|
||||
|
||||
public static final DaggerSingletonObject<WellbeingModel> INSTANCE =
|
||||
new DaggerSingletonObject<>(QuickstepBaseAppComponent::getWellbeingModel);
|
||||
public static final MainThreadInitializedObject<WellbeingModel> INSTANCE =
|
||||
new MainThreadInitializedObject<>(WellbeingModel::new);
|
||||
|
||||
private final Context mContext;
|
||||
private final String mWellbeingProviderPkg;
|
||||
|
||||
private final Handler mWorkerHandler;
|
||||
private final ContentObserver mContentObserver;
|
||||
private final SimpleBroadcastReceiver mWellbeingAppChangeReceiver;
|
||||
private final SimpleBroadcastReceiver mAppAddRemoveReceiver;
|
||||
private final SimpleBroadcastReceiver mWellbeingAppChangeReceiver =
|
||||
new SimpleBroadcastReceiver(t -> restartObserver());
|
||||
private final SimpleBroadcastReceiver mAppAddRemoveReceiver =
|
||||
new SimpleBroadcastReceiver(this::onAppPackageChanged);
|
||||
|
||||
private final Object mModelLock = new Object();
|
||||
// Maps the action Id to the corresponding RemoteAction
|
||||
@@ -100,19 +95,12 @@ public final class WellbeingModel implements SafeCloseable {
|
||||
|
||||
private boolean mIsInTest;
|
||||
|
||||
@Inject
|
||||
WellbeingModel(@ApplicationContext final Context context,
|
||||
DaggerSingletonTracker tracker) {
|
||||
private WellbeingModel(final Context context) {
|
||||
mContext = context;
|
||||
mWellbeingProviderPkg = mContext.getString(R.string.wellbeing_provider_pkg);
|
||||
mWorkerHandler = new Handler(TextUtils.isEmpty(mWellbeingProviderPkg)
|
||||
? Executors.UI_HELPER_EXECUTOR.getLooper()
|
||||
: Executors.getPackageExecutor(mWellbeingProviderPkg).getLooper());
|
||||
mWellbeingAppChangeReceiver =
|
||||
new SimpleBroadcastReceiver(context, mWorkerHandler, t -> restartObserver());
|
||||
mAppAddRemoveReceiver =
|
||||
new SimpleBroadcastReceiver(context, mWorkerHandler, this::onAppPackageChanged);
|
||||
|
||||
|
||||
mContentObserver = new ContentObserver(mWorkerHandler) {
|
||||
@Override
|
||||
@@ -121,10 +109,8 @@ public final class WellbeingModel implements SafeCloseable {
|
||||
}
|
||||
};
|
||||
mWorkerHandler.post(this::initializeInBackground);
|
||||
tracker.addCloseable(this);
|
||||
}
|
||||
|
||||
@WorkerThread
|
||||
private void initializeInBackground() {
|
||||
if (!TextUtils.isEmpty(mWellbeingProviderPkg)) {
|
||||
mContext.registerReceiver(
|
||||
@@ -148,8 +134,8 @@ public final class WellbeingModel implements SafeCloseable {
|
||||
public void close() {
|
||||
if (!TextUtils.isEmpty(mWellbeingProviderPkg)) {
|
||||
mWorkerHandler.post(() -> {
|
||||
mWellbeingAppChangeReceiver.unregisterReceiverSafely();
|
||||
mAppAddRemoveReceiver.unregisterReceiverSafely();
|
||||
mWellbeingAppChangeReceiver.unregisterReceiverSafely(mContext);
|
||||
mAppAddRemoveReceiver.unregisterReceiverSafely(mContext);
|
||||
mContext.getContentResolver().unregisterContentObserver(mContentObserver);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
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;
|
||||
@@ -39,6 +40,7 @@ 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;
|
||||
|
||||
@@ -47,6 +49,7 @@ 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;
|
||||
|
||||
@@ -54,91 +57,72 @@ 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 implements AppPredictor.Callback {
|
||||
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";
|
||||
// container/screenid/[positionx,positiony]/[spanx,spany]
|
||||
// Matches the format passed used by PredictionHelper; But, position and size values aren't
|
||||
// used, so, we pass default values.
|
||||
@VisibleForTesting
|
||||
static final String LAUNCH_LOCATION = "workspace/1/[0,0]/[2,2]";
|
||||
|
||||
@Nullable
|
||||
private AppPredictor mAppPredictor;
|
||||
private final Context mContext;
|
||||
@NonNull
|
||||
private final String mUiSurface;
|
||||
private boolean mPredictionsAvailable;
|
||||
@Nullable
|
||||
private WidgetPredictionsListener mPredictionsListener = null;
|
||||
@Nullable Predicate<WidgetItem> mFilter = null;
|
||||
@NonNull
|
||||
private final Map<ComponentKey, WidgetItem> mAllWidgets;
|
||||
private final Map<PackageUserKey, List<WidgetItem>> mAllWidgets;
|
||||
|
||||
public WidgetPredictionsRequester(Context context, @NonNull String uiSurface,
|
||||
@NonNull Map<ComponentKey, WidgetItem> allWidgets) {
|
||||
@NonNull Map<PackageUserKey, List<WidgetItem>> allWidgets) {
|
||||
mContext = context;
|
||||
mUiSurface = uiSurface;
|
||||
mAllWidgets = Collections.unmodifiableMap(allWidgets);
|
||||
}
|
||||
|
||||
// AppPredictor.Callback -> onTargetsAvailable
|
||||
@Override
|
||||
@WorkerThread
|
||||
public void onTargetsAvailable(List<AppTarget> targets) {
|
||||
List<WidgetItem> filteredPredictions = filterPredictions(targets, mAllWidgets, mFilter);
|
||||
List<ItemInfo> mappedPredictions = mapWidgetItemsToItemInfo(filteredPredictions);
|
||||
|
||||
if (!mPredictionsAvailable && mPredictionsListener != null) {
|
||||
mPredictionsAvailable = true;
|
||||
MAIN_EXECUTOR.execute(
|
||||
() -> mPredictionsListener.onPredictionsAvailable(mappedPredictions));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests one time predictions from the app predictions manager and invokes provided callback
|
||||
* once predictions are available. Any previous requests may be cancelled.
|
||||
* 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 listener consumer of prediction results to be called when predictions are
|
||||
* available; any previous listener will no longer receive updates.
|
||||
* @param callback consumer of prediction results to be called when predictions are
|
||||
* available
|
||||
*/
|
||||
@WorkerThread // e.g. MODEL_EXECUTOR
|
||||
public void request(List<AppWidgetProviderInfo> existingWidgets,
|
||||
WidgetPredictionsListener listener) {
|
||||
clear();
|
||||
mPredictionsListener = listener;
|
||||
mFilter = notOnUiSurfaceFilter(existingWidgets);
|
||||
Consumer<List<ItemInfo>> callback) {
|
||||
Bundle bundle = buildBundleForPredictionSession(existingWidgets, mUiSurface);
|
||||
Predicate<WidgetItem> filter = notOnUiSurfaceFilter(existingWidgets);
|
||||
|
||||
AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class);
|
||||
if (apm == null) {
|
||||
return;
|
||||
}
|
||||
MODEL_EXECUTOR.execute(() -> {
|
||||
clear();
|
||||
AppPredictionManager apm = mContext.getSystemService(AppPredictionManager.class);
|
||||
if (apm == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Bundle bundle = buildBundleForPredictionSession(existingWidgets);
|
||||
mAppPredictor = apm.createAppPredictionSession(
|
||||
new AppPredictionContext.Builder(mContext)
|
||||
.setUiSurface(mUiSurface)
|
||||
.setExtras(bundle)
|
||||
.setPredictedTargetCount(NUM_OF_RECOMMENDED_WIDGETS_PREDICATION)
|
||||
.build());
|
||||
mAppPredictor.registerPredictionUpdates(MODEL_EXECUTOR, /*callback=*/ this);
|
||||
mAppPredictor.requestPredictionUpdate();
|
||||
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) {
|
||||
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(info, componentName);
|
||||
AppTargetEvent appTargetEvent = buildAppTargetEvent(uiSurface, info, componentName);
|
||||
addedAppTargetEvents.add(appTargetEvent);
|
||||
}
|
||||
bundle.putParcelableArrayList(BUNDLE_KEY_ADDED_APP_WIDGETS, addedAppTargetEvents);
|
||||
@@ -150,13 +134,13 @@ public class WidgetPredictionsRequester implements AppPredictor.Callback {
|
||||
* predictor.
|
||||
* Also see {@link PredictionHelper}
|
||||
*/
|
||||
private static AppTargetEvent buildAppTargetEvent(AppWidgetProviderInfo info,
|
||||
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(
|
||||
LAUNCH_LOCATION).build();
|
||||
return new AppTargetEvent.Builder(appTarget, AppTargetEvent.ACTION_PIN)
|
||||
.setLaunchLocation(uiSurface).build();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -172,26 +156,49 @@ public class WidgetPredictionsRequester implements AppPredictor.Callback {
|
||||
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,
|
||||
@NonNull Map<ComponentKey, WidgetItem> allWidgets,
|
||||
@Nullable Predicate<WidgetItem> filter) {
|
||||
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 widgetItem = allWidgets.get(
|
||||
new ComponentKey(new ComponentName(prediction.getPackageName(), className),
|
||||
prediction.getUser()));
|
||||
if (widgetItem != null && (filter == null || filter.test(widgetItem))) {
|
||||
servicePredictedItems.add(widgetItem);
|
||||
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;
|
||||
@@ -201,34 +208,26 @@ public class WidgetPredictionsRequester implements AppPredictor.Callback {
|
||||
* Converts the list of {@link WidgetItem}s to the list of {@link ItemInfo}s.
|
||||
*/
|
||||
private List<ItemInfo> mapWidgetItemsToItemInfo(List<WidgetItem> widgetItems) {
|
||||
WidgetRecommendationCategoryProvider categoryProvider =
|
||||
new WidgetRecommendationCategoryProvider();
|
||||
return widgetItems.stream()
|
||||
.map(it -> new PendingAddWidgetInfo(it.widgetInfo, CONTAINER_WIDGETS_PREDICTION,
|
||||
categoryProvider.getWidgetRecommendationCategory(mContext, it)))
|
||||
.collect(Collectors.toList());
|
||||
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.unregisterPredictionUpdates(this);
|
||||
mAppPredictor.destroy();
|
||||
mAppPredictor = null;
|
||||
}
|
||||
mPredictionsListener = null;
|
||||
mPredictionsAvailable = false;
|
||||
mFilter = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Listener class to listen to updates from the {@link WidgetPredictionsRequester}
|
||||
*/
|
||||
public interface WidgetPredictionsListener {
|
||||
/**
|
||||
* Callback method that is called when the predicted widgets are available.
|
||||
* @param predictions list of predicted widgets {@link PendingAddWidgetInfo}
|
||||
*/
|
||||
void onPredictionsAvailable(List<ItemInfo> predictions);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,12 +15,8 @@
|
||||
*/
|
||||
package com.android.launcher3.model;
|
||||
|
||||
import static com.android.launcher3.Flags.enableTieredWidgetsByDefaultInPicker;
|
||||
import static com.android.launcher3.Flags.enableCategorizedWidgetSuggestions;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_WIDGETS_PREDICTION;
|
||||
import static com.android.launcher3.model.ModelUtils.WIDGET_FILTER;
|
||||
|
||||
import static java.util.stream.Collectors.groupingBy;
|
||||
import static java.util.stream.Collectors.toMap;
|
||||
|
||||
import android.app.prediction.AppTarget;
|
||||
import android.content.ComponentName;
|
||||
@@ -30,7 +26,6 @@ import android.text.TextUtils;
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.LauncherModel.ModelUpdateTask;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
|
||||
import com.android.launcher3.model.QuickstepModelDelegate.PredictorState;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
@@ -39,11 +34,10 @@ 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.Random;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/** Task to update model as a result of predicted widgets update */
|
||||
@@ -66,74 +60,45 @@ public final class WidgetsPredictionUpdateTask implements ModelUpdateTask {
|
||||
@Override
|
||||
public void execute(@NonNull ModelTaskController taskController, @NonNull BgDataModel dataModel,
|
||||
@NonNull AllAppsList apps) {
|
||||
Set<ComponentKey> widgetsInWorkspace = dataModel.itemsIdMap
|
||||
.stream()
|
||||
.filter(WIDGET_FILTER)
|
||||
.map(item -> new ComponentKey(item.getTargetComponent(), item.user))
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
// Widgets (excluding shortcuts & already added widgets) that belong to apps eligible for
|
||||
// being in predictions.
|
||||
Map<ComponentKey, WidgetItem> allEligibleWidgets =
|
||||
dataModel.widgetsModel.getWidgetsByComponentKeyForPicker()
|
||||
.entrySet()
|
||||
.stream()
|
||||
.filter(entry -> entry.getValue().widgetInfo != null
|
||||
&& !widgetsInWorkspace.contains(entry.getValue())
|
||||
).collect(toMap(Map.Entry::getKey, Map.Entry::getValue));
|
||||
|
||||
Context context = taskController.getContext();
|
||||
Set<ComponentKey> widgetsInWorkspace = dataModel.appWidgets.stream().map(
|
||||
widget -> new ComponentKey(widget.providerName, widget.user)).collect(
|
||||
Collectors.toSet());
|
||||
Predicate<WidgetItem> notOnWorkspace = w -> !widgetsInWorkspace.contains(w);
|
||||
Map<ComponentKey, WidgetItem> allWidgets =
|
||||
dataModel.widgetsModel.getAllWidgetComponentsWithoutShortcuts();
|
||||
|
||||
List<WidgetItem> servicePredictedItems = new ArrayList<>();
|
||||
List<String> addedWidgetApps = new ArrayList<>();
|
||||
|
||||
for (AppTarget app : mTargets) {
|
||||
ComponentKey componentKey = new ComponentKey(
|
||||
new ComponentName(app.getPackageName(), app.getClassName()), app.getUser());
|
||||
WidgetItem widget = allEligibleWidgets.get(componentKey);
|
||||
if (widget == null) { // widget not eligible.
|
||||
WidgetItem widget = allWidgets.get(componentKey);
|
||||
if (widget == null) {
|
||||
continue;
|
||||
}
|
||||
String className = app.getClassName();
|
||||
if (!TextUtils.isEmpty(className)) {
|
||||
servicePredictedItems.add(widget);
|
||||
addedWidgetApps.add(componentKey.componentName.getPackageName());
|
||||
if (notOnWorkspace.test(widget)) {
|
||||
servicePredictedItems.add(widget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int minPredictionCount = context.getResources().getInteger(
|
||||
R.integer.widget_predictions_min_count);
|
||||
if (enableTieredWidgetsByDefaultInPicker()
|
||||
&& servicePredictedItems.size() < minPredictionCount) {
|
||||
// Eligible apps that aren't already part of predictions.
|
||||
Map<String, List<WidgetItem>> eligibleWidgetsByApp =
|
||||
allEligibleWidgets.values().stream()
|
||||
.filter(w -> !addedWidgetApps.contains(
|
||||
w.componentName.getPackageName()))
|
||||
.collect(groupingBy(w -> w.componentName.getPackageName()));
|
||||
|
||||
// Randomize available apps list
|
||||
List<String> appPackages = new ArrayList<>(eligibleWidgetsByApp.keySet());
|
||||
Collections.shuffle(appPackages);
|
||||
|
||||
int widgetsToAdd = minPredictionCount - servicePredictedItems.size();
|
||||
for (String appPackage : appPackages) {
|
||||
if (widgetsToAdd <= 0) break;
|
||||
|
||||
List<WidgetItem> widgetsForApp = eligibleWidgetsByApp.get(appPackage);
|
||||
int index = new Random().nextInt(widgetsForApp.size());
|
||||
// Add a random widget from the app.
|
||||
servicePredictedItems.add(widgetsForApp.get(index));
|
||||
widgetsToAdd--;
|
||||
}
|
||||
List<ItemInfo> items;
|
||||
if (enableCategorizedWidgetSuggestions()) {
|
||||
Context context = taskController.getApp().getContext();
|
||||
WidgetRecommendationCategoryProvider categoryProvider =
|
||||
WidgetRecommendationCategoryProvider.newInstance(context);
|
||||
items = servicePredictedItems.stream()
|
||||
.map(it -> new PendingAddWidgetInfo(it.widgetInfo, CONTAINER_WIDGETS_PREDICTION,
|
||||
categoryProvider.getWidgetRecommendationCategory(context, it)))
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
items = servicePredictedItems.stream()
|
||||
.map(it -> new PendingAddWidgetInfo(it.widgetInfo,
|
||||
CONTAINER_WIDGETS_PREDICTION)).collect(
|
||||
Collectors.toList());
|
||||
}
|
||||
|
||||
WidgetRecommendationCategoryProvider categoryProvider =
|
||||
new WidgetRecommendationCategoryProvider();
|
||||
List<ItemInfo> items = servicePredictedItems.stream()
|
||||
.map(it -> new PendingAddWidgetInfo(it.widgetInfo, CONTAINER_WIDGETS_PREDICTION,
|
||||
categoryProvider.getWidgetRecommendationCategory(context, it)))
|
||||
.collect(Collectors.toList());
|
||||
FixedContainerItems fixedContainerItems =
|
||||
new FixedContainerItems(mPredictorState.containerId, items);
|
||||
|
||||
|
||||
@@ -61,7 +61,6 @@ public class ProxyActivityStarter extends Activity {
|
||||
}
|
||||
} catch (NullPointerException | ActivityNotFoundException | SecurityException
|
||||
| SendIntentException e) {
|
||||
Log.w(TAG, "Proxy activity starter could not start activity: ", e);
|
||||
mParams.deliverResult(this, RESULT_CANCELED, null);
|
||||
}
|
||||
finishAndRemoveTask();
|
||||
|
||||
@@ -19,7 +19,6 @@ package com.android.launcher3.statehandlers;
|
||||
import static com.android.app.animation.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_DEPTH;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.SKIP_DEPTH_CONTROLLER;
|
||||
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
|
||||
import static com.android.launcher3.util.MultiPropertyFactory.MULTI_PROPERTY_VALUE;
|
||||
|
||||
import android.animation.Animator;
|
||||
@@ -30,8 +29,6 @@ import android.view.View;
|
||||
import android.view.ViewRootImpl;
|
||||
import android.view.ViewTreeObserver;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.launcher3.BaseActivity;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherState;
|
||||
@@ -40,11 +37,11 @@ import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.statemanager.StateManager.StateHandler;
|
||||
import com.android.launcher3.states.StateAnimationConfig;
|
||||
import com.android.quickstep.util.BaseDepthController;
|
||||
import com.patrykmichalik.opto.core.PreferenceExtensionsKt;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.patrykmichalik.opto.core.PreferenceExtensionsKt;
|
||||
import app.lawnchair.compat.LawnchairQuickstepCompat;
|
||||
import app.lawnchair.preferences2.PreferenceManager2;
|
||||
|
||||
@@ -53,8 +50,8 @@ import app.lawnchair.preferences2.PreferenceManager2;
|
||||
*/
|
||||
public class DepthController extends BaseDepthController implements StateHandler<LauncherState>,
|
||||
BaseActivity.MultiWindowModeChangedListener {
|
||||
@VisibleForTesting
|
||||
final ViewTreeObserver.OnDrawListener mOnDrawListener = this::onLauncherDraw;
|
||||
|
||||
private final ViewTreeObserver.OnDrawListener mOnDrawListener = this::onLauncherDraw;
|
||||
|
||||
private final Consumer<Boolean> mCrossWindowBlurListener = this::setCrossWindowBlursEnabled;
|
||||
|
||||
@@ -65,10 +62,6 @@ public class DepthController extends BaseDepthController implements StateHandler
|
||||
|
||||
private View.OnAttachStateChangeListener mOnAttachListener;
|
||||
|
||||
// Ensure {@link mOnDrawListener} is added only once to avoid spamming DragLayer's mRunQueue
|
||||
// via {@link View#post(Runnable)}
|
||||
private boolean mIsOnDrawListenerAdded = false;
|
||||
|
||||
private final boolean mEnableDepth;
|
||||
|
||||
public DepthController(Launcher l) {
|
||||
@@ -82,46 +75,41 @@ public class DepthController extends BaseDepthController implements StateHandler
|
||||
ViewRootImpl viewRootImpl = view.getViewRootImpl();
|
||||
try {
|
||||
if (Utilities.ATLEAST_Q) {
|
||||
setBaseSurface(viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null);
|
||||
setSurface(viewRootImpl != null ? viewRootImpl.getSurfaceControl() : null);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// LC-Ignored
|
||||
// Ignore any exceptions
|
||||
}
|
||||
view.post(this::removeOnDrawListener);
|
||||
view.post(() -> view.getViewTreeObserver().removeOnDrawListener(mOnDrawListener));
|
||||
}
|
||||
|
||||
private void ensureDependencies() {
|
||||
View rootView = mLauncher.getRootView();
|
||||
if (rootView == null) {
|
||||
return;
|
||||
}
|
||||
if (mOnAttachListener != null) {
|
||||
return;
|
||||
}
|
||||
mOnAttachListener = new View.OnAttachStateChangeListener() {
|
||||
@Override
|
||||
public void onViewAttachedToWindow(View view) {
|
||||
try {
|
||||
UI_HELPER_EXECUTOR.execute(() ->
|
||||
CrossWindowBlurListeners.getInstance().addListener(
|
||||
mLauncher.getMainExecutor(), mCrossWindowBlurListener));
|
||||
} catch (Throwable t) {
|
||||
// LC-Ignored
|
||||
if (mLauncher.getRootView() != null && mOnAttachListener == null) {
|
||||
View rootView = mLauncher.getRootView();
|
||||
mOnAttachListener = new View.OnAttachStateChangeListener() {
|
||||
@Override
|
||||
public void onViewAttachedToWindow(View view) {
|
||||
try {
|
||||
CrossWindowBlurListeners.getInstance().addListener(mLauncher.getMainExecutor(),
|
||||
mCrossWindowBlurListener);
|
||||
} catch (Throwable t) {
|
||||
// Ignore
|
||||
}
|
||||
mLauncher.getScrimView().addOpaquenessListener(mOpaquenessListener);
|
||||
|
||||
// To handle the case where window token is invalid during last setDepth call.
|
||||
applyDepthAndBlur();
|
||||
}
|
||||
mLauncher.getScrimView().addOpaquenessListener(mOpaquenessListener);
|
||||
|
||||
// To handle the case where window token is invalid during last setDepth call.
|
||||
applyDepthAndBlur();
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(View view) {
|
||||
removeSecondaryListeners();
|
||||
}
|
||||
};
|
||||
rootView.addOnAttachStateChangeListener(mOnAttachListener);
|
||||
if (rootView.isAttachedToWindow()) {
|
||||
mOnAttachListener.onViewAttachedToWindow(rootView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewDetachedFromWindow(View view) {
|
||||
removeSecondaryListeners();
|
||||
}
|
||||
};
|
||||
rootView.addOnAttachStateChangeListener(mOnAttachListener);
|
||||
if (rootView.isAttachedToWindow()) {
|
||||
mOnAttachListener.onViewAttachedToWindow(rootView);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,12 +126,12 @@ public class DepthController extends BaseDepthController implements StateHandler
|
||||
}
|
||||
|
||||
private void removeSecondaryListeners() {
|
||||
try {
|
||||
UI_HELPER_EXECUTOR.execute(() ->
|
||||
CrossWindowBlurListeners.getInstance()
|
||||
.removeListener(mCrossWindowBlurListener));
|
||||
} catch (Throwable t) {
|
||||
// LC-Ignored
|
||||
if (mCrossWindowBlurListener != null) {
|
||||
try {
|
||||
CrossWindowBlurListeners.getInstance().removeListener(mCrossWindowBlurListener);
|
||||
} catch (Throwable t) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
if (mOpaquenessListener != null) {
|
||||
mLauncher.getScrimView().removeOpaquenessListener(mOpaquenessListener);
|
||||
@@ -155,10 +143,10 @@ public class DepthController extends BaseDepthController implements StateHandler
|
||||
*/
|
||||
public void setActivityStarted(boolean isStarted) {
|
||||
if (isStarted) {
|
||||
addOnDrawListener();
|
||||
mLauncher.getDragLayer().getViewTreeObserver().addOnDrawListener(mOnDrawListener);
|
||||
} else {
|
||||
removeOnDrawListener();
|
||||
setBaseSurface(null);
|
||||
mLauncher.getDragLayer().getViewTreeObserver().removeOnDrawListener(mOnDrawListener);
|
||||
setSurface(null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,13 +158,13 @@ public class DepthController extends BaseDepthController implements StateHandler
|
||||
|
||||
stateDepth.setValue(toState.getDepth(mLauncher));
|
||||
if (toState == LauncherState.BACKGROUND_APP) {
|
||||
addOnDrawListener();
|
||||
mLauncher.getDragLayer().getViewTreeObserver().addOnDrawListener(mOnDrawListener);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
|
||||
PendingAnimation animation) {
|
||||
PendingAnimation animation) {
|
||||
if (config.hasAnimationFlag(SKIP_DEPTH_CONTROLLER)
|
||||
|| mIgnoreStateChangesDuringMultiWindowAnimation) {
|
||||
return;
|
||||
@@ -195,30 +183,14 @@ public class DepthController extends BaseDepthController implements StateHandler
|
||||
super.applyDepthAndBlur();
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// LC-Ignored
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onInvalidSurface() {
|
||||
// Lets wait for surface to become valid again
|
||||
addOnDrawListener();
|
||||
}
|
||||
|
||||
private void addOnDrawListener() {
|
||||
if (mIsOnDrawListenerAdded) {
|
||||
return;
|
||||
}
|
||||
mLauncher.getDragLayer().getViewTreeObserver().addOnDrawListener(mOnDrawListener);
|
||||
mIsOnDrawListenerAdded = true;
|
||||
}
|
||||
|
||||
private void removeOnDrawListener() {
|
||||
if (!mIsOnDrawListenerAdded) {
|
||||
return;
|
||||
}
|
||||
mLauncher.getDragLayer().getViewTreeObserver().removeOnDrawListener(mOnDrawListener);
|
||||
mIsOnDrawListenerAdded = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -226,7 +198,7 @@ public class DepthController extends BaseDepthController implements StateHandler
|
||||
mIgnoreStateChangesDuringMultiWindowAnimation = true;
|
||||
|
||||
ObjectAnimator mwAnimation = ObjectAnimator.ofFloat(stateDepth, MULTI_PROPERTY_VALUE,
|
||||
mLauncher.getStateManager().getState().getDepth(mLauncher, isInMultiWindowMode))
|
||||
mLauncher.getStateManager().getState().getDepth(mLauncher, isInMultiWindowMode))
|
||||
.setDuration(300);
|
||||
mwAnimation.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
@@ -242,8 +214,7 @@ public class DepthController extends BaseDepthController implements StateHandler
|
||||
writer.println(prefix + "DepthController");
|
||||
writer.println(prefix + "\tmMaxBlurRadius=" + mMaxBlurRadius);
|
||||
writer.println(prefix + "\tmCrossWindowBlursEnabled=" + mCrossWindowBlursEnabled);
|
||||
writer.println(prefix + "\tmBaseSurface=" + mBaseSurface);
|
||||
writer.println(prefix + "\tmBaseSurfaceOverride=" + mBaseSurfaceOverride);
|
||||
writer.println(prefix + "\tmSurface=" + mSurface);
|
||||
writer.println(prefix + "\tmStateDepth=" + stateDepth.getValue());
|
||||
writer.println(prefix + "\tmWidgetDepth=" + widgetDepth.getValue());
|
||||
writer.println(prefix + "\tmCurrentBlur=" + mCurrentBlur);
|
||||
@@ -253,4 +224,4 @@ public class DepthController extends BaseDepthController implements StateHandler
|
||||
writer.println(prefix + "\tmPauseBlurs=" + mPauseBlurs);
|
||||
writer.println(prefix + "\tmWaitingOnSurfaceValidity=" + mWaitingOnSurfaceValidity);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,82 +16,36 @@
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.os.UserHandle;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import com.android.launcher3.popup.SystemShortcut;
|
||||
import com.android.launcher3.util.BaseContext;
|
||||
import com.android.launcher3.util.NavigationMode;
|
||||
import com.android.launcher3.DeviceProfile.OnDeviceProfileChangeListener;
|
||||
import com.android.launcher3.util.Themes;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
// TODO(b/218912746): Share more behavior to avoid all apps context depending directly on taskbar.
|
||||
/** Base for common behavior between taskbar window contexts. */
|
||||
public abstract class BaseTaskbarContext extends BaseContext
|
||||
implements SystemShortcut.BubbleActivityStarter {
|
||||
public abstract class BaseTaskbarContext extends ContextThemeWrapper implements ActivityContext {
|
||||
|
||||
protected final LayoutInflater mLayoutInflater;
|
||||
private final List<OnDeviceProfileChangeListener> mDPChangeListeners = new ArrayList<>();
|
||||
|
||||
public BaseTaskbarContext(Context windowContext, boolean isPrimaryDisplay) {
|
||||
public BaseTaskbarContext(Context windowContext) {
|
||||
super(windowContext, Themes.getActivityThemeRes(windowContext));
|
||||
mLayoutInflater = LayoutInflater.from(this).cloneInContext(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether taskbar is transient or persistent. External displays will be persistent.
|
||||
*
|
||||
* @return {@code true} if transient, {@code false} if persistent.
|
||||
*/
|
||||
public abstract boolean isTransientTaskbar();
|
||||
|
||||
/**
|
||||
* Returns whether the taskbar is pinned in gesture navigation mode.
|
||||
*/
|
||||
public abstract boolean isPinnedTaskbar();
|
||||
|
||||
/**
|
||||
* Returns the current navigation mode. External displays will be in THREE_BUTTONS mode.
|
||||
*/
|
||||
public abstract NavigationMode getNavigationMode();
|
||||
|
||||
/**
|
||||
* Returns whether the taskbar is in desktop mode.
|
||||
*/
|
||||
public abstract boolean isInDesktopMode();
|
||||
|
||||
/**
|
||||
* Returns whether the taskbar is forced to be pinned when home is visible.
|
||||
*/
|
||||
public abstract boolean showLockedTaskbarOnHome();
|
||||
|
||||
/**
|
||||
* Returns whether desktop taskbar (pinned taskbar that shows desktop tasks) is to be used on
|
||||
* the display because the display is a freeform display.
|
||||
*/
|
||||
public abstract boolean showDesktopTaskbarForFreeformDisplay();
|
||||
|
||||
/**
|
||||
* Returns whether the taskbar is displayed on primary or external display.
|
||||
*/
|
||||
public abstract boolean isPrimaryDisplay();
|
||||
|
||||
@Override
|
||||
public final LayoutInflater getLayoutInflater() {
|
||||
return mLayoutInflater;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showShortcutBubble(ShortcutInfo info) {
|
||||
if (info == null) return;
|
||||
SystemUiProxy.INSTANCE.get(this).showShortcutBubble(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showAppBubble(Intent intent, UserHandle user) {
|
||||
if (intent == null || intent.getPackage() == null) return;
|
||||
SystemUiProxy.INSTANCE.get(this).showAppBubble(intent, user);
|
||||
public final List<OnDeviceProfileChangeListener> getOnDeviceProfileChangeListeners() {
|
||||
return mDPChangeListeners;
|
||||
}
|
||||
|
||||
/** Callback invoked when a drag is initiated within this context. */
|
||||
|
||||
@@ -25,27 +25,20 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.popup.SystemShortcut;
|
||||
import com.android.launcher3.statemanager.StateManager;
|
||||
import com.android.launcher3.statemanager.StatefulContainer;
|
||||
import com.android.quickstep.FallbackActivityInterface;
|
||||
import com.android.quickstep.GestureState;
|
||||
import com.android.quickstep.RecentsAnimationCallbacks;
|
||||
import com.android.quickstep.RecentsActivity;
|
||||
import com.android.quickstep.TopTaskTracker;
|
||||
import com.android.quickstep.fallback.RecentsState;
|
||||
import com.android.quickstep.util.TISBindHelper;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.RecentsViewContainer;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
/**
|
||||
* A data source which integrates with the fallback RecentsActivity instance (for 3P launchers).
|
||||
* @param <T> The type of the RecentsViewContainer that will handle Recents state changes.
|
||||
*/
|
||||
public class FallbackTaskbarUIController
|
||||
<T extends RecentsViewContainer & StatefulContainer<RecentsState>>
|
||||
extends TaskbarUIController {
|
||||
public class FallbackTaskbarUIController extends TaskbarUIController {
|
||||
|
||||
private final T mRecentsContainer;
|
||||
private final RecentsActivity mRecentsActivity;
|
||||
|
||||
private final StateManager.StateListener<RecentsState> mStateListener =
|
||||
new StateManager.StateListener<RecentsState>() {
|
||||
@@ -53,12 +46,8 @@ public class FallbackTaskbarUIController
|
||||
public void onStateTransitionStart(RecentsState toState) {
|
||||
animateToRecentsState(toState);
|
||||
|
||||
RecentsView recentsView = getRecentsView();
|
||||
if (recentsView == null) {
|
||||
return;
|
||||
}
|
||||
// Handle tapping on live tile.
|
||||
recentsView.setTaskLaunchListener(toState == RecentsState.DEFAULT
|
||||
getRecentsView().setTaskLaunchListener(toState == RecentsState.DEFAULT
|
||||
? (() -> animateToRecentsState(RecentsState.BACKGROUND_APP)) : null);
|
||||
}
|
||||
|
||||
@@ -74,41 +63,30 @@ public class FallbackTaskbarUIController
|
||||
}
|
||||
};
|
||||
|
||||
public FallbackTaskbarUIController(T recentsContainer) {
|
||||
mRecentsContainer = recentsContainer;
|
||||
public FallbackTaskbarUIController(RecentsActivity recentsActivity) {
|
||||
mRecentsActivity = recentsActivity;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init(TaskbarControllers taskbarControllers) {
|
||||
super.init(taskbarControllers);
|
||||
mRecentsContainer.setTaskbarUIController(this);
|
||||
mRecentsContainer.getStateManager().addStateListener(mStateListener);
|
||||
|
||||
mRecentsActivity.setTaskbarUIController(this);
|
||||
mRecentsActivity.getStateManager().addStateListener(mStateListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
RecentsView recentsView = getRecentsView();
|
||||
if (recentsView != null) {
|
||||
recentsView.setTaskLaunchListener(null);
|
||||
}
|
||||
mRecentsContainer.setTaskbarUIController(null);
|
||||
mRecentsContainer.getStateManager().removeStateListener(mStateListener);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Animator getParallelAnimationToGestureEndTarget(GestureState.GestureEndTarget endTarget,
|
||||
long duration, RecentsAnimationCallbacks callbacks) {
|
||||
return createAnimToRecentsState(
|
||||
FallbackActivityInterface.INSTANCE.stateFromGestureEndTarget(endTarget), duration);
|
||||
mRecentsActivity.setTaskbarUIController(null);
|
||||
mRecentsActivity.getStateManager().removeStateListener(mStateListener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an animation to animate the taskbar for the given state (but does not start it).
|
||||
* Currently this animation just force stashes the taskbar in Overview.
|
||||
*/
|
||||
private Animator createAnimToRecentsState(RecentsState toState, long duration) {
|
||||
public Animator createAnimToRecentsState(RecentsState toState, long duration) {
|
||||
// Force stash taskbar (disallow unstashing) when:
|
||||
// - in a 3P launcher or overview task.
|
||||
// - not running in a test harness (unstash is needed for tests)
|
||||
@@ -130,8 +108,8 @@ public class FallbackTaskbarUIController
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable RecentsView getRecentsView() {
|
||||
return mRecentsContainer.getOverviewPanel();
|
||||
public RecentsView getRecentsView() {
|
||||
return mRecentsActivity.getOverviewPanel();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -146,21 +124,18 @@ public class FallbackTaskbarUIController
|
||||
|
||||
private boolean isIn3pHomeOrRecents() {
|
||||
TopTaskTracker.CachedTaskInfo topTask = TopTaskTracker.INSTANCE
|
||||
.get(mControllers.taskbarActivityContext).getCachedTopTask(true,
|
||||
mRecentsContainer.asContext().getDisplayId());
|
||||
.get(mControllers.taskbarActivityContext).getCachedTopTask(true);
|
||||
return topTask.isHomeTask() || topTask.isRecentsTask();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected TISBindHelper getTISBindHelper() {
|
||||
return mRecentsActivity.getTISBindHelper();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getTaskbarUIControllerName() {
|
||||
return "FallbackTaskbarUIController<" + mRecentsContainer.getClass().getSimpleName() + ">";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dumpLogs(String prefix, PrintWriter pw) {
|
||||
super.dumpLogs(prefix, pw);
|
||||
|
||||
pw.println(String.format("%s\tRecentsState=%s", prefix,
|
||||
mRecentsContainer.getStateManager().getState()));
|
||||
return "FallbackTaskbarUIController";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,28 +15,20 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static com.android.launcher3.Flags.enableAltTabKqsOnConnectedDisplays;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.content.ComponentName;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.launcher3.Flags;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.statehandlers.DesktopVisibilityController;
|
||||
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
|
||||
import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
import com.android.quickstep.RecentsFilterState;
|
||||
import com.android.quickstep.LauncherActivityInterface;
|
||||
import com.android.quickstep.RecentsModel;
|
||||
import com.android.quickstep.util.DesktopTask;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
import com.android.quickstep.util.LayoutUtils;
|
||||
import com.android.quickstep.util.SingleTask;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
@@ -45,7 +37,6 @@ import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -53,14 +44,12 @@ import java.util.stream.Collectors;
|
||||
* Handles initialization of the {@link KeyboardQuickSwitchViewController}.
|
||||
*/
|
||||
public final class KeyboardQuickSwitchController implements
|
||||
TaskbarControllers.LoggableTaskbarController, TouchController {
|
||||
TaskbarControllers.LoggableTaskbarController {
|
||||
|
||||
@VisibleForTesting
|
||||
public static final int MAX_TASKS = 6;
|
||||
|
||||
@NonNull private final ControllerCallbacks mControllerCallbacks = new ControllerCallbacks();
|
||||
// Callback used to notify when the KQS view is closed.
|
||||
@Nullable private Runnable mOnClosed;
|
||||
|
||||
// Initialized on init
|
||||
@Nullable private RecentsModel mModel;
|
||||
@@ -70,21 +59,12 @@ public final class KeyboardQuickSwitchController implements
|
||||
private int mTaskListChangeId = -1;
|
||||
// Only empty before the recent tasks list has been loaded the first time
|
||||
@NonNull private List<GroupTask> mTasks = new ArrayList<>();
|
||||
// Set of task IDs filtered out of tasks in recents model to generate list of tasks to show in
|
||||
// the Keyboard Quick Switch view. Non empty only if the view has been shown in response to
|
||||
// toggling taskbar overflow button.
|
||||
@NonNull private Set<Integer> mExcludedTaskIds = Collections.emptySet();
|
||||
|
||||
private int mNumHiddenTasks = 0;
|
||||
|
||||
// Initialized in init
|
||||
private TaskbarControllers mControllers;
|
||||
|
||||
@Nullable private KeyboardQuickSwitchViewController mQuickSwitchViewController;
|
||||
@Nullable private TaskbarOverlayContext mOverlayContext;
|
||||
|
||||
private boolean mHasDesktopTask = false;
|
||||
private boolean mWasDesktopTaskFilteredOut = false;
|
||||
|
||||
/** Initialize the controller. */
|
||||
public void init(@NonNull TaskbarControllers controllers) {
|
||||
@@ -102,12 +82,10 @@ public final class KeyboardQuickSwitchController implements
|
||||
return;
|
||||
}
|
||||
int currentFocusedIndex = mQuickSwitchViewController.getCurrentFocusedIndex();
|
||||
boolean wasOpenedFromTaskbar = mQuickSwitchViewController.wasOpenedFromTaskbar();
|
||||
onDestroy();
|
||||
if (currentFocusedIndex != -1) {
|
||||
mControllers.taskbarActivityContext.getMainThreadHandler().post(
|
||||
() -> openQuickSwitchView(currentFocusedIndex, mExcludedTaskIds,
|
||||
wasOpenedFromTaskbar));
|
||||
() -> openQuickSwitchView(currentFocusedIndex));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -115,189 +93,76 @@ public final class KeyboardQuickSwitchController implements
|
||||
openQuickSwitchView(-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens or closes the view in response to taskbar action. The view shows a filtered list of
|
||||
* tasks.
|
||||
* @param taskIdsToExclude A list of tasks to exclude in the opened view.
|
||||
* @param onClosed A callback used to notify when the KQS view is closed.
|
||||
*/
|
||||
void toggleQuickSwitchViewForTaskbar(@NonNull Set<Integer> taskIdsToExclude,
|
||||
@NonNull Runnable onClosed) {
|
||||
mOnClosed = onClosed;
|
||||
|
||||
// Close the view if its shown, and was opened from the taskbar.
|
||||
if (mQuickSwitchViewController != null
|
||||
&& !mQuickSwitchViewController.isCloseAnimationRunning()
|
||||
&& mQuickSwitchViewController.wasOpenedFromTaskbar()) {
|
||||
closeQuickSwitchView(true);
|
||||
return;
|
||||
}
|
||||
|
||||
openQuickSwitchView(-1, taskIdsToExclude, true);
|
||||
}
|
||||
|
||||
private void openQuickSwitchView(int currentFocusedIndex) {
|
||||
openQuickSwitchView(currentFocusedIndex, Collections.emptySet(), false);
|
||||
}
|
||||
|
||||
private void openQuickSwitchView(int currentFocusedIndex,
|
||||
@NonNull Set<Integer> taskIdsToExclude,
|
||||
boolean wasOpenedFromTaskbar) {
|
||||
if (mQuickSwitchViewController != null) {
|
||||
if (!mQuickSwitchViewController.isCloseAnimationRunning()) {
|
||||
if (mQuickSwitchViewController.wasOpenedFromTaskbar() == wasOpenedFromTaskbar) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Relayout the KQS view instead of recreating a new one if it is the current
|
||||
// trigger surface is different than the previous one.
|
||||
final int currentFocusIndexOverride =
|
||||
currentFocusedIndex == -1 && !mControllerCallbacks.isFirstTaskRunning()
|
||||
? 0 : currentFocusedIndex;
|
||||
|
||||
// Skip the task reload if the list is not changed.
|
||||
if (!mModel.isTaskListValid(mTaskListChangeId) || !taskIdsToExclude.equals(
|
||||
mExcludedTaskIds)) {
|
||||
final boolean shouldShowDesktopTasks = mControllers.taskbarDesktopModeController
|
||||
.shouldShowDesktopTasksInTaskbar();
|
||||
mExcludedTaskIds = taskIdsToExclude;
|
||||
mTaskListChangeId = mModel.getTasks((tasks) -> {
|
||||
processLoadedTasks(tasks, taskIdsToExclude);
|
||||
mQuickSwitchViewController.updateQuickSwitchView(
|
||||
mTasks,
|
||||
wasOpenedFromTaskbar ? 0 : mNumHiddenTasks,
|
||||
currentFocusIndexOverride,
|
||||
mHasDesktopTask,
|
||||
mWasDesktopTaskFilteredOut);
|
||||
}, shouldShowDesktopTasks ? RecentsFilterState.EMPTY_FILTER
|
||||
: RecentsFilterState.getDesktopTaskFilter());
|
||||
}
|
||||
|
||||
mQuickSwitchViewController.updateLayoutForSurface(wasOpenedFromTaskbar,
|
||||
currentFocusIndexOverride);
|
||||
return;
|
||||
} else {
|
||||
// Allow the KQS to be reopened during the close animation to make it more
|
||||
// responsive.
|
||||
closeQuickSwitchView(false);
|
||||
}
|
||||
// Allow the KQS to be reopened during the close animation to make it more responsive
|
||||
closeQuickSwitchView(false);
|
||||
}
|
||||
|
||||
mOverlayContext = mControllers.taskbarOverlayController.requestWindow();
|
||||
if (Flags.taskbarOverflow()) {
|
||||
mOverlayContext.getDragLayer().addTouchController(this);
|
||||
}
|
||||
TaskbarOverlayContext overlayContext =
|
||||
mControllers.taskbarOverlayController.requestWindow();
|
||||
KeyboardQuickSwitchView keyboardQuickSwitchView =
|
||||
(KeyboardQuickSwitchView) mOverlayContext.getLayoutInflater()
|
||||
(KeyboardQuickSwitchView) overlayContext.getLayoutInflater()
|
||||
.inflate(
|
||||
R.layout.keyboard_quick_switch_view,
|
||||
mOverlayContext.getDragLayer(),
|
||||
overlayContext.getDragLayer(),
|
||||
/* attachToRoot= */ false);
|
||||
mQuickSwitchViewController = new KeyboardQuickSwitchViewController(
|
||||
mControllers, mOverlayContext, keyboardQuickSwitchView, mControllerCallbacks);
|
||||
mControllers, overlayContext, keyboardQuickSwitchView, mControllerCallbacks);
|
||||
|
||||
final boolean shouldShowDesktopTasks = mControllers.taskbarDesktopModeController
|
||||
.shouldShowDesktopTasksInTaskbar();
|
||||
DesktopVisibilityController desktopController =
|
||||
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
|
||||
final boolean onDesktop =
|
||||
desktopController != null && desktopController.areDesktopTasksVisible();
|
||||
|
||||
if (mModel.isTaskListValid(mTaskListChangeId)
|
||||
&& taskIdsToExclude.equals(mExcludedTaskIds)) {
|
||||
if (mModel.isTaskListValid(mTaskListChangeId)) {
|
||||
// When we are opening the KQS with no focus override, check if the first task is
|
||||
// running. If not, focus that first task.
|
||||
mQuickSwitchViewController.openQuickSwitchView(
|
||||
mTasks,
|
||||
wasOpenedFromTaskbar ? 0 : mNumHiddenTasks,
|
||||
mNumHiddenTasks,
|
||||
/* updateTasks= */ false,
|
||||
currentFocusedIndex == -1 && !mControllerCallbacks.isFirstTaskRunning()
|
||||
? 0 : currentFocusedIndex,
|
||||
shouldShowDesktopTasks,
|
||||
mHasDesktopTask,
|
||||
mWasDesktopTaskFilteredOut,
|
||||
wasOpenedFromTaskbar);
|
||||
onDesktop);
|
||||
return;
|
||||
}
|
||||
|
||||
mExcludedTaskIds = taskIdsToExclude;
|
||||
mTaskListChangeId = mModel.getTasks((tasks) -> {
|
||||
processLoadedTasks(tasks, taskIdsToExclude);
|
||||
if (onDesktop) {
|
||||
processLoadedTasksOnDesktop(tasks);
|
||||
} else {
|
||||
processLoadedTasks(tasks);
|
||||
}
|
||||
// Check if the first task is running after the recents model has updated so that we use
|
||||
// the correct index.
|
||||
mQuickSwitchViewController.openQuickSwitchView(
|
||||
mTasks,
|
||||
wasOpenedFromTaskbar ? 0 : mNumHiddenTasks,
|
||||
mNumHiddenTasks,
|
||||
/* updateTasks= */ true,
|
||||
currentFocusedIndex == -1 && !mControllerCallbacks.isFirstTaskRunning()
|
||||
? 0 : currentFocusedIndex,
|
||||
shouldShowDesktopTasks,
|
||||
mHasDesktopTask,
|
||||
mWasDesktopTaskFilteredOut,
|
||||
wasOpenedFromTaskbar);
|
||||
}, shouldShowDesktopTasks ? RecentsFilterState.EMPTY_FILTER
|
||||
: RecentsFilterState.getDesktopTaskFilter());
|
||||
onDesktop);
|
||||
});
|
||||
}
|
||||
|
||||
private boolean shouldExcludeTask(GroupTask task, Set<Integer> taskIdsToExclude) {
|
||||
return Flags.taskbarOverflow() && task.getTasks().stream().anyMatch(
|
||||
t -> taskIdsToExclude.contains(t.key.id));
|
||||
}
|
||||
|
||||
private void processLoadedTasks(List<GroupTask> tasks, Set<Integer> taskIdsToExclude) {
|
||||
mHasDesktopTask = false;
|
||||
mWasDesktopTaskFilteredOut = false;
|
||||
if (mControllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()) {
|
||||
processLoadedTasksOnDesktop(tasks, taskIdsToExclude);
|
||||
} else {
|
||||
processLoadedTasksOutsideDesktop(tasks, taskIdsToExclude);
|
||||
}
|
||||
}
|
||||
|
||||
private void processLoadedTasksOutsideDesktop(List<GroupTask> tasks,
|
||||
Set<Integer> taskIdsToExclude) {
|
||||
private void processLoadedTasks(List<GroupTask> tasks) {
|
||||
// Only store MAX_TASK tasks, from most to least recent
|
||||
Collections.reverse(tasks);
|
||||
mTasks = tasks.stream()
|
||||
.filter(task -> !(task instanceof DesktopTask)
|
||||
&& !shouldExcludeTask(task, taskIdsToExclude))
|
||||
.limit(MAX_TASKS)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
for (int i = 0; i < tasks.size(); i++) {
|
||||
if (tasks.get(i) instanceof DesktopTask) {
|
||||
mHasDesktopTask = true;
|
||||
if (i < mTasks.size()) {
|
||||
mWasDesktopTaskFilteredOut = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
mNumHiddenTasks = Math.max(0,
|
||||
tasks.size() - (mWasDesktopTaskFilteredOut ? 1 : 0) - MAX_TASKS);
|
||||
mNumHiddenTasks = Math.max(0, tasks.size() - MAX_TASKS);
|
||||
}
|
||||
|
||||
private void processLoadedTasksOnDesktop(List<GroupTask> tasks, Set<Integer> taskIdsToExclude) {
|
||||
// Find all desktop tasks.
|
||||
List<DesktopTask> desktopTasks = tasks.stream()
|
||||
.filter(t -> t instanceof DesktopTask)
|
||||
.map(t -> (DesktopTask) t)
|
||||
.toList();
|
||||
private void processLoadedTasksOnDesktop(List<GroupTask> tasks) {
|
||||
// Find the single desktop task that contains a grouping of desktop tasks
|
||||
DesktopTask desktopTask = findDesktopTask(tasks);
|
||||
|
||||
// Apps on the connected displays seem to be in different Desktop tasks even with the
|
||||
// multiple desktops flag disabled. So, until multiple desktops is implemented the following
|
||||
// should help with team-fooding Alt+tab on connected displays. Post multiple desktop,
|
||||
// further changes maybe required to support launching selected desktops.
|
||||
if (enableAltTabKqsOnConnectedDisplays()) {
|
||||
mTasks = desktopTasks.stream()
|
||||
.flatMap(t -> t.getTasks().stream())
|
||||
.map(SingleTask::new)
|
||||
.filter(task -> !shouldExcludeTask(task, taskIdsToExclude))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
mNumHiddenTasks = Math.max(0, tasks.size() - desktopTasks.size());
|
||||
} else if (!desktopTasks.isEmpty()) {
|
||||
mTasks = desktopTasks.get(0).getTasks().stream()
|
||||
.map(SingleTask::new)
|
||||
.filter(task -> !shouldExcludeTask(task, taskIdsToExclude))
|
||||
.collect(Collectors.toList());
|
||||
if (desktopTask != null) {
|
||||
mTasks = desktopTask.tasks.stream().map(GroupTask::new).collect(Collectors.toList());
|
||||
// All other tasks, apart from the grouped desktop task, are hidden
|
||||
mNumHiddenTasks = Math.max(0, tasks.size() - 1);
|
||||
} else {
|
||||
@@ -307,6 +172,14 @@ public final class KeyboardQuickSwitchController implements
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private DesktopTask findDesktopTask(List<GroupTask> tasks) {
|
||||
return (DesktopTask) tasks.stream()
|
||||
.filter(t -> t instanceof DesktopTask)
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
void closeQuickSwitchView() {
|
||||
closeQuickSwitchView(true);
|
||||
}
|
||||
@@ -328,54 +201,12 @@ public final class KeyboardQuickSwitchController implements
|
||||
? -1 : mQuickSwitchViewController.launchFocusedTask();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onControllerTouchEvent(MotionEvent ev) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
|
||||
if (mQuickSwitchViewController == null
|
||||
|| mOverlayContext == null
|
||||
|| !Flags.taskbarOverflow()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TaskbarOverlayDragLayer dragLayer = mOverlayContext.getDragLayer();
|
||||
if (ev.getAction() == MotionEvent.ACTION_DOWN
|
||||
&& !mQuickSwitchViewController.isEventOverKeyboardQuickSwitch(dragLayer, ev)) {
|
||||
closeQuickSwitchView(true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void onDestroy() {
|
||||
if (mQuickSwitchViewController != null) {
|
||||
mQuickSwitchViewController.onDestroy();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean isShownFromTaskbar() {
|
||||
return isShown() && mQuickSwitchViewController.wasOpenedFromTaskbar();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
boolean isShown() {
|
||||
return mQuickSwitchViewController != null
|
||||
&& !mQuickSwitchViewController.isCloseAnimationRunning();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
List<Integer> shownTaskIds() {
|
||||
if (!isShown()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
return mTasks.stream().flatMap(
|
||||
groupTask -> groupTask.getTasks().stream().map(task -> task.key.id)).toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dumpLogs(String prefix, PrintWriter pw) {
|
||||
pw.println(prefix + "KeyboardQuickSwitchController:");
|
||||
@@ -383,16 +214,17 @@ public final class KeyboardQuickSwitchController implements
|
||||
pw.println(prefix + "\tisOpen=" + (mQuickSwitchViewController != null));
|
||||
pw.println(prefix + "\tmNumHiddenTasks=" + mNumHiddenTasks);
|
||||
pw.println(prefix + "\tmTaskListChangeId=" + mTaskListChangeId);
|
||||
pw.println(prefix + "\tmHasDesktopTask=" + mHasDesktopTask);
|
||||
pw.println(prefix + "\tmWasDesktopTaskFilteredOut=" + mWasDesktopTaskFilteredOut);
|
||||
pw.println(prefix + "\tmTasks=[");
|
||||
for (GroupTask task : mTasks) {
|
||||
int count = 0;
|
||||
for (Task t : task.getTasks()) {
|
||||
ComponentName cn = t.getTopComponent();
|
||||
pw.println(prefix + "\t\tt" + (++count) + ": (id=" + t.key.id
|
||||
+ "; package=" + (cn != null ? cn.getPackageName() + ")" : "no package)"));
|
||||
}
|
||||
Task task1 = task.task1;
|
||||
Task task2 = task.task2;
|
||||
ComponentName cn1 = task1.getTopComponent();
|
||||
ComponentName cn2 = task2 != null ? task2.getTopComponent() : null;
|
||||
pw.println(prefix + "\t\tt1: (id=" + task1.key.id
|
||||
+ "; package=" + (cn1 != null ? cn1.getPackageName() + ")" : "no package)")
|
||||
+ " t2: (id=" + (task2 != null ? task2.key.id : "-1")
|
||||
+ "; package=" + (cn2 != null ? cn2.getPackageName() + ")"
|
||||
: "no package)"));
|
||||
}
|
||||
pw.println(prefix + "\t]");
|
||||
|
||||
@@ -403,41 +235,24 @@ public final class KeyboardQuickSwitchController implements
|
||||
|
||||
class ControllerCallbacks {
|
||||
|
||||
int getTaskCount() {
|
||||
return mTasks.size() + (mNumHiddenTasks == 0 ? 0 : 1);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
GroupTask getTaskAt(int index) {
|
||||
return index < 0 || index >= mTasks.size() ? null : mTasks.get(index);
|
||||
}
|
||||
|
||||
void updateThumbnailInBackground(Task task, Consumer<ThumbnailData> callback) {
|
||||
mModel.getThumbnailCache().getThumbnailInBackground(task,
|
||||
thumbnailData -> {
|
||||
task.thumbnail = thumbnailData;
|
||||
callback.accept(thumbnailData);
|
||||
});
|
||||
mModel.getThumbnailCache().updateThumbnailInBackground(task, callback);
|
||||
}
|
||||
|
||||
void updateIconInBackground(Task task, Consumer<Task> callback) {
|
||||
mModel.getIconCache().getIconInBackground(task, (icon, contentDescription, title) -> {
|
||||
task.icon = icon;
|
||||
task.titleDescription = contentDescription;
|
||||
task.title = title;
|
||||
callback.accept(task);
|
||||
});
|
||||
}
|
||||
|
||||
void onCloseStarted() {
|
||||
if (mOnClosed != null) {
|
||||
mOnClosed.run();
|
||||
mOnClosed = null;
|
||||
}
|
||||
mModel.getIconCache().updateIconInBackground(task, callback);
|
||||
}
|
||||
|
||||
void onCloseComplete() {
|
||||
if (Flags.taskbarOverflow() && mOverlayContext != null) {
|
||||
mOverlayContext.getDragLayer()
|
||||
.removeTouchController(KeyboardQuickSwitchController.this);
|
||||
}
|
||||
mOverlayContext = null;
|
||||
mQuickSwitchViewController = null;
|
||||
}
|
||||
|
||||
@@ -445,23 +260,15 @@ public final class KeyboardQuickSwitchController implements
|
||||
if (task == null) {
|
||||
return false;
|
||||
}
|
||||
ActivityManager.RunningTaskInfo runningTaskInfo =
|
||||
ActivityManagerWrapper.getInstance().getRunningTask();
|
||||
if (runningTaskInfo == null) {
|
||||
return false;
|
||||
}
|
||||
int runningTaskId = ActivityManagerWrapper.getInstance().getRunningTask().taskId;
|
||||
Task task2 = task.task2;
|
||||
|
||||
int runningTaskId = runningTaskInfo.taskId;
|
||||
return task.containsTask(runningTaskId);
|
||||
return runningTaskId == task.task1.key.id
|
||||
|| (task2 != null && runningTaskId == task2.key.id);
|
||||
}
|
||||
|
||||
boolean isFirstTaskRunning() {
|
||||
return isTaskRunning(getTaskAt(0));
|
||||
}
|
||||
|
||||
boolean isAspectRatioSquare() {
|
||||
return mControllers != null && LayoutUtils.isAspectRatioSquare(
|
||||
mControllers.taskbarActivityContext.getDeviceProfile().aspectRatio);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,40 +36,37 @@ import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.util.Preconditions;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.SplitBounds;
|
||||
import com.android.quickstep.util.BorderAnimator;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
import com.android.wm.shell.shared.TypefaceUtils;
|
||||
import com.android.wm.shell.shared.TypefaceUtils.FontFamily;
|
||||
|
||||
import kotlin.Unit;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import kotlin.Unit;
|
||||
|
||||
/**
|
||||
* A view that displays a recent task during a keyboard quick switch.
|
||||
*/
|
||||
public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
|
||||
|
||||
private static final float THUMBNAIL_BLUR_RADIUS = 1f;
|
||||
private static final int INVALID_BORDER_RADIUS = -1;
|
||||
|
||||
@ColorInt private final int mBorderColor;
|
||||
@ColorInt private final int mBorderRadius;
|
||||
@ColorInt
|
||||
private final int mBorderColor;
|
||||
|
||||
@Nullable private BorderAnimator mBorderAnimator;
|
||||
@Nullable
|
||||
private BorderAnimator mBorderAnimator;
|
||||
|
||||
@Nullable private ImageView mThumbnailView1;
|
||||
@Nullable private ImageView mThumbnailView2;
|
||||
@Nullable private ImageView mIcon1;
|
||||
@Nullable private ImageView mIcon2;
|
||||
@Nullable private View mContent;
|
||||
|
||||
// Describe the task position in the parent container. Used to add information about the task's
|
||||
// position in a task list to the task view's content description.
|
||||
private int mIndexInParent = -1;
|
||||
private int mTotalTasksInParent = -1;
|
||||
@Nullable
|
||||
private ImageView mThumbnailView1;
|
||||
@Nullable
|
||||
private ImageView mThumbnailView2;
|
||||
@Nullable
|
||||
private ImageView mIcon1;
|
||||
@Nullable
|
||||
private ImageView mIcon2;
|
||||
@Nullable
|
||||
private View mContent;
|
||||
|
||||
public KeyboardQuickSwitchTaskView(@NonNull Context context) {
|
||||
this(context, null);
|
||||
@@ -97,8 +94,6 @@ public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
|
||||
|
||||
mBorderColor = ta.getColor(
|
||||
R.styleable.TaskView_focusBorderColor, DEFAULT_BORDER_COLOR);
|
||||
mBorderRadius = ta.getDimensionPixelSize(
|
||||
R.styleable.TaskView_focusBorderRadius, INVALID_BORDER_RADIUS);
|
||||
ta.recycle();
|
||||
}
|
||||
|
||||
@@ -111,27 +106,14 @@ public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
|
||||
mIcon2 = findViewById(R.id.icon_2);
|
||||
mContent = findViewById(R.id.content);
|
||||
|
||||
Preconditions.assertNotNull(mContent);
|
||||
|
||||
TypefaceUtils.setTypeface(
|
||||
mContent.findViewById(R.id.large_text),
|
||||
FontFamily.GSF_HEADLINE_LARGE_EMPHASIZED
|
||||
);
|
||||
TypefaceUtils.setTypeface(
|
||||
mContent.findViewById(R.id.small_text),
|
||||
FontFamily.GSF_LABEL_LARGE
|
||||
);
|
||||
|
||||
Resources resources = mContext.getResources();
|
||||
|
||||
Preconditions.assertNotNull(mContent);
|
||||
mBorderAnimator = BorderAnimator.createScalingBorderAnimator(
|
||||
/* borderRadiusPx= */ mBorderRadius != INVALID_BORDER_RADIUS
|
||||
? mBorderRadius
|
||||
: resources.getDimensionPixelSize(
|
||||
R.dimen.keyboard_quick_switch_task_view_radius),
|
||||
/* borderRadiusPx= */ resources.getDimensionPixelSize(
|
||||
R.dimen.keyboard_quick_switch_task_view_radius),
|
||||
/* borderWidthPx= */ resources.getDimensionPixelSize(
|
||||
R.dimen.keyboard_quick_switch_border_width),
|
||||
/* borderStrokePx= */ resources.getDimensionPixelSize(
|
||||
R.dimen.keyboard_quick_switch_border_stroke),
|
||||
/* boundsBuilder= */ bounds -> {
|
||||
bounds.set(0, 0, getWidth(), getHeight());
|
||||
return Unit.INSTANCE;
|
||||
@@ -162,105 +144,36 @@ public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
|
||||
applyThumbnail(mThumbnailView1, task1, thumbnailUpdateFunction);
|
||||
applyThumbnail(mThumbnailView2, task2, thumbnailUpdateFunction);
|
||||
|
||||
// Update content description, even in cases task icons, and content descriptions need to be
|
||||
// loaded asynchronously to ensure that the task has non empty description (assuming task
|
||||
// position information was set), as KeyboardQuickSwitch view may request accessibility
|
||||
// focus to be moved to the task when the quick switch UI gets shown. The description will
|
||||
// be updated once the task metadata has been loaded - the delay should be very short, and
|
||||
// the content description when task titles are not available still gives some useful
|
||||
// information to the user (the task's position in the list).
|
||||
updateContentDesctiptionForTasks(task1, task2);
|
||||
|
||||
if (iconUpdateFunction == null) {
|
||||
applyIcon(mIcon1, task1);
|
||||
applyIcon(mIcon2, task2);
|
||||
setContentDescription(task2 == null
|
||||
? task1.titleDescription
|
||||
: getContext().getString(
|
||||
R.string.quick_switch_split_task,
|
||||
task1.titleDescription,
|
||||
task2.titleDescription));
|
||||
return;
|
||||
}
|
||||
|
||||
iconUpdateFunction.updateIconInBackground(task1, t -> {
|
||||
applyIcon(mIcon1, task1);
|
||||
if (task2 != null) {
|
||||
return;
|
||||
}
|
||||
updateContentDesctiptionForTasks(task1, null);
|
||||
setContentDescription(task1.titleDescription);
|
||||
});
|
||||
|
||||
if (task2 == null) {
|
||||
return;
|
||||
}
|
||||
iconUpdateFunction.updateIconInBackground(task2, t -> {
|
||||
applyIcon(mIcon2, task2);
|
||||
updateContentDesctiptionForTasks(task1, task2);
|
||||
setContentDescription(getContext().getString(
|
||||
R.string.quick_switch_split_task,
|
||||
task1.titleDescription,
|
||||
task2.titleDescription));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes information about the task's position within the parent container context - used
|
||||
* to add position information to the view's content description.
|
||||
* Should be called before associating the view with tasks.
|
||||
*
|
||||
* @param index The view's 0-based index within the parent task container.
|
||||
* @param totalTasks The total number of tasks in the parent task container.
|
||||
*/
|
||||
protected void setPositionInformation(int index, int totalTasks) {
|
||||
mIndexInParent = index;
|
||||
mTotalTasksInParent = totalTasks;
|
||||
}
|
||||
|
||||
protected void setThumbnailsForSplitTasks(
|
||||
@NonNull Task task1,
|
||||
@Nullable Task task2,
|
||||
@Nullable ThumbnailUpdateFunction thumbnailUpdateFunction,
|
||||
@Nullable IconUpdateFunction iconUpdateFunction,
|
||||
@Nullable SplitBounds splitBounds) {
|
||||
setThumbnails(task1, task2, thumbnailUpdateFunction, iconUpdateFunction);
|
||||
|
||||
if (splitBounds == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
final boolean isLeftRightSplit = !splitBounds.appsStackedVertically;
|
||||
final float leftOrTopTaskPercent = splitBounds.getLeftTopTaskPercent();
|
||||
|
||||
ConstraintLayout.LayoutParams leftTopParams = (ConstraintLayout.LayoutParams)
|
||||
mThumbnailView1.getLayoutParams();
|
||||
ConstraintLayout.LayoutParams rightBottomParams = (ConstraintLayout.LayoutParams)
|
||||
mThumbnailView2.getLayoutParams();
|
||||
|
||||
if (isLeftRightSplit) {
|
||||
// Set thumbnail view ratio in left right split mode.
|
||||
leftTopParams.width = 0; // Set width to 0dp, so it uses the constraint dimension ratio.
|
||||
leftTopParams.height = ConstraintLayout.LayoutParams.MATCH_PARENT;
|
||||
leftTopParams.matchConstraintPercentWidth = leftOrTopTaskPercent;
|
||||
leftTopParams.leftToLeft = ConstraintLayout.LayoutParams.PARENT_ID;
|
||||
leftTopParams.rightToLeft = R.id.thumbnail_2;
|
||||
mThumbnailView1.setLayoutParams(leftTopParams);
|
||||
|
||||
rightBottomParams.width = 0;
|
||||
rightBottomParams.height = ConstraintLayout.LayoutParams.MATCH_PARENT;
|
||||
rightBottomParams.matchConstraintPercentWidth = 1 - leftOrTopTaskPercent;
|
||||
rightBottomParams.leftToRight = R.id.thumbnail_1;
|
||||
rightBottomParams.rightToRight = ConstraintLayout.LayoutParams.PARENT_ID;
|
||||
mThumbnailView2.setLayoutParams(rightBottomParams);
|
||||
} else {
|
||||
// Set thumbnail view ratio in top bottom split mode.
|
||||
leftTopParams.height = 0;
|
||||
leftTopParams.width = ConstraintLayout.LayoutParams.MATCH_PARENT;
|
||||
leftTopParams.matchConstraintPercentHeight = leftOrTopTaskPercent;
|
||||
leftTopParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID;
|
||||
leftTopParams.bottomToTop = R.id.thumbnail_2;
|
||||
mThumbnailView1.setLayoutParams(leftTopParams);
|
||||
|
||||
rightBottomParams.height = 0;
|
||||
rightBottomParams.width = ConstraintLayout.LayoutParams.MATCH_PARENT;
|
||||
rightBottomParams.matchConstraintPercentHeight = 1 - leftOrTopTaskPercent;
|
||||
rightBottomParams.topToBottom = R.id.thumbnail_1;
|
||||
rightBottomParams.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID;
|
||||
mThumbnailView2.setLayoutParams(rightBottomParams);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyThumbnail(
|
||||
@Nullable ImageView thumbnailView,
|
||||
@Nullable Task task,
|
||||
@@ -272,8 +185,8 @@ public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
|
||||
applyThumbnail(thumbnailView, task.colorBackground, task.thumbnail);
|
||||
return;
|
||||
}
|
||||
updateFunction.updateThumbnailInBackground(task, thumbnailData ->
|
||||
applyThumbnail(thumbnailView, task.colorBackground, thumbnailData));
|
||||
updateFunction.updateThumbnailInBackground(task,
|
||||
thumbnailData -> applyThumbnail(thumbnailView, task.colorBackground, thumbnailData));
|
||||
}
|
||||
|
||||
private void applyThumbnail(
|
||||
@@ -305,28 +218,6 @@ public class KeyboardQuickSwitchTaskView extends ConstraintLayout {
|
||||
constantState.newDrawable(getResources(), getContext().getTheme()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the task view's content description to reflect tasks represented by the view.
|
||||
*/
|
||||
private void updateContentDesctiptionForTasks(@NonNull Task task1, @Nullable Task task2) {
|
||||
String tasksDescription = task1.titleDescription == null || task2 == null
|
||||
? task1.titleDescription
|
||||
: getContext().getString(
|
||||
R.string.quick_switch_split_task,
|
||||
task1.titleDescription,
|
||||
task2.titleDescription);
|
||||
if (mIndexInParent < 0) {
|
||||
setContentDescription(tasksDescription);
|
||||
return;
|
||||
}
|
||||
|
||||
setContentDescription(
|
||||
getContext().getString(R.string.quick_switch_task_with_position_in_parent,
|
||||
tasksDescription != null ? tasksDescription : "",
|
||||
mIndexInParent + 1,
|
||||
mTotalTasksInParent));
|
||||
}
|
||||
|
||||
protected interface ThumbnailUpdateFunction {
|
||||
|
||||
void updateThumbnailInBackground(Task task, Consumer<ThumbnailData> callback);
|
||||
|
||||
@@ -17,6 +17,8 @@ package com.android.launcher3.taskbar;
|
||||
|
||||
import static androidx.constraintlayout.widget.ConstraintLayout.LayoutParams.PARENT_ID;
|
||||
|
||||
import static com.android.launcher3.taskbar.KeyboardQuickSwitchController.MAX_TASKS;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
@@ -30,57 +32,44 @@ import android.util.AttributeSet;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.widget.HorizontalScrollView;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.window.OnBackInvokedDispatcher;
|
||||
import android.window.WindowOnBackInvokedDispatcher;
|
||||
|
||||
import androidx.annotation.LayoutRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
import androidx.core.content.res.ResourcesCompat;
|
||||
|
||||
import com.android.app.animation.Interpolators;
|
||||
import com.android.internal.jank.Cuj;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.testing.TestLogging;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
import com.android.quickstep.util.DesktopTask;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
import com.android.quickstep.util.SingleTask;
|
||||
import com.android.quickstep.util.SplitTask;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
|
||||
import com.android.wm.shell.shared.TypefaceUtils;
|
||||
import com.android.wm.shell.shared.TypefaceUtils.FontFamily;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
/**
|
||||
* View that allows quick switching between recent tasks.
|
||||
*
|
||||
* Can be access via:
|
||||
* - keyboard alt-tab
|
||||
* - alt-shift-tab
|
||||
* - taskbar overflow button
|
||||
* View that allows quick switching between recent tasks through keyboard
|
||||
* alt-tab and alt-shift-tab
|
||||
* commands.
|
||||
*/
|
||||
public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
|
||||
private static final long OUTLINE_ANIMATION_DURATION_MS = 333;
|
||||
private static final float OUTLINE_START_HEIGHT_FACTOR = 0.45f;
|
||||
private static final float OUTLINE_START_RADIUS_FACTOR = 0.25f;
|
||||
private static final Interpolator OPEN_OUTLINE_INTERPOLATOR =
|
||||
Interpolators.EMPHASIZED_DECELERATE;
|
||||
private static final Interpolator CLOSE_OUTLINE_INTERPOLATOR =
|
||||
Interpolators.EMPHASIZED_ACCELERATE;
|
||||
private static final Interpolator OPEN_OUTLINE_INTERPOLATOR = Interpolators.EMPHASIZED_DECELERATE;
|
||||
private static final Interpolator CLOSE_OUTLINE_INTERPOLATOR = Interpolators.EMPHASIZED_ACCELERATE;
|
||||
|
||||
private static final long ALPHA_ANIMATION_DURATION_MS = 83;
|
||||
private static final long ALPHA_ANIMATION_START_DELAY_MS = 67;
|
||||
@@ -90,10 +79,8 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
private static final float CONTENT_START_TRANSLATION_X_DP = 32;
|
||||
private static final float CONTENT_START_TRANSLATION_Y_DP = 40;
|
||||
private static final Interpolator OPEN_TRANSLATION_X_INTERPOLATOR = Interpolators.EMPHASIZED;
|
||||
private static final Interpolator OPEN_TRANSLATION_Y_INTERPOLATOR =
|
||||
Interpolators.EMPHASIZED_DECELERATE;
|
||||
private static final Interpolator CLOSE_TRANSLATION_Y_INTERPOLATOR =
|
||||
Interpolators.EMPHASIZED_ACCELERATE;
|
||||
private static final Interpolator OPEN_TRANSLATION_Y_INTERPOLATOR = Interpolators.EMPHASIZED_DECELERATE;
|
||||
private static final Interpolator CLOSE_TRANSLATION_Y_INTERPOLATOR = Interpolators.EMPHASIZED_ACCELERATE;
|
||||
|
||||
private static final long CONTENT_ALPHA_ANIMATION_DURATION_MS = 83;
|
||||
private static final long CONTENT_ALPHA_ANIMATION_START_DELAY_MS = 83;
|
||||
@@ -106,25 +93,15 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
private HorizontalScrollView mScrollView;
|
||||
private ConstraintLayout mContent;
|
||||
|
||||
private boolean mSupportsScrollArrows = false;
|
||||
private ImageButton mStartScrollArrow;
|
||||
private ImageButton mEndScrollArrow;
|
||||
|
||||
private int mTaskViewBorderWidth;
|
||||
private int mTaskViewRadius;
|
||||
private int mTaskViewWidth;
|
||||
private int mTaskViewHeight;
|
||||
private int mSpacing;
|
||||
private int mSmallSpacing;
|
||||
private int mOutlineRadius;
|
||||
private boolean mIsRtl;
|
||||
|
||||
private int mOverviewTaskIndex = -1;
|
||||
private int mDesktopTaskIndex = -1;
|
||||
|
||||
@Nullable
|
||||
private AnimatorSet mOpenAnimation;
|
||||
|
||||
private boolean mIsBackCallbackRegistered = false;
|
||||
|
||||
@Nullable
|
||||
private KeyboardQuickSwitchViewController.ViewCallbacks mViewCallbacks;
|
||||
|
||||
@@ -147,75 +124,26 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
super(context, attrs, defStyleAttr, defStyleRes);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
if (mViewCallbacks != null) {
|
||||
mViewCallbacks.onViewDetchedFromWindow();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onFinishInflate() {
|
||||
super.onFinishInflate();
|
||||
mNoRecentItemsPane = findViewById(R.id.no_recent_items_pane);
|
||||
mScrollView = findViewById(R.id.scroll_view);
|
||||
mContent = findViewById(R.id.content);
|
||||
mStartScrollArrow = findViewById(R.id.scroll_button_start);
|
||||
mEndScrollArrow = findViewById(R.id.scroll_button_end);
|
||||
|
||||
setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
|
||||
|
||||
Resources resources = getResources();
|
||||
mTaskViewWidth = resources.getDimensionPixelSize(
|
||||
R.dimen.keyboard_quick_switch_taskview_width);
|
||||
mTaskViewHeight = resources.getDimensionPixelSize(
|
||||
R.dimen.keyboard_quick_switch_taskview_height);
|
||||
mSpacing = resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_view_spacing);
|
||||
mSmallSpacing = resources.getDimensionPixelSize(
|
||||
R.dimen.keyboard_quick_switch_view_small_spacing);
|
||||
mOutlineRadius = resources.getDimensionPixelSize(R.dimen.keyboard_quick_switch_view_radius);
|
||||
mTaskViewBorderWidth = resources.getDimensionPixelSize(
|
||||
R.dimen.keyboard_quick_switch_border_width);
|
||||
mTaskViewRadius = resources.getDimensionPixelSize(
|
||||
R.dimen.keyboard_quick_switch_task_view_radius);
|
||||
|
||||
mIsRtl = Utilities.isRtl(resources);
|
||||
|
||||
TypefaceUtils.setTypeface(
|
||||
mNoRecentItemsPane.findViewById(R.id.no_recent_items_text),
|
||||
FontFamily.GSF_LABEL_LARGE);
|
||||
}
|
||||
|
||||
private void registerOnBackInvokedCallback() {
|
||||
OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
|
||||
|
||||
if (isOnBackInvokedCallbackEnabled(dispatcher)
|
||||
&& !mIsBackCallbackRegistered) {
|
||||
dispatcher.registerOnBackInvokedCallback(
|
||||
OnBackInvokedDispatcher.PRIORITY_OVERLAY, mViewCallbacks.onBackInvokedCallback);
|
||||
mIsBackCallbackRegistered = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void unregisterOnBackInvokedCallback() {
|
||||
OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
|
||||
|
||||
if (isOnBackInvokedCallbackEnabled(dispatcher)
|
||||
&& mIsBackCallbackRegistered) {
|
||||
dispatcher.unregisterOnBackInvokedCallback(
|
||||
mViewCallbacks.onBackInvokedCallback);
|
||||
mIsBackCallbackRegistered = false;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isOnBackInvokedCallbackEnabled(OnBackInvokedDispatcher dispatcher) {
|
||||
return dispatcher instanceof WindowOnBackInvokedDispatcher
|
||||
&& ((WindowOnBackInvokedDispatcher) dispatcher).isOnBackInvokedCallbackEnabled()
|
||||
&& mViewCallbacks != null;
|
||||
}
|
||||
|
||||
private KeyboardQuickSwitchTaskView createAndAddTaskView(
|
||||
int index,
|
||||
boolean isFinalView,
|
||||
boolean useSmallStartSpacing,
|
||||
@LayoutRes int resId,
|
||||
@NonNull LayoutInflater layoutInflater,
|
||||
@Nullable View previousView) {
|
||||
@@ -224,7 +152,7 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
taskView.setId(View.generateViewId());
|
||||
taskView.setOnClickListener(v -> mViewCallbacks.launchTaskAt(index));
|
||||
|
||||
LayoutParams lp = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
|
||||
LayoutParams lp = new LayoutParams(mTaskViewWidth, mTaskViewHeight);
|
||||
// Create a left-to-right ordering of views (or right-to-left in RTL locales)
|
||||
if (previousView != null) {
|
||||
lp.startToEnd = previousView.getId();
|
||||
@@ -234,9 +162,10 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
lp.topToTop = PARENT_ID;
|
||||
lp.bottomToBottom = PARENT_ID;
|
||||
// Add spacing between views
|
||||
lp.setMarginStart(useSmallStartSpacing ? mSmallSpacing : mSpacing);
|
||||
lp.setMarginStart(mSpacing);
|
||||
if (isFinalView) {
|
||||
// Add spacing to the end of the final view so that scrolling ends with some padding.
|
||||
// Add spacing to the end of the final view so that scrolling ends with some
|
||||
// padding.
|
||||
lp.endToEnd = PARENT_ID;
|
||||
lp.setMarginEnd(mSpacing);
|
||||
lp.horizontalBias = 1f;
|
||||
@@ -253,93 +182,67 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
int numHiddenTasks,
|
||||
boolean updateTasks,
|
||||
int currentFocusIndexOverride,
|
||||
@NonNull KeyboardQuickSwitchViewController.ViewCallbacks viewCallbacks,
|
||||
boolean useDesktopTaskView) {
|
||||
mContent.removeAllViews();
|
||||
|
||||
@NonNull KeyboardQuickSwitchViewController.ViewCallbacks viewCallbacks) {
|
||||
mViewCallbacks = viewCallbacks;
|
||||
Resources resources = context.getResources();
|
||||
Resources.Theme theme = context.getTheme();
|
||||
|
||||
View previousTaskView = null;
|
||||
LayoutInflater layoutInflater = LayoutInflater.from(context);
|
||||
int tasksToDisplay = groupTasks.size();
|
||||
int tasksToDisplay = Math.min(MAX_TASKS, groupTasks.size());
|
||||
for (int i = 0; i < tasksToDisplay; i++) {
|
||||
GroupTask groupTask = groupTasks.get(i);
|
||||
KeyboardQuickSwitchTaskView currentTaskView = createAndAddTaskView(
|
||||
i,
|
||||
/* isFinalView= */ i == tasksToDisplay - 1
|
||||
&& numHiddenTasks == 0 && !useDesktopTaskView,
|
||||
/* useSmallStartSpacing= */ false,
|
||||
mViewCallbacks.isAspectRatioSquare()
|
||||
? R.layout.keyboard_quick_switch_taskview_square
|
||||
/* isFinalView= */ i == tasksToDisplay - 1 && numHiddenTasks == 0,
|
||||
groupTask instanceof DesktopTask
|
||||
? R.layout.keyboard_quick_switch_textonly_taskview
|
||||
: R.layout.keyboard_quick_switch_taskview,
|
||||
layoutInflater,
|
||||
previousTaskView);
|
||||
|
||||
Task task1;
|
||||
Task task2;
|
||||
if (groupTask instanceof SplitTask splitTask) {
|
||||
task1 = splitTask.getTopLeftTask();
|
||||
task2 = splitTask.getBottomRightTask();
|
||||
} else if (groupTask instanceof SingleTask singleTask) {
|
||||
task1 = singleTask.getTask();
|
||||
task2 = null;
|
||||
if (groupTask instanceof DesktopTask desktopTask) {
|
||||
HashMap<String, Integer> args = new HashMap<>();
|
||||
args.put("count", desktopTask.tasks.size());
|
||||
|
||||
currentTaskView.<ImageView>findViewById(R.id.icon).setImageDrawable(
|
||||
ResourcesCompat.getDrawable(resources, R.drawable.ic_desktop, theme));
|
||||
currentTaskView.<TextView>findViewById(R.id.text).setText(new MessageFormat(
|
||||
resources.getString(R.string.quick_switch_desktop),
|
||||
Locale.getDefault()).format(args));
|
||||
} else {
|
||||
continue;
|
||||
currentTaskView.setThumbnails(
|
||||
groupTask.task1,
|
||||
groupTask.task2,
|
||||
updateTasks ? mViewCallbacks::updateThumbnailInBackground : null,
|
||||
updateTasks ? mViewCallbacks::updateIconInBackground : null);
|
||||
}
|
||||
|
||||
currentTaskView.setPositionInformation(i, tasksToDisplay);
|
||||
currentTaskView.setThumbnailsForSplitTasks(
|
||||
task1,
|
||||
task2,
|
||||
updateTasks ? mViewCallbacks::updateThumbnailInBackground : null,
|
||||
updateTasks ? mViewCallbacks::updateIconInBackground : null,
|
||||
groupTask instanceof SplitTask splitTask ? splitTask.getSplitBounds() : null);
|
||||
|
||||
previousTaskView = currentTaskView;
|
||||
}
|
||||
|
||||
if (numHiddenTasks > 0) {
|
||||
HashMap<String, Integer> args = new HashMap<>();
|
||||
args.put("count", numHiddenTasks);
|
||||
|
||||
mOverviewTaskIndex = getTaskCount();
|
||||
View overviewButton = createAndAddTaskView(
|
||||
mOverviewTaskIndex,
|
||||
/* isFinalView= */ !useDesktopTaskView,
|
||||
/* useSmallStartSpacing= */ false,
|
||||
R.layout.keyboard_quick_switch_overview_taskview,
|
||||
MAX_TASKS,
|
||||
/* isFinalView= */ true,
|
||||
R.layout.keyboard_quick_switch_textonly_taskview,
|
||||
layoutInflater,
|
||||
previousTaskView);
|
||||
|
||||
overviewButton.<TextView>findViewById(R.id.large_text).setText(
|
||||
String.format(Locale.getDefault(), "%d", numHiddenTasks));
|
||||
overviewButton.<TextView>findViewById(R.id.small_text).setText(new MessageFormat(
|
||||
overviewButton.<ImageView>findViewById(R.id.icon).setImageDrawable(
|
||||
ResourcesCompat.getDrawable(resources, R.drawable.view_carousel, theme));
|
||||
overviewButton.<TextView>findViewById(R.id.text).setText(new MessageFormat(
|
||||
resources.getString(R.string.quick_switch_overflow),
|
||||
Locale.getDefault()).format(args));
|
||||
|
||||
previousTaskView = overviewButton;
|
||||
}
|
||||
if (useDesktopTaskView) {
|
||||
mDesktopTaskIndex = getTaskCount();
|
||||
View desktopButton = createAndAddTaskView(
|
||||
mDesktopTaskIndex,
|
||||
/* isFinalView= */ true,
|
||||
/* useSmallStartSpacing= */ numHiddenTasks > 0,
|
||||
R.layout.keyboard_quick_switch_desktop_taskview,
|
||||
layoutInflater,
|
||||
previousTaskView);
|
||||
|
||||
desktopButton.<TextView>findViewById(R.id.small_text).setText(
|
||||
resources.getString(R.string.quick_switch_desktop));
|
||||
}
|
||||
mDisplayingRecentTasks = !groupTasks.isEmpty() || useDesktopTaskView;
|
||||
mDisplayingRecentTasks = !groupTasks.isEmpty();
|
||||
|
||||
getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
registerOnBackInvokedCallback();
|
||||
animateOpen(currentFocusIndexOverride);
|
||||
|
||||
getViewTreeObserver().removeOnGlobalLayoutListener(this);
|
||||
@@ -347,120 +250,6 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void enableScrollArrowSupport() {
|
||||
if (mSupportsScrollArrows) {
|
||||
return;
|
||||
}
|
||||
mSupportsScrollArrows = true;
|
||||
|
||||
if (mIsRtl) {
|
||||
mStartScrollArrow.setContentDescription(
|
||||
getResources().getString(R.string.quick_switch_scroll_arrow_right));
|
||||
mEndScrollArrow.setContentDescription(
|
||||
getResources().getString(R.string.quick_switch_scroll_arrow_left));
|
||||
}
|
||||
|
||||
|
||||
mStartScrollArrow.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mIsRtl) {
|
||||
runScrollCommand(false, () -> {
|
||||
mScrollView.smoothScrollBy(mScrollView.getWidth(), 0);
|
||||
});
|
||||
} else {
|
||||
runScrollCommand(false, () -> {
|
||||
mScrollView.smoothScrollBy(-mScrollView.getWidth(), 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mEndScrollArrow.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mIsRtl) {
|
||||
runScrollCommand(false, () -> {
|
||||
mScrollView.smoothScrollBy(-mScrollView.getWidth(), 0);
|
||||
});
|
||||
} else {
|
||||
runScrollCommand(false, () -> {
|
||||
mScrollView.smoothScrollBy(mScrollView.getWidth(), 0);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add listeners to disable arrow buttons when the scroll view cannot be further scrolled in
|
||||
// the associated direction.
|
||||
mScrollView.setOnScrollChangeListener(new OnScrollChangeListener() {
|
||||
@Override
|
||||
public void onScrollChange(View v, int scrollX, int scrollY, int oldScrollX,
|
||||
int oldScrollY) {
|
||||
updateArrowButtonsEnabledState();
|
||||
}
|
||||
});
|
||||
|
||||
// Update scroll view outline to clip its contents with rounded corners.
|
||||
mScrollView.setClipToOutline(true);
|
||||
mScrollView.setOutlineProvider(new ViewOutlineProvider() {
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
int spacingWithoutBorder = mSpacing - mTaskViewBorderWidth;
|
||||
outline.setRoundRect(spacingWithoutBorder,
|
||||
spacingWithoutBorder, view.getWidth() - spacingWithoutBorder,
|
||||
view.getHeight() - spacingWithoutBorder,
|
||||
mTaskViewRadius);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateArrowButtonsEnabledState() {
|
||||
if (!mDisplayingRecentTasks) {
|
||||
return;
|
||||
}
|
||||
|
||||
int scrollX = mScrollView.getScrollX();
|
||||
if (mIsRtl) {
|
||||
mEndScrollArrow.setEnabled(scrollX > 0);
|
||||
mStartScrollArrow.setEnabled(scrollX < mContent.getWidth() - mScrollView.getWidth());
|
||||
} else {
|
||||
mStartScrollArrow.setEnabled(scrollX > 0);
|
||||
mEndScrollArrow.setEnabled(scrollX < mContent.getWidth() - mScrollView.getWidth());
|
||||
}
|
||||
}
|
||||
|
||||
int getOverviewTaskIndex() {
|
||||
return mOverviewTaskIndex;
|
||||
}
|
||||
|
||||
int getDesktopTaskIndex() {
|
||||
return mDesktopTaskIndex;
|
||||
}
|
||||
|
||||
void resetViewCallbacks() {
|
||||
// Unregister the back invoked callback after the view is closed and before the
|
||||
// mViewCallbacks is reset.
|
||||
unregisterOnBackInvokedCallback();
|
||||
mViewCallbacks = null;
|
||||
}
|
||||
|
||||
private void animateDisplayedContentForClose(View view, AnimatorSet animator) {
|
||||
Animator translationYAnimation = ObjectAnimator.ofFloat(
|
||||
view,
|
||||
TRANSLATION_Y,
|
||||
0, -Utilities.dpToPx(CONTENT_START_TRANSLATION_Y_DP));
|
||||
translationYAnimation.setDuration(CONTENT_TRANSLATION_Y_ANIMATION_DURATION_MS);
|
||||
translationYAnimation.setInterpolator(CLOSE_TRANSLATION_Y_INTERPOLATOR);
|
||||
animator.play(translationYAnimation);
|
||||
|
||||
Animator contentAlphaAnimation = ObjectAnimator.ofFloat(view, ALPHA, 1f, 0f);
|
||||
contentAlphaAnimation.setDuration(CONTENT_ALPHA_ANIMATION_DURATION_MS);
|
||||
animator.play(contentAlphaAnimation);
|
||||
|
||||
}
|
||||
|
||||
protected Animator getCloseAnimation() {
|
||||
AnimatorSet closeAnimation = new AnimatorSet();
|
||||
|
||||
@@ -475,11 +264,17 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
closeAnimation.play(alphaAnimation);
|
||||
|
||||
View displayedContent = mDisplayingRecentTasks ? mScrollView : mNoRecentItemsPane;
|
||||
animateDisplayedContentForClose(displayedContent, closeAnimation);
|
||||
if (mSupportsScrollArrows) {
|
||||
animateDisplayedContentForClose(mStartScrollArrow, closeAnimation);
|
||||
animateDisplayedContentForClose(mEndScrollArrow, closeAnimation);
|
||||
}
|
||||
Animator translationYAnimation = ObjectAnimator.ofFloat(
|
||||
displayedContent,
|
||||
TRANSLATION_Y,
|
||||
0, -Utilities.dpToPx(CONTENT_START_TRANSLATION_Y_DP));
|
||||
translationYAnimation.setDuration(CONTENT_TRANSLATION_Y_ANIMATION_DURATION_MS);
|
||||
translationYAnimation.setInterpolator(CLOSE_TRANSLATION_Y_INTERPOLATOR);
|
||||
closeAnimation.play(translationYAnimation);
|
||||
|
||||
Animator contentAlphaAnimation = ObjectAnimator.ofFloat(displayedContent, ALPHA, 1f, 0f);
|
||||
contentAlphaAnimation.setDuration(CONTENT_ALPHA_ANIMATION_DURATION_MS);
|
||||
closeAnimation.play(contentAlphaAnimation);
|
||||
|
||||
closeAnimation.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
@@ -494,42 +289,12 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
return closeAnimation;
|
||||
}
|
||||
|
||||
private void animateDisplayedContentForOpen(View view, AnimatorSet animator) {
|
||||
Animator translationXAnimation = ObjectAnimator.ofFloat(
|
||||
view,
|
||||
TRANSLATION_X,
|
||||
-Utilities.dpToPx(CONTENT_START_TRANSLATION_X_DP), 0);
|
||||
translationXAnimation.setDuration(CONTENT_TRANSLATION_X_ANIMATION_DURATION_MS);
|
||||
translationXAnimation.setInterpolator(OPEN_TRANSLATION_X_INTERPOLATOR);
|
||||
animator.play(translationXAnimation);
|
||||
|
||||
Animator translationYAnimation = ObjectAnimator.ofFloat(
|
||||
view,
|
||||
TRANSLATION_Y,
|
||||
-Utilities.dpToPx(CONTENT_START_TRANSLATION_Y_DP), 0);
|
||||
translationYAnimation.setDuration(CONTENT_TRANSLATION_Y_ANIMATION_DURATION_MS);
|
||||
translationYAnimation.setInterpolator(OPEN_TRANSLATION_Y_INTERPOLATOR);
|
||||
animator.play(translationYAnimation);
|
||||
|
||||
view.setAlpha(0.0f);
|
||||
Animator contentAlphaAnimation = ObjectAnimator.ofFloat(view, ALPHA, 0f,
|
||||
1f);
|
||||
contentAlphaAnimation.setStartDelay(CONTENT_ALPHA_ANIMATION_START_DELAY_MS);
|
||||
contentAlphaAnimation.setDuration(CONTENT_ALPHA_ANIMATION_DURATION_MS);
|
||||
animator.play(contentAlphaAnimation);
|
||||
}
|
||||
|
||||
protected void animateOpen(int currentFocusIndexOverride) {
|
||||
private void animateOpen(int currentFocusIndexOverride) {
|
||||
if (mOpenAnimation != null) {
|
||||
// Restart animation since currentFocusIndexOverride can change the initial scroll.
|
||||
// Restart animation since currentFocusIndexOverride can change the initial
|
||||
// scroll.
|
||||
mOpenAnimation.cancel();
|
||||
}
|
||||
|
||||
// Reset the alpha for the case where the KQS view is opened before.
|
||||
setAlpha(0);
|
||||
mScrollView.setAlpha(0);
|
||||
mNoRecentItemsPane.setAlpha(0);
|
||||
|
||||
mOpenAnimation = new AnimatorSet();
|
||||
|
||||
Animator outlineAnimation = mOutlineAnimationProgress.animateToValue(1f);
|
||||
@@ -541,23 +306,32 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
mOpenAnimation.play(alphaAnimation);
|
||||
|
||||
View displayedContent = mDisplayingRecentTasks ? mScrollView : mNoRecentItemsPane;
|
||||
animateDisplayedContentForOpen(displayedContent, mOpenAnimation);
|
||||
if (mSupportsScrollArrows) {
|
||||
animateDisplayedContentForOpen(mStartScrollArrow, mOpenAnimation);
|
||||
animateDisplayedContentForOpen(mEndScrollArrow, mOpenAnimation);
|
||||
}
|
||||
Animator translationXAnimation = ObjectAnimator.ofFloat(
|
||||
displayedContent,
|
||||
TRANSLATION_X,
|
||||
-Utilities.dpToPx(CONTENT_START_TRANSLATION_X_DP), 0);
|
||||
translationXAnimation.setDuration(CONTENT_TRANSLATION_X_ANIMATION_DURATION_MS);
|
||||
translationXAnimation.setInterpolator(OPEN_TRANSLATION_X_INTERPOLATOR);
|
||||
mOpenAnimation.play(translationXAnimation);
|
||||
|
||||
Animator translationYAnimation = ObjectAnimator.ofFloat(
|
||||
displayedContent,
|
||||
TRANSLATION_Y,
|
||||
-Utilities.dpToPx(CONTENT_START_TRANSLATION_Y_DP), 0);
|
||||
translationYAnimation.setDuration(CONTENT_TRANSLATION_Y_ANIMATION_DURATION_MS);
|
||||
translationYAnimation.setInterpolator(OPEN_TRANSLATION_Y_INTERPOLATOR);
|
||||
mOpenAnimation.play(translationYAnimation);
|
||||
|
||||
Animator contentAlphaAnimation = ObjectAnimator.ofFloat(displayedContent, ALPHA, 0f, 1f);
|
||||
contentAlphaAnimation.setStartDelay(CONTENT_ALPHA_ANIMATION_START_DELAY_MS);
|
||||
contentAlphaAnimation.setDuration(CONTENT_ALPHA_ANIMATION_DURATION_MS);
|
||||
mOpenAnimation.play(contentAlphaAnimation);
|
||||
|
||||
ViewOutlineProvider outlineProvider = getOutlineProvider();
|
||||
int defaultFocusedTaskIndex = Math.min(
|
||||
getTaskCount() - 1,
|
||||
currentFocusIndexOverride == -1 ? 1 : currentFocusIndexOverride);
|
||||
mOpenAnimation.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
super.onAnimationStart(animation);
|
||||
InteractionJankMonitorWrapper.begin(
|
||||
KeyboardQuickSwitchView.this, Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN);
|
||||
setClipToPadding(false);
|
||||
setOutlineProvider(new ViewOutlineProvider() {
|
||||
@Override
|
||||
@@ -584,39 +358,14 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
OPEN_OUTLINE_INTERPOLATOR));
|
||||
}
|
||||
});
|
||||
|
||||
if (mSupportsScrollArrows) {
|
||||
mScrollView.getViewTreeObserver().addOnGlobalLayoutListener(
|
||||
new ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
if (mScrollView.getWidth() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (mContent.getWidth() > mScrollView.getWidth()) {
|
||||
mStartScrollArrow.setVisibility(VISIBLE);
|
||||
mEndScrollArrow.setVisibility(VISIBLE);
|
||||
updateArrowButtonsEnabledState();
|
||||
}
|
||||
mScrollView.getViewTreeObserver().removeOnGlobalLayoutListener(
|
||||
this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
animateFocusMove(-1, defaultFocusedTaskIndex);
|
||||
animateFocusMove(-1, Math.min(
|
||||
mContent.getChildCount() - 1,
|
||||
currentFocusIndexOverride == -1 ? 1 : currentFocusIndexOverride));
|
||||
displayedContent.setVisibility(VISIBLE);
|
||||
setVisibility(VISIBLE);
|
||||
requestFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
super.onAnimationCancel(animation);
|
||||
InteractionJankMonitorWrapper.cancel(Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
super.onAnimationEnd(animation);
|
||||
@@ -624,12 +373,6 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
setOutlineProvider(outlineProvider);
|
||||
invalidateOutline();
|
||||
mOpenAnimation = null;
|
||||
InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_OPEN);
|
||||
|
||||
View focusedTask = getTaskAt(defaultFocusedTaskIndex);
|
||||
if (focusedTask != null) {
|
||||
focusedTask.requestAccessibilityFocus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -661,7 +404,8 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
int firstVisibleTaskIndex = toIndex == 0
|
||||
? toIndex
|
||||
: getTaskAt(toIndex - 1) == null
|
||||
? toIndex : toIndex - 1;
|
||||
? toIndex
|
||||
: toIndex - 1;
|
||||
// Scroll so that the previous task view is truncated as a visual hint that
|
||||
// there are more tasks
|
||||
initializeScroll(
|
||||
@@ -792,9 +536,8 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
}
|
||||
|
||||
private boolean shouldScroll(@NonNull View targetTask, boolean shouldTruncateTarget) {
|
||||
boolean isTargetTruncated =
|
||||
targetTask.getRight() + mSpacing > mScrollView.getScrollX() + mScrollView.getWidth()
|
||||
|| Math.max(0, targetTask.getLeft() - mSpacing) < mScrollView.getScrollX();
|
||||
boolean isTargetTruncated = targetTask.getRight() + mSpacing > mScrollView.getScrollX() + mScrollView.getWidth()
|
||||
|| Math.max(0, targetTask.getLeft() - mSpacing) < mScrollView.getScrollX();
|
||||
|
||||
return isTargetTruncated && !shouldTruncateTarget;
|
||||
}
|
||||
@@ -816,11 +559,8 @@ public class KeyboardQuickSwitchView extends ConstraintLayout {
|
||||
|
||||
@Nullable
|
||||
protected KeyboardQuickSwitchTaskView getTaskAt(int index) {
|
||||
return !mDisplayingRecentTasks || index < 0 || index >= getTaskCount()
|
||||
? null : (KeyboardQuickSwitchTaskView) mContent.getChildAt(index);
|
||||
}
|
||||
|
||||
public int getTaskCount() {
|
||||
return mContent.getChildCount();
|
||||
return !mDisplayingRecentTasks || index < 0 || index >= mContent.getChildCount()
|
||||
? null
|
||||
: (KeyboardQuickSwitchTaskView) mContent.getChildAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
+41
-194
@@ -15,42 +15,30 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static com.android.launcher3.desktop.DesktopAppLaunchTransition.AppLaunchType.UNMINIMIZE;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static android.window.SplashScreen.SPLASH_SCREEN_STYLE_UNDEFINED;
|
||||
|
||||
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.content.res.Resources;
|
||||
import android.view.Gravity;
|
||||
import android.app.ActivityOptions;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.window.OnBackInvokedCallback;
|
||||
import android.window.RemoteTransition;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.jank.Cuj;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Flags;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatorListeners;
|
||||
import com.android.launcher3.desktop.DesktopAppLaunchTransition;
|
||||
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
|
||||
import com.android.launcher3.taskbar.overlay.TaskbarOverlayDragLayer;
|
||||
import com.android.launcher3.views.BaseDragLayer;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.util.DesktopTask;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
import com.android.quickstep.util.SingleTask;
|
||||
import com.android.quickstep.util.SlideInRemoteTransition;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
|
||||
import com.android.systemui.shared.system.ActivityManagerWrapper;
|
||||
import com.android.systemui.shared.system.QuickStepContract;
|
||||
import com.android.wm.shell.shared.desktopmode.DesktopTaskToFrontReason;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.List;
|
||||
@@ -73,10 +61,6 @@ public class KeyboardQuickSwitchViewController {
|
||||
private int mCurrentFocusIndex = -1;
|
||||
|
||||
private boolean mOnDesktop;
|
||||
private boolean mWasDesktopTaskFilteredOut;
|
||||
private boolean mWasOpenedFromTaskbar;
|
||||
|
||||
private boolean mDetachingFromWindow = false;
|
||||
|
||||
protected KeyboardQuickSwitchViewController(
|
||||
@NonNull TaskbarControllers controllers,
|
||||
@@ -93,36 +77,14 @@ public class KeyboardQuickSwitchViewController {
|
||||
return mCurrentFocusIndex;
|
||||
}
|
||||
|
||||
protected boolean wasOpenedFromTaskbar() {
|
||||
return mWasOpenedFromTaskbar;
|
||||
}
|
||||
|
||||
protected void openQuickSwitchView(
|
||||
@NonNull List<GroupTask> tasks,
|
||||
int numHiddenTasks,
|
||||
boolean updateTasks,
|
||||
int currentFocusIndexOverride,
|
||||
boolean onDesktop,
|
||||
boolean hasDesktopTask,
|
||||
boolean wasDesktopTaskFilteredOut,
|
||||
boolean wasOpenedFromTaskbar) {
|
||||
final boolean isTransientTaskBar = mControllers.taskbarActivityContext.isTransientTaskbar();
|
||||
positionView(wasOpenedFromTaskbar, isTransientTaskBar);
|
||||
|
||||
// Keep the taskbar unstashed if the KQS is opened.
|
||||
if (wasOpenedFromTaskbar && isTransientTaskBar) {
|
||||
mControllers.taskbarStashController.updateTaskbarTimeout(/* isAutohideSuspended= */
|
||||
true);
|
||||
}
|
||||
|
||||
boolean onDesktop) {
|
||||
mOverlayContext.getDragLayer().addView(mKeyboardQuickSwitchView);
|
||||
mOnDesktop = onDesktop;
|
||||
mWasDesktopTaskFilteredOut = wasDesktopTaskFilteredOut;
|
||||
mWasOpenedFromTaskbar = wasOpenedFromTaskbar;
|
||||
|
||||
if (Flags.taskbarOverflow() && wasOpenedFromTaskbar) {
|
||||
mKeyboardQuickSwitchView.enableScrollArrowSupport();
|
||||
}
|
||||
|
||||
mKeyboardQuickSwitchView.applyLoadPlan(
|
||||
mOverlayContext,
|
||||
@@ -130,66 +92,7 @@ public class KeyboardQuickSwitchViewController {
|
||||
numHiddenTasks,
|
||||
updateTasks,
|
||||
currentFocusIndexOverride,
|
||||
mViewCallbacks,
|
||||
/* useDesktopTaskView= */ !onDesktop && hasDesktopTask);
|
||||
}
|
||||
|
||||
protected void updateQuickSwitchView(
|
||||
@NonNull List<GroupTask> tasks,
|
||||
int numHiddenTasks,
|
||||
int currentFocusIndexOverride,
|
||||
boolean hasDesktopTask,
|
||||
boolean wasDesktopTaskFilteredOut) {
|
||||
mWasDesktopTaskFilteredOut = wasDesktopTaskFilteredOut;
|
||||
mKeyboardQuickSwitchView.applyLoadPlan(
|
||||
mOverlayContext,
|
||||
tasks,
|
||||
numHiddenTasks,
|
||||
/* updateTasks= */ true,
|
||||
currentFocusIndexOverride,
|
||||
mViewCallbacks,
|
||||
/* useDesktopTaskView= */ !mOnDesktop && hasDesktopTask);
|
||||
}
|
||||
|
||||
protected void positionView(boolean wasOpenedFromTaskbar, boolean isTransientTaskbar) {
|
||||
if (!wasOpenedFromTaskbar) {
|
||||
// Keep the default positioning.
|
||||
return;
|
||||
}
|
||||
|
||||
BaseDragLayer.LayoutParams lp = new BaseDragLayer.LayoutParams(
|
||||
mKeyboardQuickSwitchView.getLayoutParams());
|
||||
final Resources resources = mKeyboardQuickSwitchView.getResources();
|
||||
final int marginHorizontal = resources.getDimensionPixelSize(
|
||||
R.dimen.keyboard_quick_switch_margin_ends);
|
||||
|
||||
final DeviceProfile dp = mControllers.taskbarActivityContext.getDeviceProfile();
|
||||
// Calculate the additional margin space that the KQS should move up for the transient
|
||||
// taskbar. The value of spaceForTaskbar is the distance between the bottom of the KQS
|
||||
// view with 0 bottom margin to the top of the transient taskbar view.
|
||||
final int spaceForTaskbar = isTransientTaskbar ? dp.taskbarHeight + dp.taskbarBottomMargin
|
||||
- dp.stashedTaskbarHeight : 0;
|
||||
final int marginBottom = spaceForTaskbar + resources.getDimensionPixelSize(
|
||||
R.dimen.keyboard_quick_switch_margin_bottom);
|
||||
|
||||
lp.setMargins(marginHorizontal, 0, marginHorizontal, marginBottom);
|
||||
lp.width = BaseDragLayer.LayoutParams.WRAP_CONTENT;
|
||||
lp.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
|
||||
mKeyboardQuickSwitchView.setLayoutParams(lp);
|
||||
}
|
||||
|
||||
protected void updateLayoutForSurface(boolean updateLayoutFromTaskbar,
|
||||
int currentFocusIndexOverride) {
|
||||
BaseDragLayer.LayoutParams lp =
|
||||
(BaseDragLayer.LayoutParams) mKeyboardQuickSwitchView.getLayoutParams();
|
||||
|
||||
if (updateLayoutFromTaskbar) {
|
||||
lp.width = BaseDragLayer.LayoutParams.WRAP_CONTENT;
|
||||
} else {
|
||||
lp.width = BaseDragLayer.LayoutParams.MATCH_PARENT;
|
||||
}
|
||||
|
||||
mKeyboardQuickSwitchView.animateOpen(currentFocusIndexOverride);
|
||||
mViewCallbacks);
|
||||
}
|
||||
|
||||
boolean isCloseAnimationRunning() {
|
||||
@@ -198,29 +101,18 @@ public class KeyboardQuickSwitchViewController {
|
||||
|
||||
protected void closeQuickSwitchView(boolean animate) {
|
||||
if (isCloseAnimationRunning()) {
|
||||
// Let currently-running animation finish.
|
||||
if (!animate) {
|
||||
mCloseAnimation.end();
|
||||
}
|
||||
// Let currently-running animation finish.
|
||||
return;
|
||||
}
|
||||
mControllerCallbacks.onCloseStarted();
|
||||
if (!animate) {
|
||||
InteractionJankMonitorWrapper.begin(
|
||||
mKeyboardQuickSwitchView, Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE);
|
||||
onCloseComplete();
|
||||
return;
|
||||
}
|
||||
mCloseAnimation = mKeyboardQuickSwitchView.getCloseAnimation();
|
||||
|
||||
mCloseAnimation.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
super.onAnimationStart(animation);
|
||||
InteractionJankMonitorWrapper.begin(
|
||||
mKeyboardQuickSwitchView, Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE);
|
||||
}
|
||||
});
|
||||
mCloseAnimation.addListener(AnimatorListeners.forEndCallback(this::onCloseComplete));
|
||||
mCloseAnimation.start();
|
||||
}
|
||||
@@ -239,7 +131,7 @@ public class KeyboardQuickSwitchViewController {
|
||||
}
|
||||
// If the user quick switches too quickly, updateCurrentFocusIndex might not have run.
|
||||
return launchTaskAt(mControllerCallbacks.isFirstTaskRunning()
|
||||
&& mKeyboardQuickSwitchView.getTaskCount() > 1 ? 1 : 0);
|
||||
&& mControllerCallbacks.getTaskCount() > 1 ? 1 : 0);
|
||||
}
|
||||
|
||||
private int launchTaskAt(int index) {
|
||||
@@ -247,33 +139,6 @@ public class KeyboardQuickSwitchViewController {
|
||||
// Ignore taps on task views and alt key unpresses while the close animation is running.
|
||||
return -1;
|
||||
}
|
||||
if (index == mKeyboardQuickSwitchView.getOverviewTaskIndex()) {
|
||||
// If there is a desktop task view, then we should account for it when focusing the
|
||||
// first hidden non-desktop task view in recents view
|
||||
return mOnDesktop ? 1 : (mWasDesktopTaskFilteredOut ? index + 1 : index);
|
||||
}
|
||||
Runnable onStartCallback = () -> InteractionJankMonitorWrapper.begin(
|
||||
mKeyboardQuickSwitchView, Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH);
|
||||
Runnable onFinishCallback = () -> InteractionJankMonitorWrapper.end(
|
||||
Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_APP_LAUNCH);
|
||||
TaskbarActivityContext context = mControllers.taskbarActivityContext;
|
||||
final RemoteTransition slideInTransition = new RemoteTransition(new SlideInRemoteTransition(
|
||||
Utilities.isRtl(mControllers.taskbarActivityContext.getResources()),
|
||||
context.getDeviceProfile().overviewPageSpacing,
|
||||
QuickStepContract.getWindowCornerRadius(context),
|
||||
AnimationUtils.loadInterpolator(
|
||||
context, android.R.interpolator.fast_out_extra_slow_in),
|
||||
onStartCallback,
|
||||
onFinishCallback),
|
||||
"SlideInTransition");
|
||||
if (index == mKeyboardQuickSwitchView.getDesktopTaskIndex()) {
|
||||
UI_HELPER_EXECUTOR.execute(() ->
|
||||
SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext())
|
||||
.showDesktopApps(
|
||||
mKeyboardQuickSwitchView.getDisplay().getDisplayId(),
|
||||
slideInTransition));
|
||||
return -1;
|
||||
}
|
||||
// Even with a valid index, this can be null if the user tries to quick switch before the
|
||||
// views have been added in the KeyboardQuickSwitchView.
|
||||
GroupTask task = mControllerCallbacks.getTaskAt(index);
|
||||
@@ -284,41 +149,44 @@ public class KeyboardQuickSwitchViewController {
|
||||
// Ignore attempts to run the selected task if it is already running.
|
||||
return -1;
|
||||
}
|
||||
RemoteTransition remoteTransition = slideInTransition;
|
||||
boolean canUnminimizeDesktopTask = task instanceof SingleTask singleTask
|
||||
&& mControllers.taskbarActivityContext.canUnminimizeDesktopTask(
|
||||
singleTask.getTask().key.id);
|
||||
if (mOnDesktop && canUnminimizeDesktopTask) {
|
||||
// This app is being unminimized - use our own transition runner.
|
||||
remoteTransition = new RemoteTransition(
|
||||
new DesktopAppLaunchTransition(
|
||||
context,
|
||||
UNMINIMIZE,
|
||||
Cuj.CUJ_DESKTOP_MODE_KEYBOARD_QUICK_SWITCH_APP_LAUNCH,
|
||||
MAIN_EXECUTOR
|
||||
),
|
||||
"DesktopKeyboardQuickSwitchUnminimize");
|
||||
|
||||
TaskbarActivityContext context = mControllers.taskbarActivityContext;
|
||||
RemoteTransition remoteTransition = new RemoteTransition(new SlideInRemoteTransition(
|
||||
Utilities.isRtl(mControllers.taskbarActivityContext.getResources()),
|
||||
context.getDeviceProfile().overviewPageSpacing,
|
||||
QuickStepContract.getWindowCornerRadius(context),
|
||||
AnimationUtils.loadInterpolator(
|
||||
context, android.R.interpolator.fast_out_extra_slow_in)),
|
||||
"SlideInTransition");
|
||||
if (task instanceof DesktopTask) {
|
||||
UI_HELPER_EXECUTOR.execute(() ->
|
||||
SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext())
|
||||
.showDesktopApps(
|
||||
mKeyboardQuickSwitchView.getDisplay().getDisplayId(),
|
||||
remoteTransition));
|
||||
} else if (mOnDesktop) {
|
||||
UI_HELPER_EXECUTOR.execute(() ->
|
||||
SystemUiProxy.INSTANCE.get(mKeyboardQuickSwitchView.getContext())
|
||||
.showDesktopApp(task.task1.key.id));
|
||||
} else if (task.task2 == null) {
|
||||
UI_HELPER_EXECUTOR.execute(() -> {
|
||||
ActivityOptions activityOptions = mControllers.taskbarActivityContext
|
||||
.makeDefaultActivityOptions(SPLASH_SCREEN_STYLE_UNDEFINED).options;
|
||||
activityOptions.setRemoteTransition(remoteTransition);
|
||||
|
||||
ActivityManagerWrapper.getInstance().startActivityFromRecents(
|
||||
task.task1.key, activityOptions);
|
||||
});
|
||||
} else {
|
||||
mControllers.uiController.launchSplitTasks(task, remoteTransition);
|
||||
}
|
||||
mControllers.taskbarActivityContext.handleGroupTaskLaunch(
|
||||
task,
|
||||
remoteTransition,
|
||||
mOnDesktop,
|
||||
DesktopTaskToFrontReason.ALT_TAB,
|
||||
onStartCallback,
|
||||
onFinishCallback);
|
||||
return -1;
|
||||
}
|
||||
|
||||
private void onCloseComplete() {
|
||||
mCloseAnimation = null;
|
||||
// Reset the view callbacks to prevent `onDetachedFromWindow` getting called in response to
|
||||
// the `removeView(mKeyboardQuickSwitchView)` call.
|
||||
mKeyboardQuickSwitchView.resetViewCallbacks();
|
||||
if (!mDetachingFromWindow) {
|
||||
mOverlayContext.getDragLayer().removeView(mKeyboardQuickSwitchView);
|
||||
}
|
||||
mOverlayContext.getDragLayer().removeView(mKeyboardQuickSwitchView);
|
||||
mControllerCallbacks.onCloseComplete();
|
||||
InteractionJankMonitorWrapper.end(Cuj.CUJ_LAUNCHER_KEYBOARD_QUICK_SWITCH_CLOSE);
|
||||
}
|
||||
|
||||
protected void onDestroy() {
|
||||
@@ -331,20 +199,9 @@ public class KeyboardQuickSwitchViewController {
|
||||
pw.println(prefix + "\thasFocus=" + mKeyboardQuickSwitchView.hasFocus());
|
||||
pw.println(prefix + "\tisCloseAnimationRunning=" + isCloseAnimationRunning());
|
||||
pw.println(prefix + "\tmCurrentFocusIndex=" + mCurrentFocusIndex);
|
||||
pw.println(prefix + "\tmOnDesktop=" + mOnDesktop);
|
||||
pw.println(prefix + "\tmWasDesktopTaskFilteredOut=" + mWasDesktopTaskFilteredOut);
|
||||
pw.println(prefix + "\tmWasOpenedFromTaskbar=" + mWasOpenedFromTaskbar);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if the MotionEvent is over the {@link KeyboardQuickSwitchView}.
|
||||
*/
|
||||
protected boolean isEventOverKeyboardQuickSwitch(TaskbarOverlayDragLayer dl, MotionEvent ev) {
|
||||
return dl.isEventOverView(mKeyboardQuickSwitchView, ev);
|
||||
}
|
||||
|
||||
class ViewCallbacks {
|
||||
public final OnBackInvokedCallback onBackInvokedCallback = () -> closeQuickSwitchView(true);
|
||||
|
||||
boolean onKeyUp(int keyCode, KeyEvent event, boolean isRTL, boolean allowTraversal) {
|
||||
if (keyCode != KeyEvent.KEYCODE_TAB
|
||||
@@ -369,7 +226,7 @@ public class KeyboardQuickSwitchViewController {
|
||||
boolean traverseBackwards = (keyCode == KeyEvent.KEYCODE_TAB && event.isShiftPressed())
|
||||
|| (keyCode == KeyEvent.KEYCODE_DPAD_RIGHT && isRTL)
|
||||
|| (keyCode == KeyEvent.KEYCODE_DPAD_LEFT && !isRTL);
|
||||
int taskCount = mKeyboardQuickSwitchView.getTaskCount();
|
||||
int taskCount = mControllerCallbacks.getTaskCount();
|
||||
int toIndex = mCurrentFocusIndex == -1
|
||||
// Focus the second-most recent app if possible
|
||||
? (taskCount > 1 ? 1 : 0)
|
||||
@@ -404,15 +261,5 @@ public class KeyboardQuickSwitchViewController {
|
||||
void updateIconInBackground(Task task, Consumer<Task> callback) {
|
||||
mControllerCallbacks.updateIconInBackground(task, callback);
|
||||
}
|
||||
|
||||
boolean isAspectRatioSquare() {
|
||||
return mControllerCallbacks.isAspectRatioSquare();
|
||||
}
|
||||
|
||||
void onViewDetchedFromWindow() {
|
||||
mDetachingFromWindow = true;
|
||||
closeQuickSwitchView(false);
|
||||
mDetachingFromWindow = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,16 +15,12 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY;
|
||||
|
||||
import static com.android.launcher3.Flags.syncAppLaunchWithTaskbarStash;
|
||||
import static com.android.launcher3.QuickstepTransitionManager.TASKBAR_TO_APP_DURATION;
|
||||
import static com.android.launcher3.QuickstepTransitionManager.TRANSIENT_TASKBAR_TRANSITION_DURATION;
|
||||
import static com.android.launcher3.QuickstepTransitionManager.getTaskbarToHomeDuration;
|
||||
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.launcher3.taskbar.TaskbarStashController.FLAG_IGNORE_IN_APP;
|
||||
import static com.android.quickstep.TaskAnimationManager.ENABLE_SHELL_TRANSITIONS;
|
||||
import static com.android.window.flags2.Flags.enableDesktopWindowingWallpaperActivity;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorSet;
|
||||
@@ -35,32 +31,27 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Flags;
|
||||
import com.android.launcher3.Hotseat;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.QuickstepTransitionManager;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.logging.InstanceId;
|
||||
import com.android.launcher3.logging.InstanceIdSequence;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.statehandlers.DesktopVisibilityController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
|
||||
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.GestureState;
|
||||
import com.android.quickstep.HomeVisibilityState;
|
||||
import com.android.quickstep.LauncherActivityInterface;
|
||||
import com.android.quickstep.RecentsAnimationCallbacks;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.fallback.window.RecentsDisplayModel;
|
||||
import com.android.quickstep.fallback.window.RecentsWindowFlags;
|
||||
import com.android.quickstep.util.SplitTask;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
import com.android.quickstep.util.TISBindHelper;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.RecentsViewContainer;
|
||||
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
|
||||
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
|
||||
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
@@ -76,41 +67,32 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
public static final int ALL_APPS_PAGE_PROGRESS_INDEX = 1;
|
||||
public static final int WIDGETS_PAGE_PROGRESS_INDEX = 2;
|
||||
public static final int SYSUI_SURFACE_PROGRESS_INDEX = 3;
|
||||
public static final int LAUNCHER_PAUSE_PROGRESS_INDEX = 4;
|
||||
|
||||
public static final int DISPLAY_PROGRESS_COUNT = 5;
|
||||
public static final int DISPLAY_PROGRESS_COUNT = 4;
|
||||
|
||||
private final AnimatedFloat mTaskbarInAppDisplayProgress = new AnimatedFloat(
|
||||
this::onInAppDisplayProgressChanged);
|
||||
private final MultiPropertyFactory<AnimatedFloat> mTaskbarInAppDisplayProgressMultiProp =
|
||||
new MultiPropertyFactory<>(mTaskbarInAppDisplayProgress,
|
||||
AnimatedFloat.VALUE, DISPLAY_PROGRESS_COUNT, Float::max);
|
||||
private final AnimatedFloat mLauncherPauseProgress = new AnimatedFloat(
|
||||
this::onLauncherPauseProgressUpdate);
|
||||
private final MultiPropertyFactory<AnimatedFloat> mTaskbarInAppDisplayProgressMultiProp = new MultiPropertyFactory<>(
|
||||
mTaskbarInAppDisplayProgress,
|
||||
AnimatedFloat.VALUE, DISPLAY_PROGRESS_COUNT, Float::max);
|
||||
|
||||
private final QuickstepLauncher mLauncher;
|
||||
private final HomeVisibilityState mHomeState;
|
||||
|
||||
private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
|
||||
dp -> {
|
||||
onStashedInAppChanged(dp);
|
||||
postAdjustHotseatForBubbleBar();
|
||||
if (mControllers != null && mControllers.taskbarViewController != null) {
|
||||
mControllers.taskbarViewController.onRotationChanged(dp);
|
||||
}
|
||||
};
|
||||
private final HomeVisibilityState.VisibilityChangeListener mVisibilityChangeListener =
|
||||
this::onLauncherVisibilityChanged;
|
||||
private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener = dp -> {
|
||||
onStashedInAppChanged(dp);
|
||||
if (mControllers != null && mControllers.taskbarViewController != null) {
|
||||
mControllers.taskbarViewController.onRotationChanged(dp);
|
||||
}
|
||||
};
|
||||
private final HomeVisibilityState.VisibilityChangeListener mVisibilityChangeListener = this::onLauncherVisibilityChanged;
|
||||
|
||||
// Initialized in init.
|
||||
private final TaskbarLauncherStateController
|
||||
mTaskbarLauncherStateController = new TaskbarLauncherStateController();
|
||||
// When overview-in-a-window is enabled, that window is the container, else it is mLauncher.
|
||||
private RecentsViewContainer mRecentsViewContainer;
|
||||
private final TaskbarLauncherStateController mTaskbarLauncherStateController = new TaskbarLauncherStateController();
|
||||
|
||||
public LauncherTaskbarUIController(QuickstepLauncher launcher) {
|
||||
mLauncher = launcher;
|
||||
mHomeState = SystemUiProxy.INSTANCE.get(mLauncher).getHomeVisibilityState();
|
||||
mHomeState = SystemUiProxy.INSTANCE.get(mLauncher).getHomeVisibilityState();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -119,23 +101,13 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
|
||||
mTaskbarLauncherStateController.init(mControllers, mLauncher,
|
||||
mControllers.getSharedState().sysuiStateFlags);
|
||||
final TaskbarActivityContext taskbarContext = mControllers.taskbarActivityContext;
|
||||
if (RecentsWindowFlags.getEnableOverviewInWindow()) {
|
||||
mRecentsViewContainer = RecentsDisplayModel.getINSTANCE()
|
||||
.get(taskbarContext).getRecentsWindowManager(taskbarContext.getDisplayId());
|
||||
}
|
||||
if (mRecentsViewContainer == null) {
|
||||
mRecentsViewContainer = mLauncher;
|
||||
}
|
||||
mLauncher.setTaskbarUIController(this);
|
||||
if (mRecentsViewContainer != mLauncher) {
|
||||
mRecentsViewContainer.setTaskbarUIController(this);
|
||||
}
|
||||
|
||||
mLauncher.setTaskbarUIController(this);
|
||||
mHomeState.addListener(mVisibilityChangeListener);
|
||||
onLauncherVisibilityChanged(
|
||||
Flags.useActivityOverlay()
|
||||
? mHomeState.isHomeVisible() : mLauncher.hasBeenResumed(),
|
||||
? mHomeState.isHomeVisible()
|
||||
: mLauncher.hasBeenResumed(),
|
||||
true /* fromInit */);
|
||||
|
||||
onStashedInAppChanged(mLauncher.getDeviceProfile());
|
||||
@@ -143,8 +115,10 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
|
||||
// Restore the in-app display progress from before Taskbar was recreated.
|
||||
float[] prevProgresses = mControllers.getSharedState().inAppDisplayProgressMultiPropValues;
|
||||
// Make a copy of the previous progress to set since updating the multiprop will update
|
||||
// the property which also calls onInAppDisplayProgressChanged() which writes the current
|
||||
// Make a copy of the previous progress to set since updating the multiprop will
|
||||
// update
|
||||
// the property which also calls onInAppDisplayProgressChanged() which writes
|
||||
// the current
|
||||
// values into the shared state
|
||||
prevProgresses = Arrays.copyOf(prevProgresses, prevProgresses.length);
|
||||
for (int i = 0; i < prevProgresses.length; i++) {
|
||||
@@ -154,15 +128,12 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
onLauncherVisibilityChanged(false /* isVisible */, true /* fromInitOrDestroy */);
|
||||
mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
|
||||
super.onDestroy();
|
||||
onLauncherVisibilityChanged(false);
|
||||
mTaskbarLauncherStateController.onDestroy();
|
||||
|
||||
mLauncher.setTaskbarUIController(null);
|
||||
if (mRecentsViewContainer != mLauncher) {
|
||||
mRecentsViewContainer.setTaskbarUIController(null);
|
||||
}
|
||||
mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
|
||||
mHomeState.removeListener(mVisibilityChangeListener);
|
||||
}
|
||||
|
||||
@@ -170,8 +141,9 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
if (mControllers != null) {
|
||||
// Update our shared state so we can restore it if taskbar gets recreated.
|
||||
for (int i = 0; i < DISPLAY_PROGRESS_COUNT; i++) {
|
||||
mControllers.getSharedState().inAppDisplayProgressMultiPropValues[i] =
|
||||
mTaskbarInAppDisplayProgressMultiProp.get(i).getValue();
|
||||
mControllers
|
||||
.getSharedState().inAppDisplayProgressMultiPropValues[i] = mTaskbarInAppDisplayProgressMultiProp
|
||||
.get(i).getValue();
|
||||
}
|
||||
// Ensure nav buttons react to our latest state if necessary.
|
||||
mControllers.navbarButtonsViewController.updateNavButtonTranslationY();
|
||||
@@ -180,9 +152,8 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
|
||||
@Override
|
||||
protected boolean isTaskbarTouchable() {
|
||||
// Touching down during animation to Hotseat will end the transition and allow the touch to
|
||||
// go through to the Hotseat directly.
|
||||
return !isAnimatingToHotseat();
|
||||
return !(mTaskbarLauncherStateController.isAnimatingToLauncher()
|
||||
&& mTaskbarLauncherStateController.isTaskbarAlignedWithHotseat());
|
||||
}
|
||||
|
||||
public void setShouldDelayLauncherStateAnim(boolean shouldDelayLauncherStateAnim) {
|
||||
@@ -190,25 +161,19 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
shouldDelayLauncherStateAnim);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stashHotseat(boolean stash) {
|
||||
mTaskbarLauncherStateController.stashHotseat(stash);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unStashHotseatInstantly() {
|
||||
mTaskbarLauncherStateController.unStashHotseatInstantly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the Launcher resume animator to the given animator set.
|
||||
*
|
||||
* This should be used to run a Launcher resume animation whose progress matches a
|
||||
* This should be used to run a Launcher resume animation whose progress matches
|
||||
* a
|
||||
* swipe progress.
|
||||
*
|
||||
* @param placeholderDuration a placeholder duration to be used to ensure all full-length
|
||||
* sub-animations are properly coordinated. This duration should not
|
||||
* actually be used since this animation tracks a swipe progress.
|
||||
* @param placeholderDuration a placeholder duration to be used to ensure all
|
||||
* full-length
|
||||
* sub-animations are properly coordinated. This
|
||||
* duration should not
|
||||
* actually be used since this animation tracks a
|
||||
* swipe progress.
|
||||
*/
|
||||
protected void addLauncherVisibilityChangedAnimation(AnimatorSet animation,
|
||||
int placeholderDuration) {
|
||||
@@ -220,67 +185,51 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Should be called from onResume() and onPause(), and animates the Taskbar accordingly.
|
||||
* Should be called from onResume() and onPause(), and animates the Taskbar
|
||||
* accordingly.
|
||||
*/
|
||||
@Override
|
||||
public void onLauncherVisibilityChanged(boolean isVisible) {
|
||||
if (DesktopModeStatus.enterDesktopByDefaultOnFreeformDisplay(mLauncher)
|
||||
&& mControllers.taskbarActivityContext.isPrimaryDisplay()) {
|
||||
DisplayController.INSTANCE.get(mLauncher).notifyConfigChange();
|
||||
}
|
||||
|
||||
onLauncherVisibilityChanged(isVisible, false /* fromInit */);
|
||||
}
|
||||
|
||||
private void onLauncherVisibilityChanged(boolean isVisible, boolean fromInitOrDestroy) {
|
||||
if (mControllers == null) {
|
||||
return;
|
||||
}
|
||||
private void onLauncherVisibilityChanged(boolean isVisible, boolean fromInit) {
|
||||
onLauncherVisibilityChanged(
|
||||
isVisible,
|
||||
fromInitOrDestroy,
|
||||
fromInit,
|
||||
/* startAnimation= */ true,
|
||||
getTaskbarAnimationDuration(isVisible));
|
||||
}
|
||||
|
||||
private int getTaskbarAnimationDuration(boolean isVisible) {
|
||||
// fast animation duration since we will not be playing workspace reveal animation.
|
||||
boolean shouldOverrideToFastAnimation = !isHotseatIconOnTopWhenAligned();
|
||||
if (!Flags.predictiveBackToHomePolish()) {
|
||||
shouldOverrideToFastAnimation |= mLauncher.getPredictiveBackToHomeInProgress();
|
||||
}
|
||||
|
||||
boolean isPinned = mControllers.taskbarActivityContext.isPinnedTaskbar();
|
||||
if (isVisible || isPinned) {
|
||||
return getTaskbarToHomeDuration(shouldOverrideToFastAnimation, isPinned);
|
||||
} else {
|
||||
return (mControllers.taskbarActivityContext.isTransientTaskbar())
|
||||
? TRANSIENT_TASKBAR_TRANSITION_DURATION : TASKBAR_TO_APP_DURATION;
|
||||
}
|
||||
DisplayController.isTransientTaskbar(mLauncher)
|
||||
? TRANSIENT_TASKBAR_TRANSITION_DURATION
|
||||
: (!isVisible
|
||||
? QuickstepTransitionManager.TASKBAR_TO_APP_DURATION
|
||||
: QuickstepTransitionManager.getTaskbarToHomeDuration()));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Animator onLauncherVisibilityChanged(
|
||||
boolean isVisible, boolean fromInitOrDestroy, boolean startAnimation, int duration) {
|
||||
// Launcher is resumed during the swipe-to-overview gesture under shell-transitions, so
|
||||
// avoid updating taskbar state in that situation (when it's non-interactive -- or
|
||||
boolean isVisible, boolean fromInit, boolean startAnimation, int duration) {
|
||||
// Launcher is resumed during the swipe-to-overview gesture under
|
||||
// shell-transitions, so
|
||||
// avoid updating taskbar state in that situation (when it's non-interactive --
|
||||
// or
|
||||
// "background") to avoid premature animations.
|
||||
LauncherState state = mTaskbarLauncherStateController.getLauncherState();
|
||||
boolean nonInteractiveState = state.hasFlag(FLAG_NON_INTERACTIVE)
|
||||
&& !state.isTaskbarAlignedWithHotseat(mLauncher);
|
||||
if (isVisible && (nonInteractiveState || mSkipLauncherVisibilityChange)) {
|
||||
if (ENABLE_SHELL_TRANSITIONS && isVisible
|
||||
&& mLauncher.getStateManager().getState().hasFlag(FLAG_NON_INTERACTIVE)
|
||||
&& !mLauncher.getStateManager().getState().isTaskbarAlignedWithHotseat(mLauncher)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
|
||||
&& mControllers.taskbarDesktopModeController
|
||||
.isInDesktopModeAndNotInOverview(mLauncher.getDisplayId())) {
|
||||
DesktopVisibilityController desktopController = LauncherActivityInterface.INSTANCE
|
||||
.getDesktopVisibilityController();
|
||||
if (!enableDesktopWindowingWallpaperActivity()
|
||||
&& desktopController != null
|
||||
&& desktopController.areDesktopTasksVisible()) {
|
||||
// TODO: b/333533253 - Remove after flag rollout
|
||||
isVisible = false;
|
||||
}
|
||||
|
||||
mTaskbarLauncherStateController.updateStateForFlag(FLAG_VISIBLE, isVisible);
|
||||
if (fromInitOrDestroy) {
|
||||
if (fromInit) {
|
||||
duration = 0;
|
||||
}
|
||||
return mTaskbarLauncherStateController.applyState(duration, startAnimation);
|
||||
@@ -294,7 +243,8 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
@Override
|
||||
public void refreshResumedState() {
|
||||
onLauncherVisibilityChanged(Flags.useActivityOverlay()
|
||||
? mHomeState.isHomeVisible() : mLauncher.hasBeenResumed());
|
||||
? mHomeState.isHomeVisible()
|
||||
: mLauncher.hasBeenResumed());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -304,56 +254,19 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
}
|
||||
}
|
||||
|
||||
private void postAdjustHotseatForBubbleBar() {
|
||||
Hotseat hotseat = mLauncher.getHotseat();
|
||||
if (hotseat == null || !isBubbleBarVisible()) return;
|
||||
hotseat.post(() -> {
|
||||
if (mControllers == null) return;
|
||||
adjustHotseatForBubbleBar(isBubbleBarVisible());
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isBubbleBarVisible() {
|
||||
BubbleControllers bubbleControllers = mControllers.bubbleControllers.orElse(null);
|
||||
return bubbleControllers != null
|
||||
&& bubbleControllers.bubbleBarViewController.isBubbleBarVisible();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Taskbar animation when going from an app to Launcher as part of recents transition.
|
||||
* {@inheritDoc}
|
||||
* Create Taskbar animation when going from an app to Launcher as part of
|
||||
* recents transition.
|
||||
*
|
||||
* @param toState If known, the state we will end up in when reaching
|
||||
* Launcher.
|
||||
* @param callbacks callbacks to track the recents animation lifecycle. The
|
||||
* state change is
|
||||
* automatically reset once the recents animation finishes
|
||||
*/
|
||||
@Override
|
||||
public Animator getParallelAnimationToGestureEndTarget(
|
||||
GestureState.GestureEndTarget gestureEndTarget, long duration,
|
||||
RecentsAnimationCallbacks callbacks) {
|
||||
return mTaskbarLauncherStateController.createAnimToLauncher(
|
||||
LauncherActivityInterface.INSTANCE.stateFromGestureEndTarget(gestureEndTarget),
|
||||
callbacks,
|
||||
duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create Taskbar animation to be played alongside the Launcher app launch animation.
|
||||
*/
|
||||
public @Nullable Animator createAnimToApp() {
|
||||
if (!syncAppLaunchWithTaskbarStash()) {
|
||||
return null;
|
||||
}
|
||||
TaskbarStashController stashController = mControllers.taskbarStashController;
|
||||
stashController.updateStateForFlag(TaskbarStashController.FLAG_IN_APP, true);
|
||||
return stashController.createApplyStateAnimator(stashController.getStashDuration());
|
||||
}
|
||||
|
||||
/**
|
||||
* Temporarily ignore FLAG_IN_APP for app launches to prevent premature taskbar stashing.
|
||||
* This is needed because taskbar gets a signal to stash before we actually start the
|
||||
* app launch animation.
|
||||
*/
|
||||
public void setIgnoreInAppFlagForSync(boolean enabled) {
|
||||
if (syncAppLaunchWithTaskbarStash()) {
|
||||
mControllers.taskbarStashController.updateStateForFlag(FLAG_IGNORE_IN_APP, enabled);
|
||||
}
|
||||
public Animator createAnimToLauncher(@NonNull LauncherState toState,
|
||||
@NonNull RecentsAnimationCallbacks callbacks, long duration) {
|
||||
return mTaskbarLauncherStateController.createAnimToLauncher(toState, callbacks, duration);
|
||||
}
|
||||
|
||||
public void updateTaskbarLauncherStateGoingHome() {
|
||||
@@ -362,12 +275,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
}
|
||||
|
||||
public boolean isDraggingItem() {
|
||||
boolean bubblesDragging = false;
|
||||
if (mControllers.bubbleControllers.isPresent()) {
|
||||
bubblesDragging =
|
||||
mControllers.bubbleControllers.get().bubbleDragController.isDragging();
|
||||
}
|
||||
return mControllers.taskbarDragController.isDragging() || bubblesDragging;
|
||||
return mControllers.taskbarDragController.isDragging();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -381,7 +289,8 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Starts a Taskbar EDU flow, if the user should see one upon launching an application.
|
||||
* Starts a Taskbar EDU flow, if the user should see one upon launching an
|
||||
* application.
|
||||
*/
|
||||
public void showEduOnAppLaunch() {
|
||||
if (!shouldShowEduOnAppLaunch()) {
|
||||
@@ -391,7 +300,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
}
|
||||
|
||||
// Persistent features EDU tooltip.
|
||||
if (!mControllers.taskbarActivityContext.isTransientTaskbar()) {
|
||||
if (!DisplayController.isTransientTaskbar(mLauncher)) {
|
||||
mControllers.taskbarEduTooltipController.maybeShowFeaturesEdu();
|
||||
return;
|
||||
}
|
||||
@@ -406,7 +315,8 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if a Taskbar education should be shown on application launch.
|
||||
* Returns {@code true} if a Taskbar education should be shown on application
|
||||
* launch.
|
||||
*/
|
||||
public boolean shouldShowEduOnAppLaunch() {
|
||||
if (Utilities.isRunningInTestHarness()) {
|
||||
@@ -414,7 +324,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
}
|
||||
|
||||
// Persistent features EDU tooltip.
|
||||
if (!mControllers.taskbarActivityContext.isTransientTaskbar()) {
|
||||
if (!DisplayController.isTransientTaskbar(mLauncher)) {
|
||||
return !OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP.hasReachedMax(mLauncher);
|
||||
}
|
||||
|
||||
@@ -431,7 +341,8 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates Taskbar elements during a transition to a Launcher state that should use in-app
|
||||
* Animates Taskbar elements during a transition to a Launcher state that should
|
||||
* use in-app
|
||||
* layouts.
|
||||
*
|
||||
* @param progress [0, 1]
|
||||
@@ -444,22 +355,16 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
// This method can be called before init() is called.
|
||||
return;
|
||||
}
|
||||
if (mControllers.uiController.isIconAlignedWithHotseat()) {
|
||||
if (!mTaskbarLauncherStateController.isAnimatingToLauncher()) {
|
||||
// Only animate the nav buttons while home and not animating home, otherwise let
|
||||
// the TaskbarViewController handle it.
|
||||
mControllers.navbarButtonsViewController
|
||||
.getTaskbarNavButtonTranslationYForInAppDisplay()
|
||||
.updateValue(mLauncher.getDeviceProfile().getTaskbarOffsetY()
|
||||
* mTaskbarInAppDisplayProgress.value);
|
||||
mControllers.navbarButtonsViewController
|
||||
.getOnTaskbarBackgroundNavButtonColorOverride().updateValue(progress);
|
||||
}
|
||||
if (isBubbleBarEnabled()) {
|
||||
mControllers.bubbleControllers.ifPresent(
|
||||
c -> c.bubbleStashController.setInAppDisplayOverrideProgress(
|
||||
mTaskbarInAppDisplayProgress.value));
|
||||
}
|
||||
if (mControllers.uiController.isIconAlignedWithHotseat()
|
||||
&& !mTaskbarLauncherStateController.isAnimatingToLauncher()) {
|
||||
// Only animate the nav buttons while home and not animating home, otherwise let
|
||||
// the TaskbarViewController handle it.
|
||||
mControllers.navbarButtonsViewController
|
||||
.getTaskbarNavButtonTranslationYForInAppDisplay()
|
||||
.updateValue(mLauncher.getDeviceProfile().getTaskbarOffsetY()
|
||||
* mTaskbarInAppDisplayProgress.value);
|
||||
mControllers.navbarButtonsViewController
|
||||
.getOnTaskbarBackgroundNavButtonColorOverride().updateValue(progress);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -504,18 +409,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
public boolean isHotseatIconOnTopWhenAligned() {
|
||||
return mTaskbarLauncherStateController.isInHotseatOnTopStates()
|
||||
&& mTaskbarInAppDisplayProgressMultiProp.get(MINUS_ONE_PAGE_PROGRESS_INDEX)
|
||||
.getValue() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isAnimatingToHotseat() {
|
||||
return mTaskbarLauncherStateController.isAnimatingToLauncher()
|
||||
&& isIconAlignedWithHotseat();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void endAnimationToHotseat() {
|
||||
mTaskbarLauncherStateController.resetIconAlignment();
|
||||
.getValue() == 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -524,26 +418,21 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void toggleAllApps(boolean focusSearch) {
|
||||
boolean canToggleHomeAllApps = mLauncher.isResumed()
|
||||
protected boolean canToggleHomeAllApps() {
|
||||
return mLauncher.isResumed()
|
||||
&& !mTaskbarLauncherStateController.isInOverviewUi()
|
||||
&& !mLauncher.areDesktopTasksVisible();
|
||||
if (canToggleHomeAllApps) {
|
||||
mLauncher.toggleAllApps(focusSearch);
|
||||
return;
|
||||
}
|
||||
super.toggleAllApps(focusSearch);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecentsView getRecentsView() {
|
||||
return mRecentsViewContainer.getOverviewPanel();
|
||||
return mLauncher.getOverviewPanel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void launchSplitTasks(
|
||||
@NonNull SplitTask splitTask, @Nullable RemoteTransition remoteTransition) {
|
||||
mLauncher.launchSplitTasks(splitTask, remoteTransition);
|
||||
@NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) {
|
||||
mLauncher.launchSplitTasks(groupTask, remoteTransition);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -551,6 +440,12 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
mTaskbarLauncherStateController.resetIconAlignment();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected TISBindHelper getTISBindHelper() {
|
||||
return mLauncher.getTISBindHelper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dumpLogs(String prefix, PrintWriter pw) {
|
||||
super.dumpLogs(prefix, pw);
|
||||
@@ -564,9 +459,7 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
"MINUS_ONE_PAGE_PROGRESS_INDEX",
|
||||
"ALL_APPS_PAGE_PROGRESS_INDEX",
|
||||
"WIDGETS_PAGE_PROGRESS_INDEX",
|
||||
"SYSUI_SURFACE_PROGRESS_INDEX",
|
||||
"LAUNCHER_PAUSE_PROGRESS_INDEX");
|
||||
pw.println(String.format("%s\tmRecentsWindowContainer=%s", prefix, mRecentsViewContainer));
|
||||
"SYSUI_SURFACE_PROGRESS_INDEX");
|
||||
|
||||
mTaskbarLauncherStateController.dumpLogs(prefix + "\t", pw);
|
||||
}
|
||||
@@ -575,60 +468,4 @@ public class LauncherTaskbarUIController extends TaskbarUIController {
|
||||
protected String getTaskbarUIControllerName() {
|
||||
return "LauncherTaskbarUIController";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBubbleBarLocationAnimated(BubbleBarLocation location) {
|
||||
mTaskbarLauncherStateController.onBubbleBarLocationChanged(location, /* animate = */ true);
|
||||
mLauncher.setBubbleBarLocation(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBubbleBarLocationUpdated(BubbleBarLocation location) {
|
||||
mTaskbarLauncherStateController.onBubbleBarLocationChanged(location, /* animate = */ false);
|
||||
mLauncher.setBubbleBarLocation(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSwipeToUnstashTaskbar() {
|
||||
// Once taskbar is unstashed, the user cannot return back to the overlay. We can
|
||||
// clear it here to set the expected state once the user goes home.
|
||||
if (mLauncher.getWorkspace().isOverlayShown()) {
|
||||
mLauncher.getWorkspace().onOverlayScrollChanged(0);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when Launcher Activity resumed while staying at home.
|
||||
* <p>
|
||||
* Shift nav buttons up to at-home position.
|
||||
*/
|
||||
public void onLauncherResume() {
|
||||
mLauncherPauseProgress.animateToValue(0.0f).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when Launcher Activity paused while staying at home.
|
||||
* <p>
|
||||
* To avoid UI clash between taskbar & bottom sheet, shift nav buttons down to in-app position.
|
||||
*/
|
||||
public void onLauncherPause() {
|
||||
mLauncherPauseProgress.animateToValue(1.0f).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* On launcher stop, avoid animating taskbar & overriding pre-existing animations.
|
||||
*/
|
||||
public void onLauncherStop() {
|
||||
mLauncherPauseProgress.cancelAnimation();
|
||||
mLauncherPauseProgress.updateValue(0.0f);
|
||||
}
|
||||
|
||||
private void onLauncherPauseProgressUpdate() {
|
||||
// If we are not aligned with hotseat, setting this will clobber the 3 button nav position.
|
||||
// So in that case, treat the progress as 0 instead.
|
||||
float pauseProgress = isIconAlignedWithHotseat() ? mLauncherPauseProgress.value : 0;
|
||||
onTaskbarInAppDisplayProgressUpdate(pauseProgress, LAUNCHER_PAUSE_PROGRESS_INDEX);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -47,7 +47,6 @@ public class StashedHandleView extends View {
|
||||
private final int[] mTmpArr = new int[2];
|
||||
|
||||
private @Nullable ObjectAnimator mColorChangeAnim;
|
||||
private Boolean mIsRegionDark;
|
||||
|
||||
public StashedHandleView(Context context) {
|
||||
this(context, null);
|
||||
@@ -96,11 +95,7 @@ public class StashedHandleView extends View {
|
||||
* @param animate Whether to animate the change, or apply it immediately.
|
||||
*/
|
||||
public void updateHandleColor(boolean isRegionDark, boolean animate) {
|
||||
if (mIsRegionDark != null && mIsRegionDark == isRegionDark) {
|
||||
return;
|
||||
}
|
||||
int newColor = isRegionDark ? mStashedHandleLightColor : mStashedHandleDarkColor;
|
||||
mIsRegionDark = isRegionDark;
|
||||
if (mColorChangeAnim != null) {
|
||||
mColorChangeAnim.cancel();
|
||||
}
|
||||
|
||||
@@ -36,12 +36,13 @@ import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.anim.RevealOutlineAnimation;
|
||||
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.Executors;
|
||||
import com.android.launcher3.util.MultiPropertyFactory;
|
||||
import com.android.launcher3.util.MultiValueAlpha;
|
||||
import com.android.quickstep.NavHandle;
|
||||
import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
|
||||
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
|
||||
import com.android.wm.shell.shared.handles.RegionSamplingHelper;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
@@ -93,7 +94,6 @@ public class StashedHandleViewController implements TaskbarControllers.LoggableT
|
||||
// States that affect whether region sampling is enabled or not
|
||||
private boolean mIsStashed;
|
||||
private boolean mIsLumaSamplingEnabled;
|
||||
private boolean mIsAppTransitionPending;
|
||||
private boolean mTaskbarHidden;
|
||||
|
||||
private float mTranslationYForSwipe;
|
||||
@@ -118,8 +118,7 @@ public class StashedHandleViewController implements TaskbarControllers.LoggableT
|
||||
mControllers = controllers;
|
||||
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
|
||||
Resources resources = mActivity.getResources();
|
||||
if (mActivity.isPhoneGestureNavMode() || mActivity.isTinyTaskbar()
|
||||
|| mActivity.isBubbleBarOnPhone()) {
|
||||
if (mActivity.isPhoneGestureNavMode() || mActivity.isTinyTaskbar()) {
|
||||
mTaskbarSize = resources.getDimensionPixelSize(R.dimen.taskbar_phone_size);
|
||||
mStashedHandleWidth =
|
||||
resources.getDimensionPixelSize(R.dimen.taskbar_stashed_small_screen);
|
||||
@@ -192,9 +191,7 @@ public class StashedHandleViewController implements TaskbarControllers.LoggableT
|
||||
|
||||
|
||||
public void onDestroy() {
|
||||
if (mRegionSamplingHelper != null) {
|
||||
mRegionSamplingHelper.stopAndDestroy();
|
||||
}
|
||||
mRegionSamplingHelper.stopAndDestroy();
|
||||
mRegionSamplingHelper = null;
|
||||
}
|
||||
|
||||
@@ -212,11 +209,10 @@ public class StashedHandleViewController implements TaskbarControllers.LoggableT
|
||||
* morphs into the size of where the taskbar icons will be.
|
||||
*/
|
||||
public Animator createRevealAnimToIsStashed(boolean isStashed) {
|
||||
Rect visualBounds = mControllers.taskbarViewController
|
||||
.getTransientTaskbarIconLayoutBounds();
|
||||
Rect visualBounds = new Rect(mControllers.taskbarViewController.getIconLayoutBounds());
|
||||
float startRadius = mStashedHandleRadius;
|
||||
|
||||
if (mActivity.isTransientTaskbar()) {
|
||||
if (DisplayController.isTransientTaskbar(mActivity)) {
|
||||
// Account for the full visual height of the transient taskbar.
|
||||
int heightDiff = (mTaskbarSize - visualBounds.height()) / 2;
|
||||
visualBounds.top -= heightDiff;
|
||||
@@ -261,11 +257,6 @@ public class StashedHandleViewController implements TaskbarControllers.LoggableT
|
||||
updateSamplingState();
|
||||
}
|
||||
|
||||
public void setIsAppTransitionPending(boolean pending) {
|
||||
mIsAppTransitionPending = pending;
|
||||
updateSamplingState();
|
||||
}
|
||||
|
||||
private void updateSamplingState() {
|
||||
updateRegionSamplingWindowVisibility();
|
||||
if (shouldSample()) {
|
||||
@@ -277,7 +268,7 @@ public class StashedHandleViewController implements TaskbarControllers.LoggableT
|
||||
}
|
||||
|
||||
private boolean shouldSample() {
|
||||
return mIsStashed && mIsLumaSamplingEnabled && !mIsAppTransitionPending;
|
||||
return mIsStashed && mIsLumaSamplingEnabled;
|
||||
}
|
||||
|
||||
protected void updateStashedHandleHintScale() {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -49,10 +49,6 @@ public class TaskbarAutohideSuspendController implements
|
||||
public static final int FLAG_AUTOHIDE_SUSPEND_TRANSIENT_TASKBAR = 1 << 5;
|
||||
// User has hovered the taskbar.
|
||||
public static final int FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS = 1 << 6;
|
||||
// User has multi instance window open.
|
||||
public static final int FLAG_AUTOHIDE_SUSPEND_MULTI_INSTANCE_MENU_OPEN = 1 << 7;
|
||||
// User has taskbar overflow open.
|
||||
public static final int FLAG_AUTOHIDE_SUSPEND_TASKBAR_OVERFLOW = 1 << 8;
|
||||
|
||||
@IntDef(flag = true, value = {
|
||||
FLAG_AUTOHIDE_SUSPEND_FULLSCREEN,
|
||||
@@ -62,8 +58,6 @@ public class TaskbarAutohideSuspendController implements
|
||||
FLAG_AUTOHIDE_SUSPEND_IN_LAUNCHER,
|
||||
FLAG_AUTOHIDE_SUSPEND_TRANSIENT_TASKBAR,
|
||||
FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS,
|
||||
FLAG_AUTOHIDE_SUSPEND_MULTI_INSTANCE_MENU_OPEN,
|
||||
FLAG_AUTOHIDE_SUSPEND_TASKBAR_OVERFLOW,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface AutohideSuspendFlag {
|
||||
@@ -144,10 +138,6 @@ public class TaskbarAutohideSuspendController implements
|
||||
"FLAG_AUTOHIDE_SUSPEND_IN_LAUNCHER");
|
||||
appendFlag(str, flags, FLAG_AUTOHIDE_SUSPEND_TRANSIENT_TASKBAR,
|
||||
"FLAG_AUTOHIDE_SUSPEND_TRANSIENT_TASKBAR");
|
||||
appendFlag(str, flags, FLAG_AUTOHIDE_SUSPEND_MULTI_INSTANCE_MENU_OPEN,
|
||||
"FLAG_AUTOHIDE_SUSPEND_MULTI_INSTANCE_MENU_OPEN");
|
||||
appendFlag(str, flags, FLAG_AUTOHIDE_SUSPEND_TASKBAR_OVERFLOW,
|
||||
"FLAG_AUTOHIDE_SUSPEND_TASKBAR_OVERFLOW");
|
||||
return str.toString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ import com.android.launcher3.Utilities.mapToRange
|
||||
import com.android.launcher3.icons.GraphicsUtils.setColorAlphaBound
|
||||
import com.android.launcher3.taskbar.TaskbarPinningController.Companion.PINNING_PERSISTENT
|
||||
import com.android.launcher3.taskbar.TaskbarPinningController.Companion.PINNING_TRANSIENT
|
||||
import com.android.launcher3.util.DisplayController
|
||||
import kotlin.math.min
|
||||
|
||||
/** Helps draw the taskbar background, made up of a rectangle plus two inverted rounded corners. */
|
||||
@@ -42,15 +43,13 @@ class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) {
|
||||
private val maxPersistentTaskbarHeight =
|
||||
context.persistentTaskbarDeviceProfile.taskbarHeight.toFloat()
|
||||
var backgroundProgress =
|
||||
if (context.isTransientTaskbar) {
|
||||
if (DisplayController.isTransientTaskbar(context)) {
|
||||
PINNING_TRANSIENT
|
||||
} else {
|
||||
PINNING_PERSISTENT
|
||||
}
|
||||
|
||||
var isAnimatingPinning = false
|
||||
var isAnimatingPersistentTaskbar = false
|
||||
var isAnimatingTransientTaskbar = false
|
||||
|
||||
val paint = Paint()
|
||||
private val strokePaint = Paint()
|
||||
@@ -58,7 +57,6 @@ class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) {
|
||||
var backgroundHeight = context.deviceProfile.taskbarHeight.toFloat()
|
||||
var translationYForSwipe = 0f
|
||||
var translationYForStash = 0f
|
||||
var translationXForBubbleBar = 0f
|
||||
|
||||
private val transientBackgroundBounds = context.transientTaskbarBounds
|
||||
|
||||
@@ -68,8 +66,8 @@ class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) {
|
||||
private var keyShadowDistance = 0f
|
||||
private var bottomMargin = 0
|
||||
|
||||
private val fullCornerRadius: Float
|
||||
private var cornerRadius = 0f
|
||||
private val fullCornerRadius = context.cornerRadius.toFloat()
|
||||
private var cornerRadius = fullCornerRadius
|
||||
private var widthInsetPercentage = 0f
|
||||
private val square = Path()
|
||||
private val circle = Path()
|
||||
@@ -99,17 +97,13 @@ class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) {
|
||||
shadowAlpha = LIGHT_THEME_SHADOW_ALPHA
|
||||
}
|
||||
|
||||
fullCornerRadius = context.cornerRadius.toFloat()
|
||||
cornerRadius = fullCornerRadius
|
||||
if (!context.isInDesktopMode()) {
|
||||
setCornerRoundness(MAX_ROUNDNESS)
|
||||
}
|
||||
setCornerRoundness(DEFAULT_ROUNDNESS)
|
||||
}
|
||||
|
||||
fun updateStashedHandleWidth(context: TaskbarActivityContext, res: Resources) {
|
||||
stashedHandleWidth =
|
||||
res.getDimensionPixelSize(
|
||||
if (context.isPhoneMode || context.isTinyTaskbar || context.isBubbleBarOnPhone) {
|
||||
if (context.isPhoneMode || context.isTinyTaskbar) {
|
||||
R.dimen.taskbar_stashed_small_screen
|
||||
} else {
|
||||
R.dimen.taskbar_stashed_handle_width
|
||||
@@ -123,7 +117,7 @@ class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) {
|
||||
* @param cornerRoundness 0 has no round corner, 1 has complete round corner.
|
||||
*/
|
||||
fun setCornerRoundness(cornerRoundness: Float) {
|
||||
if (context.isTransientTaskbar && !transientBackgroundBounds.isEmpty) {
|
||||
if (DisplayController.isTransientTaskbar(context) && !transientBackgroundBounds.isEmpty) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -145,7 +139,7 @@ class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) {
|
||||
/** Draws the background with the given paint and height, on the provided canvas. */
|
||||
fun draw(canvas: Canvas) {
|
||||
if (isInSetup) return
|
||||
val isTransientTaskbar = context.isTransientTaskbar
|
||||
val isTransientTaskbar = backgroundProgress == 0f
|
||||
canvas.save()
|
||||
if (!isTransientTaskbar || transientBackgroundBounds.isEmpty || isAnimatingPinning) {
|
||||
drawPersistentBackground(canvas)
|
||||
@@ -159,7 +153,7 @@ class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) {
|
||||
}
|
||||
|
||||
private fun drawPersistentBackground(canvas: Canvas) {
|
||||
if (isAnimatingPinning || isAnimatingPersistentTaskbar) {
|
||||
if (isAnimatingPinning) {
|
||||
val persistentTaskbarHeight = maxPersistentTaskbarHeight * backgroundProgress
|
||||
canvas.translate(0f, canvas.height - persistentTaskbarHeight)
|
||||
// Draw the background behind taskbar content.
|
||||
@@ -182,20 +176,19 @@ class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) {
|
||||
private fun drawTransientBackground(canvas: Canvas) {
|
||||
val res = context.resources
|
||||
val transientTaskbarHeight = maxTransientTaskbarHeight * (1f - backgroundProgress)
|
||||
val isAnimating = isAnimatingPinning || isAnimatingTransientTaskbar
|
||||
val heightProgressWhileAnimating =
|
||||
if (isAnimating) transientTaskbarHeight else backgroundHeight
|
||||
if (isAnimatingPinning) transientTaskbarHeight else backgroundHeight
|
||||
|
||||
var progress = heightProgressWhileAnimating / maxTransientTaskbarHeight
|
||||
progress = Math.round(progress * 100f) / 100f
|
||||
if (isAnimating) {
|
||||
if (isAnimatingPinning) {
|
||||
var scale = transientTaskbarHeight / maxTransientTaskbarHeight
|
||||
scale = Math.round(scale * 100f) / 100f
|
||||
bottomMargin =
|
||||
mapRange(
|
||||
scale,
|
||||
0f,
|
||||
res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin).toFloat(),
|
||||
res.getDimensionPixelSize(R.dimen.transient_taskbar_bottom_margin).toFloat()
|
||||
)
|
||||
.toInt()
|
||||
shadowBlur =
|
||||
@@ -234,7 +227,7 @@ class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) {
|
||||
-mapRange(
|
||||
1f - progress,
|
||||
0f,
|
||||
if (isAnimatingPinning) 0f else stashedHandleHeight / 2f,
|
||||
if (isAnimatingPinning) 0f else stashedHandleHeight / 2f
|
||||
)
|
||||
|
||||
// Draw shadow.
|
||||
@@ -244,15 +237,15 @@ class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) {
|
||||
shadowBlur,
|
||||
0f,
|
||||
keyShadowDistance,
|
||||
setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha)),
|
||||
setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
|
||||
)
|
||||
strokePaint.alpha = (paint.alpha * strokeAlpha) / 255
|
||||
val currentTranslationX = translationXForBubbleBar * progress
|
||||
|
||||
lastDrawnTransientRect.set(
|
||||
transientBackgroundBounds.left + halfWidthDelta + currentTranslationX,
|
||||
transientBackgroundBounds.left + halfWidthDelta,
|
||||
bottom - newBackgroundHeight,
|
||||
transientBackgroundBounds.right - halfWidthDelta + currentTranslationX,
|
||||
bottom,
|
||||
transientBackgroundBounds.right - halfWidthDelta,
|
||||
bottom
|
||||
)
|
||||
val horizontalInset = fullWidth * widthInsetPercentage
|
||||
lastDrawnTransientRect.inset(horizontalInset, 0f)
|
||||
@@ -270,7 +263,7 @@ class TaskbarBackgroundRenderer(private val context: TaskbarActivityContext) {
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val MAX_ROUNDNESS = 1f
|
||||
const val DEFAULT_ROUNDNESS = 1f
|
||||
private const val DARK_THEME_STROKE_ALPHA = 51
|
||||
private const val LIGHT_THEME_STROKE_ALPHA = 41
|
||||
private const val DARK_THEME_SHADOW_ALPHA = 51f
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import android.animation.AnimatorSet;
|
||||
import android.content.pm.ActivityInfo.Config;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -25,10 +24,8 @@ import androidx.annotation.VisibleForTesting;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsController;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
|
||||
import com.android.launcher3.taskbar.growth.NudgeController;
|
||||
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
|
||||
import com.android.systemui.shared.rotation.RotationButtonController;
|
||||
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
@@ -67,8 +64,6 @@ public class TaskbarControllers {
|
||||
public final KeyboardQuickSwitchController keyboardQuickSwitchController;
|
||||
public final TaskbarPinningController taskbarPinningController;
|
||||
public final Optional<BubbleControllers> bubbleControllers;
|
||||
public final TaskbarDesktopModeController taskbarDesktopModeController;
|
||||
public final NudgeController nudgeController;
|
||||
|
||||
@Nullable private LoggableTaskbarController[] mControllersToLog = null;
|
||||
@Nullable private BackgroundRendererController[] mBackgroundRendererControllers = null;
|
||||
@@ -116,9 +111,7 @@ public class TaskbarControllers {
|
||||
TaskbarEduTooltipController taskbarEduTooltipController,
|
||||
KeyboardQuickSwitchController keyboardQuickSwitchController,
|
||||
TaskbarPinningController taskbarPinningController,
|
||||
Optional<BubbleControllers> bubbleControllers,
|
||||
TaskbarDesktopModeController taskbarDesktopModeController,
|
||||
NudgeController nudgeController) {
|
||||
Optional<BubbleControllers> bubbleControllers) {
|
||||
this.taskbarActivityContext = taskbarActivityContext;
|
||||
this.taskbarDragController = taskbarDragController;
|
||||
this.navButtonController = navButtonController;
|
||||
@@ -145,8 +138,6 @@ public class TaskbarControllers {
|
||||
this.keyboardQuickSwitchController = keyboardQuickSwitchController;
|
||||
this.taskbarPinningController = taskbarPinningController;
|
||||
this.bubbleControllers = bubbleControllers;
|
||||
this.taskbarDesktopModeController = taskbarDesktopModeController;
|
||||
this.nudgeController = nudgeController;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -154,15 +145,15 @@ public class TaskbarControllers {
|
||||
* TaskbarControllers instance, but should be careful to only access things that were created
|
||||
* in constructors for now, as some controllers may still be waiting for init().
|
||||
*/
|
||||
public void init(@NonNull TaskbarSharedState sharedState, AnimatorSet startAnimation) {
|
||||
public void init(@NonNull TaskbarSharedState sharedState) {
|
||||
mAreAllControllersInitialized = false;
|
||||
mSharedState = sharedState;
|
||||
|
||||
taskbarDragController.init(this);
|
||||
navbarButtonsViewController.init(this);
|
||||
rotationButtonController.init();
|
||||
taskbarDragLayerController.init(this, startAnimation);
|
||||
taskbarViewController.init(this, startAnimation);
|
||||
taskbarDragLayerController.init(this);
|
||||
taskbarViewController.init(this);
|
||||
taskbarScrimViewController.init(this);
|
||||
taskbarUnfoldAnimationController.init(this);
|
||||
taskbarKeyguardController.init(navbarButtonsViewController);
|
||||
@@ -174,7 +165,6 @@ public class TaskbarControllers {
|
||||
taskbarOverlayController.init(this);
|
||||
taskbarAllAppsController.init(this, sharedState.allAppsVisible);
|
||||
navButtonController.init(this);
|
||||
bubbleControllers.ifPresent(controllers -> controllers.init(sharedState, this));
|
||||
taskbarInsetsController.init(this);
|
||||
voiceInteractionWindowController.init(this);
|
||||
taskbarRecentAppsController.init(this);
|
||||
@@ -182,8 +172,7 @@ public class TaskbarControllers {
|
||||
taskbarEduTooltipController.init(this);
|
||||
keyboardQuickSwitchController.init(this);
|
||||
taskbarPinningController.init(this, mSharedState);
|
||||
taskbarDesktopModeController.init(this, mSharedState);
|
||||
nudgeController.init(this);
|
||||
bubbleControllers.ifPresent(controllers -> controllers.init(this));
|
||||
|
||||
mControllersToLog = new LoggableTaskbarController[] {
|
||||
taskbarDragController, navButtonController, navbarButtonsViewController,
|
||||
@@ -194,29 +183,13 @@ public class TaskbarControllers {
|
||||
voiceInteractionWindowController, taskbarRecentAppsController,
|
||||
taskbarTranslationController, taskbarEduTooltipController,
|
||||
keyboardQuickSwitchController, taskbarPinningController,
|
||||
nudgeController
|
||||
};
|
||||
mBackgroundRendererControllers = new BackgroundRendererController[] {
|
||||
taskbarDragLayerController, taskbarScrimViewController,
|
||||
voiceInteractionWindowController
|
||||
};
|
||||
mCornerRoundness.updateValue(TaskbarBackgroundRenderer.DEFAULT_ROUNDNESS);
|
||||
|
||||
// TODO(b/401061748): get primary status from
|
||||
// TaskbarDesktopModeController/DesktopVisibilityController.
|
||||
if (taskbarDesktopModeController.isInDesktopModeAndNotInOverview(
|
||||
taskbarActivityContext.getDisplayId())
|
||||
|| !taskbarActivityContext.isPrimaryDisplay()) {
|
||||
mCornerRoundness.value = taskbarDesktopModeController.getTaskbarCornerRoundness(
|
||||
mSharedState.showCornerRadiusInDesktopMode);
|
||||
} else {
|
||||
mCornerRoundness.value = TaskbarBackgroundRenderer.MAX_ROUNDNESS;
|
||||
}
|
||||
updateCornerRoundness();
|
||||
onPostInit();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void onPostInit() {
|
||||
mAreAllControllersInitialized = true;
|
||||
for (Runnable postInitCallback : mPostInitCallbacks) {
|
||||
postInitCallback.run();
|
||||
@@ -232,17 +205,7 @@ public class TaskbarControllers {
|
||||
uiController = newUiController;
|
||||
uiController.init(this);
|
||||
uiController.updateStateForSysuiFlags(mSharedState.sysuiStateFlags);
|
||||
// if bubble controllers are present configure the UI controller
|
||||
bubbleControllers.ifPresentOrElse(bubbleControllers -> {
|
||||
BubbleBarLocation location =
|
||||
bubbleControllers.bubbleBarViewController.getBubbleBarLocation();
|
||||
boolean hiddenForBubbles =
|
||||
bubbleControllers.bubbleBarViewController.isHiddenForNoBubbles();
|
||||
if (!hiddenForBubbles) {
|
||||
uiController.adjustHotseatForBubbleBar(/* isBubbleBarVisible= */ true);
|
||||
}
|
||||
uiController.onBubbleBarLocationUpdated(location);
|
||||
}, () -> uiController.onBubbleBarLocationUpdated(null));
|
||||
|
||||
// Notify that the ui controller has changed
|
||||
navbarButtonsViewController.onUiControllerChanged();
|
||||
}
|
||||
@@ -266,7 +229,6 @@ public class TaskbarControllers {
|
||||
mAreAllControllersInitialized = false;
|
||||
mSharedState = null;
|
||||
|
||||
taskbarDragController.onDestroy();
|
||||
navbarButtonsViewController.onDestroy();
|
||||
uiController.onDestroy();
|
||||
rotationButtonController.onDestroy();
|
||||
@@ -286,7 +248,7 @@ public class TaskbarControllers {
|
||||
keyboardQuickSwitchController.onDestroy();
|
||||
taskbarStashController.onDestroy();
|
||||
bubbleControllers.ifPresent(controllers -> controllers.onDestroy());
|
||||
taskbarDesktopModeController.onDestroy();
|
||||
|
||||
mControllersToLog = null;
|
||||
mBackgroundRendererControllers = null;
|
||||
}
|
||||
@@ -320,11 +282,6 @@ public class TaskbarControllers {
|
||||
}
|
||||
uiController.dumpLogs(prefix + "\t", pw);
|
||||
rotationButtonController.dumpLogs(prefix + "\t", pw);
|
||||
if (bubbleControllers.isPresent()) {
|
||||
bubbleControllers.get().dump(pw);
|
||||
} else {
|
||||
pw.println(String.format("%s\t%s", prefix, "Bubble controllers are empty."));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -350,7 +307,7 @@ public class TaskbarControllers {
|
||||
return taskbarActivityContext;
|
||||
}
|
||||
|
||||
public interface LoggableTaskbarController {
|
||||
protected interface LoggableTaskbarController {
|
||||
void dumpLogs(String prefix, PrintWriter pw);
|
||||
}
|
||||
|
||||
|
||||
@@ -31,20 +31,21 @@ import android.widget.LinearLayout
|
||||
import android.widget.Switch
|
||||
import androidx.core.view.postDelayed
|
||||
import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
|
||||
import com.android.launcher3.Flags
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.popup.ArrowPopup
|
||||
import com.android.launcher3.popup.RoundedArrowDrawable
|
||||
import com.android.launcher3.util.DisplayController
|
||||
import com.android.launcher3.util.Themes
|
||||
import com.android.launcher3.views.ActivityContext
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
/** Popup view with arrow for taskbar pinning */
|
||||
class TaskbarDividerPopupView<T : TaskbarActivityContext>
|
||||
@JvmOverloads
|
||||
constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
|
||||
ArrowPopup<T>(context, attrs, defStyleAttr) {
|
||||
constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
) : ArrowPopup<T>(context, attrs, defStyleAttr) {
|
||||
companion object {
|
||||
private const val TAG = "TaskbarDividerPopupView"
|
||||
private const val DIVIDER_POPUP_CLOSING_DELAY = 333L
|
||||
@@ -54,33 +55,26 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
fun createAndPopulate(
|
||||
view: View,
|
||||
taskbarActivityContext: TaskbarActivityContext,
|
||||
horizontalPosition: Float,
|
||||
): TaskbarDividerPopupView<*> {
|
||||
val taskMenuViewWithArrow =
|
||||
taskbarActivityContext.layoutInflater.inflate(
|
||||
R.layout.taskbar_divider_popup_menu,
|
||||
taskbarActivityContext.dragLayer,
|
||||
false,
|
||||
false
|
||||
) as TaskbarDividerPopupView<*>
|
||||
|
||||
return taskMenuViewWithArrow.populateForView(view, horizontalPosition)
|
||||
return taskMenuViewWithArrow.populateForView(view)
|
||||
}
|
||||
}
|
||||
|
||||
private lateinit var dividerView: View
|
||||
private var horizontalPosition = 0.0f
|
||||
private val taskbarActivityContext: TaskbarActivityContext =
|
||||
ActivityContext.lookupContext(context)
|
||||
|
||||
private val popupCornerRadius = Themes.getDialogCornerRadius(context)
|
||||
private val arrowWidth = resources.getDimension(R.dimen.popup_arrow_width)
|
||||
private val arrowHeight = resources.getDimension(R.dimen.popup_arrow_height)
|
||||
private val arrowPointRadius = resources.getDimension(R.dimen.popup_arrow_corner_radius)
|
||||
private val minPaddingFromScreenEdge =
|
||||
resources.getDimension(R.dimen.taskbar_pinning_popup_menu_min_padding_from_screen_edge)
|
||||
|
||||
// TODO: add test for isTransientTaskbar & long presses divider and ensures the popup shows up.
|
||||
private var alwaysShowTaskbarOn = !taskbarActivityContext.isTransientTaskbar
|
||||
private var alwaysShowTaskbarOn = !DisplayController.isTransientTaskbar(context)
|
||||
private var didPreferenceChange = false
|
||||
private var verticalOffsetForPopupView =
|
||||
resources.getDimensionPixelSize(R.dimen.taskbar_pinning_popup_menu_vertical_margin)
|
||||
@@ -115,7 +109,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
}
|
||||
alwaysShowTaskbarSwitch.setOnClickListener { view -> (view.parent as View).performClick() }
|
||||
|
||||
if (taskbarActivityContext.isGestureNav) {
|
||||
if (ActivityContext.lookupContext<TaskbarActivityContext>(context).isGestureNav) {
|
||||
taskbarSwitchOption.setOnClickListener {
|
||||
alwaysShowTaskbarSwitch.isChecked = !alwaysShowTaskbarOn
|
||||
onClickAlwaysShowTaskbarSwitchOption()
|
||||
@@ -134,36 +128,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
/** Orient object as usual and then center object horizontally. */
|
||||
override fun orientAboutObject() {
|
||||
super.orientAboutObject()
|
||||
x =
|
||||
if (Flags.showTaskbarPinningPopupFromAnywhere()) {
|
||||
val xForCenterAlignment = horizontalPosition - measuredWidth / 2f
|
||||
val maxX = popupContainer.getWidth() - measuredWidth - minPaddingFromScreenEdge
|
||||
when {
|
||||
// Left-aligned popup and its arrow pointing to the event position if there is
|
||||
// not enough space to center it.
|
||||
xForCenterAlignment < minPaddingFromScreenEdge ->
|
||||
max(
|
||||
minPaddingFromScreenEdge,
|
||||
horizontalPosition - mArrowOffsetHorizontal - mArrowWidth / 2,
|
||||
)
|
||||
|
||||
// Right-aligned popup and its arrow pointing to the event position if there
|
||||
// is not enough space to center it.
|
||||
xForCenterAlignment > maxX ->
|
||||
min(
|
||||
horizontalPosition - measuredWidth +
|
||||
mArrowOffsetHorizontal +
|
||||
mArrowWidth / 2,
|
||||
popupContainer.getWidth() - measuredWidth - minPaddingFromScreenEdge,
|
||||
)
|
||||
|
||||
// Default alignment where the popup and its arrow are centered relative to the
|
||||
// event position.
|
||||
else -> xForCenterAlignment
|
||||
}
|
||||
} else {
|
||||
mTempRect.centerX() - measuredWidth / 2f
|
||||
}
|
||||
x = mTempRect.centerX() - measuredWidth / 2f
|
||||
}
|
||||
|
||||
override fun onControllerInterceptTouchEvent(ev: MotionEvent?): Boolean {
|
||||
@@ -177,9 +142,8 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
return false
|
||||
}
|
||||
|
||||
private fun populateForView(view: View, horizontalPosition: Float): TaskbarDividerPopupView<*> {
|
||||
private fun populateForView(view: View): TaskbarDividerPopupView<*> {
|
||||
dividerView = view
|
||||
this@TaskbarDividerPopupView.horizontalPosition = horizontalPosition
|
||||
tryUpdateBackground()
|
||||
return this
|
||||
}
|
||||
@@ -205,31 +169,12 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
|
||||
override fun addArrow() {
|
||||
super.addArrow()
|
||||
if (Flags.showTaskbarPinningPopupFromAnywhere()) {
|
||||
mArrow.x =
|
||||
min(
|
||||
max(
|
||||
minPaddingFromScreenEdge + mArrowOffsetHorizontal,
|
||||
horizontalPosition - mArrowWidth / 2,
|
||||
),
|
||||
popupContainer.getWidth() -
|
||||
minPaddingFromScreenEdge -
|
||||
mArrowOffsetHorizontal -
|
||||
mArrowWidth,
|
||||
)
|
||||
} else {
|
||||
val location = IntArray(2)
|
||||
popupContainer.getLocationInDragLayer(dividerView, location)
|
||||
val dividerViewX = location[0].toFloat()
|
||||
// Change arrow location to the middle of popup.
|
||||
mArrow.x = (dividerViewX + dividerView.width / 2) - (mArrowWidth / 2)
|
||||
}
|
||||
// Change arrow location to the middle of popup.
|
||||
mArrow.x = (dividerView.x + dividerView.width / 2) - (mArrowWidth / 2)
|
||||
}
|
||||
|
||||
override fun updateArrowColor() {
|
||||
if (Flags.showTaskbarPinningPopupFromAnywhere()) {
|
||||
super.updateArrowColor()
|
||||
} else if (!Gravity.isVertical(mGravity)) {
|
||||
if (!Gravity.isVertical(mGravity)) {
|
||||
mArrow.background =
|
||||
RoundedArrowDrawable(
|
||||
arrowWidth,
|
||||
@@ -265,7 +210,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
|
||||
/** Aligning the view pivot to center for animation. */
|
||||
override fun setPivotForOpenCloseAnimation() {
|
||||
pivotX = mArrow.x + mArrowWidth / 2 - x
|
||||
pivotX = measuredWidth / 2f
|
||||
pivotY = measuredHeight.toFloat()
|
||||
}
|
||||
|
||||
@@ -279,13 +224,13 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
ObjectAnimator.ofFloat(
|
||||
this,
|
||||
TRANSLATION_Y,
|
||||
*floatArrayOf(this.translationY, this.translationY + translateYValue),
|
||||
*floatArrayOf(this.translationY, this.translationY + translateYValue)
|
||||
)
|
||||
val arrowTranslateY =
|
||||
ObjectAnimator.ofFloat(
|
||||
mArrow,
|
||||
TRANSLATION_Y,
|
||||
*floatArrayOf(mArrow.translationY, mArrow.translationY + translateYValue),
|
||||
*floatArrayOf(mArrow.translationY, mArrow.translationY + translateYValue)
|
||||
)
|
||||
val animatorSet = AnimatorSet()
|
||||
animatorSet.playTogether(alpha, arrowAlpha, translateY, arrowTranslateY)
|
||||
@@ -295,7 +240,7 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
private fun getAnimatorOfFloat(
|
||||
view: View,
|
||||
property: Property<View, Float>,
|
||||
vararg values: Float,
|
||||
vararg values: Float
|
||||
): Animator {
|
||||
val animator: Animator = ObjectAnimator.ofFloat(view, property, *values)
|
||||
animator.setDuration(DIVIDER_POPUP_CLOSING_ANIMATION_DURATION)
|
||||
|
||||
@@ -34,7 +34,6 @@ import android.content.ClipData;
|
||||
import android.content.ClipDescription;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Point;
|
||||
@@ -52,7 +51,6 @@ import android.view.ViewRootImpl;
|
||||
import android.window.SurfaceSyncGroup;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.app.animation.Interpolators;
|
||||
import com.android.internal.logging.InstanceId;
|
||||
@@ -68,7 +66,6 @@ import com.android.launcher3.dragndrop.DragDriver;
|
||||
import com.android.launcher3.dragndrop.DragOptions;
|
||||
import com.android.launcher3.dragndrop.DragView;
|
||||
import com.android.launcher3.dragndrop.DraggableView;
|
||||
import com.android.launcher3.folder.Folder;
|
||||
import com.android.launcher3.graphics.DragPreviewProvider;
|
||||
import com.android.launcher3.logger.LauncherAtom.ContainerInfo;
|
||||
import com.android.launcher3.logging.StatsLogManager;
|
||||
@@ -77,18 +74,18 @@ import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.popup.PopupContainerWithArrow;
|
||||
import com.android.launcher3.shortcuts.DeepShortcutView;
|
||||
import com.android.launcher3.shortcuts.ShortcutDragPreviewProvider;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
|
||||
import com.android.launcher3.statehandlers.DesktopVisibilityController;
|
||||
import com.android.launcher3.testing.TestLogging;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.IntSet;
|
||||
import com.android.launcher3.util.ItemInfoMatcher;
|
||||
import com.android.launcher3.views.BubbleTextHolder;
|
||||
import com.android.quickstep.LauncherActivityInterface;
|
||||
import com.android.quickstep.util.LogUtils;
|
||||
import com.android.quickstep.util.MultiValueUpdateListener;
|
||||
import com.android.quickstep.util.SingleTask;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
|
||||
import com.android.wm.shell.shared.draganddrop.DragAndDropConstants;
|
||||
import com.android.wm.shell.draganddrop.DragAndDropConstants;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Arrays;
|
||||
@@ -96,7 +93,8 @@ import java.util.Collections;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Handles long click on Taskbar items to start a system drag and drop operation.
|
||||
* Handles long click on Taskbar items to start a system drag and drop
|
||||
* operation.
|
||||
*/
|
||||
public class TaskbarDragController extends DragController<BaseTaskbarContext> implements
|
||||
TaskbarControllers.LoggableTaskbarController {
|
||||
@@ -116,7 +114,6 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
private int mRegistrationY;
|
||||
|
||||
private boolean mIsSystemDragInProgress;
|
||||
private boolean mIsDropHandledByDropTarget;
|
||||
|
||||
// Animation for the drag shadow back into position after an unsuccessful drag
|
||||
private ValueAnimator mReturnAnimator;
|
||||
@@ -131,14 +128,6 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
|
||||
public void init(TaskbarControllers controllers) {
|
||||
mControllers = controllers;
|
||||
mControllers.bubbleControllers.ifPresent(
|
||||
c -> c.bubbleBarViewController.addBubbleBarDropTargets(this));
|
||||
}
|
||||
|
||||
/** Called when the controller is destroyed. */
|
||||
public void onDestroy() {
|
||||
mControllers.bubbleControllers.ifPresent(
|
||||
c -> c.bubbleBarViewController.removeBubbleBarDropTargets(this));
|
||||
}
|
||||
|
||||
public void setDisallowGlobalDrag(boolean disallowGlobalDrag) {
|
||||
@@ -150,8 +139,10 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to start a system drag and drop operation for the given View, using its tag to
|
||||
* Attempts to start a system drag and drop operation for the given View, using
|
||||
* its tag to
|
||||
* generate the ClipDescription and Intent.
|
||||
*
|
||||
* @return Whether {@link View#startDragAndDrop} started successfully.
|
||||
*/
|
||||
public boolean startDragOnLongClick(View view) {
|
||||
@@ -193,9 +184,7 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
|
||||
private DragView startInternalDrag(
|
||||
BubbleTextView btv, @Nullable DragPreviewProvider dragPreviewProvider) {
|
||||
// TODO(b/344038728): null check is only necessary because Recents doesn't use
|
||||
// FastBitmapDrawable
|
||||
float iconScale = btv.getIcon() == null ? 1f : btv.getIcon().getAnimatedScale();
|
||||
float iconScale = btv.getIcon().getAnimatedScale();
|
||||
|
||||
// Clear the pressed state if necessary
|
||||
btv.clearFocus();
|
||||
@@ -203,7 +192,8 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
btv.clearPressedBackground();
|
||||
|
||||
final DragPreviewProvider previewProvider = dragPreviewProvider == null
|
||||
? new DragPreviewProvider(btv) : dragPreviewProvider;
|
||||
? new DragPreviewProvider(btv)
|
||||
: dragPreviewProvider;
|
||||
final Drawable drawable = previewProvider.createDrawable();
|
||||
final float scale = previewProvider.getScaleAndPosition(drawable, mTempXY);
|
||||
int dragLayerX = mTempXY[0];
|
||||
@@ -215,13 +205,12 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
|
||||
DragOptions dragOptions = new DragOptions();
|
||||
// First, see if view is a search result that needs custom pre-drag conditions.
|
||||
dragOptions.preDragCondition =
|
||||
mControllers.taskbarAllAppsController.createPreDragConditionForSearch(btv);
|
||||
dragOptions.preDragCondition = mControllers.taskbarAllAppsController.createPreDragConditionForSearch(btv);
|
||||
|
||||
if (dragOptions.preDragCondition == null) {
|
||||
// See if view supports a popup container.
|
||||
PopupContainerWithArrow<BaseTaskbarContext> popupContainer =
|
||||
mControllers.taskbarPopupController.showForIcon(btv);
|
||||
PopupContainerWithArrow<BaseTaskbarContext> popupContainer = mControllers.taskbarPopupController
|
||||
.showForIcon(btv);
|
||||
if (popupContainer != null) {
|
||||
dragOptions.preDragCondition = popupContainer.createPreDragCondition(false);
|
||||
}
|
||||
@@ -261,9 +250,9 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
/* originalView = */ btv,
|
||||
dragLayerX + dragOffset.x,
|
||||
dragLayerY + dragOffset.y,
|
||||
(View target, DropTarget.DragObject d, boolean success) ->
|
||||
mIsDropHandledByDropTarget = success /* DragSource */,
|
||||
btv.getTag() instanceof ItemInfo itemInfo ? itemInfo : null,
|
||||
(View target, DropTarget.DragObject d, boolean success) -> {
|
||||
} /* DragSource */,
|
||||
(ItemInfo) btv.getTag(),
|
||||
dragRect,
|
||||
scale * iconScale,
|
||||
scale,
|
||||
@@ -303,23 +292,21 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
initialDragViewScale,
|
||||
dragViewScaleOnDrop,
|
||||
scalePx);
|
||||
if (dragInfo != null) {
|
||||
dragView.setItemInfo(dragInfo);
|
||||
}
|
||||
dragView.setItemInfo(dragInfo);
|
||||
mDragObject.dragComplete = false;
|
||||
|
||||
mDragObject.xOffset = mMotionDown.x - (dragLayerX + dragRegionLeft);
|
||||
mDragObject.yOffset = mMotionDown.y - (dragLayerY + dragRegionTop);
|
||||
|
||||
mDragDriver = DragDriver.create(this, mOptions, /* secondaryEventConsumer = */ ev -> {});
|
||||
mDragDriver = DragDriver.create(this, mOptions, /* secondaryEventConsumer = */ ev -> {
|
||||
});
|
||||
if (!mOptions.isAccessibleDrag) {
|
||||
mDragObject.stateAnnouncer = DragViewStateAnnouncer.createFor(dragView);
|
||||
}
|
||||
|
||||
mDragObject.dragSource = source;
|
||||
mDragObject.dragInfo = dragInfo;
|
||||
mDragObject.originalDragInfo =
|
||||
mDragObject.dragInfo != null ? mDragObject.dragInfo.makeShallowCopy() : null;
|
||||
mDragObject.originalDragInfo = mDragObject.dragInfo.makeShallowCopy();
|
||||
|
||||
if (mOptions.preDragCondition != null) {
|
||||
dragView.setHasDragOffset(mOptions.preDragCondition.getDragOffset().x != 0
|
||||
@@ -346,7 +333,8 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
|
||||
/** Invoked when an animation running as part of pre-drag finishes. */
|
||||
public void onPreDragAnimationEnd() {
|
||||
// Drag might be cancelled during the DragView animation, so check mIsPreDrag again.
|
||||
// Drag might be cancelled during the DragView animation, so check mIsPreDrag
|
||||
// again.
|
||||
if (mIsInPreDrag) {
|
||||
callOnDragStart();
|
||||
}
|
||||
@@ -356,10 +344,12 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
protected void callOnDragStart() {
|
||||
super.callOnDragStart();
|
||||
// TODO(297921594) clean it up when taskbar to desktop drag is implemented.
|
||||
DesktopVisibilityController desktopController = LauncherActivityInterface.INSTANCE
|
||||
.getDesktopVisibilityController();
|
||||
|
||||
// Pre-drag has ended, start the global system drag.
|
||||
if (mDisallowGlobalDrag
|
||||
|| mControllers.taskbarDesktopModeController
|
||||
.isInDesktopModeAndNotInOverview(mActivity.getDisplayId())) {
|
||||
if (mDisallowGlobalDrag || (desktopController != null
|
||||
&& desktopController.areDesktopTasksVisible())) {
|
||||
AbstractFloatingView.closeAllOpenViewsExcept(mActivity, TYPE_TASKBAR_ALL_APPS);
|
||||
return;
|
||||
}
|
||||
@@ -430,12 +420,9 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
item.user));
|
||||
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, item.getIntent().getPackage());
|
||||
intent.putExtra(Intent.EXTRA_SHORTCUT_ID, deepShortcutId);
|
||||
ShortcutInfo shortcutInfo = ((WorkspaceItemInfo) item).getDeepShortcutInfo();
|
||||
if (BubbleAnythingFlagHelper.enableCreateAnyBubble() && shortcutInfo != null) {
|
||||
intent.putExtra(DragAndDropConstants.EXTRA_SHORTCUT_INFO, shortcutInfo);
|
||||
}
|
||||
} else if (item.itemType == ITEM_TYPE_SEARCH_ACTION) {
|
||||
// TODO(b/289261756): Buggy behavior when split opposite to an existing search pane.
|
||||
// TODO(b/289261756): Buggy behavior when split opposite to an existing search
|
||||
// pane.
|
||||
intent.putExtra(
|
||||
ClipDescription.EXTRA_PENDING_INTENT,
|
||||
PendingIntent.getActivityAsUser(
|
||||
@@ -451,8 +438,8 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
null, item.user));
|
||||
}
|
||||
intent.putExtra(Intent.EXTRA_USER, item.user);
|
||||
} else if (tag instanceof SingleTask singleTask) {
|
||||
Task task = singleTask.getTask();
|
||||
} else if (tag instanceof Task) {
|
||||
Task task = (Task) tag;
|
||||
clipDescription = new ClipDescription(task.titleDescription,
|
||||
new String[] {
|
||||
ClipDescription.MIMETYPE_APPLICATION_TASK
|
||||
@@ -463,14 +450,14 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
}
|
||||
|
||||
if (clipDescription != null && intent != null) {
|
||||
Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds =
|
||||
LogUtils.getShellShareableInstanceId();
|
||||
Pair<InstanceId, com.android.launcher3.logging.InstanceId> instanceIds = LogUtils
|
||||
.getShellShareableInstanceId();
|
||||
// Need to share the same InstanceId between launcher3 and WM Shell (internal).
|
||||
InstanceId internalInstanceId = instanceIds.first;
|
||||
com.android.launcher3.logging.InstanceId launcherInstanceId = instanceIds.second;
|
||||
|
||||
intent.putExtra(ClipDescription.EXTRA_LOGGING_INSTANCE_ID, internalInstanceId);
|
||||
if (mActivity.isTransientTaskbar()) {
|
||||
if (DisplayController.isTransientTaskbar(mActivity)) {
|
||||
// Tell WM Shell to ignore drag events in the provided transient taskbar region.
|
||||
TaskbarDragLayer dragLayer = mControllers.taskbarActivityContext.getDragLayer();
|
||||
int[] locationOnScreen = dragLayer.getLocationOnScreen();
|
||||
@@ -510,8 +497,6 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
} else {
|
||||
// This will take care of calling maybeOnDragEnd() after the animation
|
||||
animateGlobalDragViewToOriginalPosition(btv, dragEvent);
|
||||
//TODO(b/399678274): hide drop target in shell
|
||||
notifyBubbleBarItemDragCanceled();
|
||||
}
|
||||
mActivity.getDragLayer().setOnDragListener(null);
|
||||
|
||||
@@ -531,61 +516,39 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
return mIsSystemDragInProgress;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
private void maybeOnDragEnd() {
|
||||
if (!isDragging()) {
|
||||
((BubbleTextView) mDragObject.originalView).setIconDisabled(false);
|
||||
mControllers.taskbarAutohideSuspendController.updateFlag(
|
||||
TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_DRAGGING, false);
|
||||
mActivity.onDragEnd();
|
||||
// If an item is dropped on the bubble bar, the bubble bar handles the drop,
|
||||
// so it should not collapse along with the taskbar.
|
||||
boolean droppedOnBubbleBar = notifyBubbleBarItemDropped();
|
||||
if (mReturnAnimator == null) {
|
||||
// Upon successful drag, immediately stash taskbar.
|
||||
// Note, this must be done last to ensure no AutohideSuspendFlags are active, as
|
||||
// that will prevent us from stashing until the timeout.
|
||||
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(
|
||||
/* stash = */ true,
|
||||
/* shouldBubblesFollow = */ !droppedOnBubbleBar
|
||||
);
|
||||
mControllers.taskbarStashController.updateAndAnimateTransientTaskbar(true);
|
||||
|
||||
mActivity.getStatsLogManager().logger().withItemInfo(mDragObject.dragInfo)
|
||||
.log(LAUNCHER_APP_LAUNCH_DRAGDROP);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exits the Bubble Bar drop target mode if applicable.
|
||||
*
|
||||
* @return {@code true} if drop target mode was active.
|
||||
*/
|
||||
private boolean notifyBubbleBarItemDropped() {
|
||||
return mControllers.bubbleControllers.map(bc -> {
|
||||
BubbleBarViewController bubbleBarViewController = bc.bubbleBarViewController;
|
||||
boolean showingDropTarget = bubbleBarViewController.isShowingDropTarget();
|
||||
if (showingDropTarget) {
|
||||
bubbleBarViewController.onItemDragCompleted();
|
||||
}
|
||||
return showingDropTarget;
|
||||
}).orElse(false);
|
||||
}
|
||||
|
||||
private void notifyBubbleBarItemDragCanceled() {
|
||||
mControllers.bubbleControllers.ifPresent(bc ->
|
||||
bc.bubbleBarViewController.onItemDraggedOutsideBubbleBarDropZone());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void endDrag() {
|
||||
if (mDisallowGlobalDrag && !mIsDropHandledByDropTarget) {
|
||||
// We need to explicitly set deferDragViewCleanupPostAnimation to true here so the
|
||||
// super call doesn't remove it from the drag layer before the animation completes.
|
||||
if (mDisallowGlobalDrag) {
|
||||
// We need to explicitly set deferDragViewCleanupPostAnimation to true here so
|
||||
// the
|
||||
// super call doesn't remove it from the drag layer before the animation
|
||||
// completes.
|
||||
// This variable gets set in to false in super.dispatchDropComplete() because it
|
||||
// (rightfully so, perhaps) thinks this drag operation has failed, and does its own
|
||||
// (rightfully so, perhaps) thinks this drag operation has failed, and does its
|
||||
// own
|
||||
// internal cleanup.
|
||||
// Another way to approach this would be to make all of overview a drop target and
|
||||
// accept the drop as successful and then run the setupReturnDragAnimator to simulate
|
||||
// Another way to approach this would be to make all of overview a drop target
|
||||
// and
|
||||
// accept the drop as successful and then run the setupReturnDragAnimator to
|
||||
// simulate
|
||||
// drop failure to the user
|
||||
mDragObject.deferDragViewCleanupPostAnimation = true;
|
||||
|
||||
@@ -670,7 +633,8 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
syncGroup.add(viewRoot, null /* runnable */);
|
||||
syncGroup.addTransaction(transaction);
|
||||
syncGroup.markSyncReady();
|
||||
// Do this after maybeOnDragEnd(), because we use mReturnAnimator != null to imply
|
||||
// Do this after maybeOnDragEnd(), because we use mReturnAnimator != null to
|
||||
// imply
|
||||
// the drag was canceled rather than successful.
|
||||
mReturnAnimator = null;
|
||||
}
|
||||
@@ -691,7 +655,8 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
// We're dragging in taskbarAllApps, we don't have folders or shortcuts
|
||||
return iconView;
|
||||
}
|
||||
// Since all apps closes when the drag starts, target the all apps button instead.
|
||||
// Since all apps closes when the drag starts, target the all apps button
|
||||
// instead.
|
||||
return taskbarViewController.getAllAppsButtonView();
|
||||
} else if (item.container >= 0) {
|
||||
// Since folders close when the drag starts, target the folder icon instead.
|
||||
@@ -711,8 +676,7 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
private static boolean isInSearchResultContainer(ItemInfo item) {
|
||||
ContainerInfo containerInfo = item.getContainerInfo();
|
||||
return containerInfo.getContainerCase() == EXTENDED_CONTAINERS
|
||||
&& containerInfo.getExtendedContainers().getContainerCase()
|
||||
== DEVICE_SEARCH_RESULT_CONTAINER;
|
||||
&& containerInfo.getExtendedContainers().getContainerCase() == DEVICE_SEARCH_RESULT_CONTAINER;
|
||||
}
|
||||
|
||||
private void setupReturnDragAnimator(float fromX, float fromY, View originalView,
|
||||
@@ -741,6 +705,7 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
final FloatProp mDy = new FloatProp(fromY, toPosition[1], FAST_OUT_SLOW_IN);
|
||||
final FloatProp mScale = new FloatProp(1f, toScale, FAST_OUT_SLOW_IN);
|
||||
final FloatProp mAlpha = new FloatProp(1f, toAlpha, Interpolators.ACCELERATE_2);
|
||||
|
||||
@Override
|
||||
public void onUpdate(float percent, boolean initOnly) {
|
||||
animListener.updateDragShadow(mDx.value, mDy.value, mScale.value, mAlpha.value);
|
||||
@@ -754,15 +719,19 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
|
||||
@Override
|
||||
protected float getX(MotionEvent ev) {
|
||||
// We will resize to fill the screen while dragging, so use screen coordinates. This ensures
|
||||
// we start at the correct position even though touch down is on the smaller DragLayer size.
|
||||
// We will resize to fill the screen while dragging, so use screen coordinates.
|
||||
// This ensures
|
||||
// we start at the correct position even though touch down is on the smaller
|
||||
// DragLayer size.
|
||||
return ev.getRawX();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected float getY(MotionEvent ev) {
|
||||
// We will resize to fill the screen while dragging, so use screen coordinates. This ensures
|
||||
// we start at the correct position even though touch down is on the smaller DragLayer size.
|
||||
// We will resize to fill the screen while dragging, so use screen coordinates.
|
||||
// This ensures
|
||||
// we start at the correct position even though touch down is on the smaller
|
||||
// DragLayer size.
|
||||
return ev.getRawY();
|
||||
}
|
||||
|
||||
@@ -782,11 +751,8 @@ public class TaskbarDragController extends DragController<BaseTaskbarContext> im
|
||||
|
||||
@Override
|
||||
public void addDropTarget(DropTarget target) {
|
||||
if (target instanceof Folder) {
|
||||
// we need to ignore Folder.
|
||||
return;
|
||||
}
|
||||
super.addDropTarget(target);
|
||||
// No-op as Taskbar currently doesn't support any drop targets internally.
|
||||
// Note: if we do add internal DropTargets, we'll still need to ignore Folder.
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -18,6 +18,7 @@ package com.android.launcher3.taskbar;
|
||||
import static android.view.KeyEvent.ACTION_UP;
|
||||
import static android.view.KeyEvent.KEYCODE_BACK;
|
||||
|
||||
import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
|
||||
|
||||
import android.content.Context;
|
||||
@@ -42,6 +43,7 @@ import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.testing.TestLogging;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.MultiPropertyFactory;
|
||||
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
|
||||
import com.android.launcher3.views.BaseDragLayer;
|
||||
@@ -100,16 +102,20 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
|
||||
public TaskbarDragLayer(@NonNull Context context, @Nullable AttributeSet attrs,
|
||||
int defStyleAttr, int defStyleRes) {
|
||||
super(context, attrs, 1 /* alphaChannelCount */);
|
||||
mBackgroundRenderer = new TaskbarBackgroundRenderer(mContainer);
|
||||
mBackgroundRenderer = new TaskbarBackgroundRenderer(mActivity);
|
||||
|
||||
mTaskbarBackgroundAlpha = new MultiPropertyFactory<>(this, BG_ALPHA, INDEX_COUNT,
|
||||
(a, b) -> a * b, 1f);
|
||||
mTaskbarBackgroundAlpha.get(INDEX_ALL_OTHER_STATES).setValue(0);
|
||||
mTaskbarBackgroundAlpha.get(INDEX_STASH_ANIM).setValue(
|
||||
enableScalingRevealHomeAnimation() && DisplayController.isTransientTaskbar(context)
|
||||
? 0
|
||||
: 1);
|
||||
}
|
||||
|
||||
public void init(TaskbarDragLayerController.TaskbarDragLayerCallbacks callbacks) {
|
||||
mControllerCallbacks = callbacks;
|
||||
mBackgroundRenderer.updateStashedHandleWidth(mContainer, getResources());
|
||||
mBackgroundRenderer.updateStashedHandleWidth(mActivity, getResources());
|
||||
recreateControllers();
|
||||
}
|
||||
|
||||
@@ -191,7 +197,6 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
|
||||
|
||||
@Override
|
||||
protected void dispatchDraw(Canvas canvas) {
|
||||
if (mContainer.isDestroyed()) return;
|
||||
float backgroundHeight = mControllerCallbacks.getTaskbarBackgroundHeight()
|
||||
* (1f - mTaskbarBackgroundOffset);
|
||||
mBackgroundRenderer.setBackgroundHeight(backgroundHeight);
|
||||
@@ -260,11 +265,6 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
|
||||
invalidate();
|
||||
}
|
||||
|
||||
protected void setBackgroundTranslationXForBubbleBar(float translationX) {
|
||||
mBackgroundRenderer.setTranslationXForBubbleBar(translationX);
|
||||
invalidate();
|
||||
}
|
||||
|
||||
/** Returns the bounds in DragLayer coordinates of where the transient background was drawn. */
|
||||
protected RectF getLastDrawnTransientRect() {
|
||||
return mBackgroundRenderer.getLastDrawnTransientRect();
|
||||
@@ -273,7 +273,6 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(MotionEvent ev) {
|
||||
TestLogging.recordMotionEvent(TestProtocol.SEQUENCE_MAIN, "Touch event", ev);
|
||||
mControllerCallbacks.onDispatchTouchEvent(ev);
|
||||
return super.dispatchTouchEvent(ev);
|
||||
}
|
||||
|
||||
@@ -281,7 +280,7 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK) {
|
||||
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mContainer);
|
||||
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
|
||||
if (topView != null && topView.canHandleBack()) {
|
||||
topView.onBackInvoked();
|
||||
// Handled by the floating view.
|
||||
@@ -291,21 +290,6 @@ public class TaskbarDragLayer extends BaseDragLayer<TaskbarActivityContext> {
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets animation boolean when only animating persistent taskbar.
|
||||
*/
|
||||
public void setIsAnimatingPersistentTaskbarBackground(boolean animatingPersistentTaskbarBg) {
|
||||
mBackgroundRenderer.setAnimatingPersistentTaskbar(animatingPersistentTaskbarBg);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets animation boolean when only animating transient taskbar.
|
||||
*/
|
||||
public void setIsAnimatingTransientTaskbarBackground(boolean animatingTransientTaskbarBg) {
|
||||
mBackgroundRenderer.setAnimatingTransientTaskbar(animatingTransientTaskbarBg);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sets the width percentage to inset the transient taskbar's background from the left and from
|
||||
* the right.
|
||||
|
||||
@@ -15,25 +15,21 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static com.android.app.animation.Interpolators.EMPHASIZED;
|
||||
import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_PERSISTENT;
|
||||
import static com.android.launcher3.taskbar.TaskbarPinningController.PINNING_TRANSIENT;
|
||||
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.Rect;
|
||||
import android.os.SystemProperties;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.ViewTreeObserver;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.anim.AnimatorListeners;
|
||||
import com.android.launcher3.util.DimensionUtils;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
|
||||
@@ -61,8 +57,6 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
|
||||
private final AnimatedFloat mImeBgTaskbar = new AnimatedFloat(this::updateBackgroundAlpha);
|
||||
private final AnimatedFloat mAssistantBgTaskbar = new AnimatedFloat(
|
||||
this::updateBackgroundAlpha);
|
||||
private final AnimatedFloat mBgTaskbarRecreate = new AnimatedFloat(
|
||||
this::updateBackgroundAlpha);
|
||||
// Used to hide our background color when someone else (e.g. ScrimView) is handling it.
|
||||
private final AnimatedFloat mBgOverride = new AnimatedFloat(this::updateBackgroundAlpha);
|
||||
|
||||
@@ -93,10 +87,7 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
|
||||
mFolderMargin = resources.getDimensionPixelSize(R.dimen.taskbar_folder_margin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Init of taskbar drag layer controller
|
||||
*/
|
||||
public void init(TaskbarControllers controllers, AnimatorSet startAnimation) {
|
||||
public void init(TaskbarControllers controllers) {
|
||||
mControllers = controllers;
|
||||
mTaskbarStashViaTouchController = new TaskbarStashViaTouchController(mControllers);
|
||||
mTaskbarDragLayer.init(new TaskbarDragLayerCallbacks());
|
||||
@@ -104,43 +95,15 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
|
||||
mOnBackgroundNavButtonColorIntensity = mControllers.navbarButtonsViewController
|
||||
.getOnTaskbarBackgroundNavButtonColorOverride();
|
||||
|
||||
|
||||
if (startAnimation != null) {
|
||||
// set taskbar background render animation boolean
|
||||
if (mActivity.isTransientTaskbar()) {
|
||||
mTaskbarDragLayer.setIsAnimatingTransientTaskbarBackground(true);
|
||||
} else {
|
||||
mTaskbarDragLayer.setIsAnimatingPersistentTaskbarBackground(true);
|
||||
}
|
||||
|
||||
float desiredValue = mActivity.isTransientTaskbar()
|
||||
? PINNING_TRANSIENT
|
||||
: PINNING_PERSISTENT;
|
||||
|
||||
float nonDesiredvalue =
|
||||
!mActivity.isTransientTaskbar() ? PINNING_TRANSIENT : PINNING_PERSISTENT;
|
||||
|
||||
ObjectAnimator objectAnimator = mTaskbarBackgroundProgress.animateToValue(
|
||||
nonDesiredvalue, desiredValue);
|
||||
objectAnimator.setInterpolator(EMPHASIZED);
|
||||
startAnimation.play(objectAnimator);
|
||||
startAnimation.addListener(AnimatorListeners.forEndCallback(()-> {
|
||||
// reset taskbar background render animation boolean
|
||||
mTaskbarDragLayer.setIsAnimatingPersistentTaskbarBackground(false);
|
||||
mTaskbarDragLayer.setIsAnimatingTransientTaskbarBackground(false);
|
||||
}));
|
||||
|
||||
} else {
|
||||
mTaskbarBackgroundProgress.updateValue(
|
||||
mActivity.isTransientTaskbar() ? PINNING_TRANSIENT : PINNING_PERSISTENT);
|
||||
}
|
||||
mTaskbarBackgroundProgress.updateValue(DisplayController.isTransientTaskbar(mActivity)
|
||||
? PINNING_TRANSIENT
|
||||
: PINNING_PERSISTENT);
|
||||
|
||||
mBgTaskbar.value = 1;
|
||||
mKeyguardBgTaskbar.value = 1;
|
||||
mNotificationShadeBgTaskbar.value = 1;
|
||||
mImeBgTaskbar.value = 1;
|
||||
mAssistantBgTaskbar.value = 1;
|
||||
mBgTaskbarRecreate.value = 1;
|
||||
mBgOverride.value = 1;
|
||||
updateBackgroundAlpha();
|
||||
|
||||
@@ -148,13 +111,6 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
|
||||
updateTaskbarAlpha();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when destroying Taskbar with animation.
|
||||
*/
|
||||
public void onDestroyAnimation(AnimatorSet animatorSet) {
|
||||
animatorSet.play(mBgTaskbarRecreate.animateToValue(0f));
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
mTaskbarDragLayer.onDestroy();
|
||||
}
|
||||
@@ -215,14 +171,10 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
|
||||
}
|
||||
|
||||
private void updateBackgroundAlpha() {
|
||||
if (mActivity.isPhoneMode() || mActivity.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
|
||||
final float bgNavbar = mBgNavbar.value;
|
||||
final float bgTaskbar = mBgTaskbar.value * mKeyguardBgTaskbar.value
|
||||
* mNotificationShadeBgTaskbar.value * mImeBgTaskbar.value
|
||||
* mAssistantBgTaskbar.value * mBgTaskbarRecreate.value;
|
||||
* mAssistantBgTaskbar.value;
|
||||
mLastSetBackgroundAlpha = mBgOverride.value * Math.max(bgNavbar, bgTaskbar);
|
||||
mBackgroundRendererAlpha.setValue(mLastSetBackgroundAlpha);
|
||||
|
||||
@@ -240,13 +192,6 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
|
||||
mTaskbarDragLayer.setBackgroundTranslationYForSwipe(transY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the translation of the background for the bubble bar.
|
||||
*/
|
||||
public void setTranslationXForBubbleBar(float transX) {
|
||||
mTaskbarDragLayer.setBackgroundTranslationXForBubbleBar(transX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the translation of the background during the spring on stash animation.
|
||||
*/
|
||||
@@ -309,7 +254,6 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
|
||||
pw.println(prefix + "\t\tmNotificationShadeBgTaskbar=" + mNotificationShadeBgTaskbar.value);
|
||||
pw.println(prefix + "\t\tmImeBgTaskbar=" + mImeBgTaskbar.value);
|
||||
pw.println(prefix + "\t\tmAssistantBgTaskbar=" + mAssistantBgTaskbar.value);
|
||||
pw.println(prefix + "\t\tmBgTaskbarRecreate=" + mBgTaskbarRecreate.value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -347,7 +291,7 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
|
||||
if (mActivity.isPhoneMode()) {
|
||||
Resources resources = mActivity.getResources();
|
||||
Point taskbarDimensions = DimensionUtils.getTaskbarPhoneDimensions(deviceProfile,
|
||||
resources, true /* isPhoneMode */, mActivity.isGestureNav());
|
||||
resources, true /* isPhoneMode */);
|
||||
return taskbarDimensions.y == -1 ?
|
||||
deviceProfile.getDisplayInfo().currentSize.y :
|
||||
taskbarDimensions.y;
|
||||
@@ -377,15 +321,5 @@ public class TaskbarDragLayerController implements TaskbarControllers.LoggableTa
|
||||
}
|
||||
mControllers.taskbarInsetsController.drawDebugTouchableRegionBounds(canvas);
|
||||
}
|
||||
|
||||
/** Handles any touch event before it is dispatched to the rest of TaskbarDragLayer. */
|
||||
public void onDispatchTouchEvent(MotionEvent ev) {
|
||||
if (mActivity.isThreeButtonNav() && ev.getAction() == MotionEvent.ACTION_OUTSIDE
|
||||
&& mControllers.uiController.isAnimatingToHotseat()) {
|
||||
// When touching during animation to home, jump to the end so Hotseat can handle
|
||||
// the touch. (Gesture Navigation handles this in AbsSwipeUpHandler.)
|
||||
mControllers.uiController.endAnimationToHotseat();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.view.animation.Interpolator
|
||||
import android.window.OnBackInvokedDispatcher
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import app.lawnchair.theme.color.tokens.ColorTokens
|
||||
import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
|
||||
import com.android.app.animation.Interpolators.EMPHASIZED_DECELERATE
|
||||
import com.android.app.animation.Interpolators.STANDARD
|
||||
@@ -39,20 +40,22 @@ import com.android.launcher3.popup.RoundedArrowDrawable
|
||||
import com.android.launcher3.util.Themes
|
||||
import com.android.launcher3.views.ActivityContext
|
||||
|
||||
import app.lawnchair.theme.color.tokens.ColorTokens
|
||||
|
||||
private const val ENTER_DURATION_MS = 300L
|
||||
private const val EXIT_DURATION_MS = 150L
|
||||
|
||||
/** Floating tooltip for Taskbar education. */
|
||||
class TaskbarEduTooltip
|
||||
@JvmOverloads
|
||||
constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
|
||||
AbstractFloatingView(context, attrs, defStyleAttr) {
|
||||
constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0,
|
||||
) : AbstractFloatingView(context, attrs, defStyleAttr) {
|
||||
|
||||
private val activityContext: ActivityContext = ActivityContext.lookupContext(context)
|
||||
|
||||
private val backgroundColor = ColorTokens.SurfaceBrightColor.resolveColor(getContext())
|
||||
private val backgroundColor =
|
||||
ColorTokens.SurfaceBrightColor.resolveColor(getContext())
|
||||
|
||||
private val tooltipCornerRadius = Themes.getDialogCornerRadius(context)
|
||||
private val arrowWidth = resources.getDimension(R.dimen.popup_arrow_width)
|
||||
@@ -99,8 +102,8 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
override fun onFinishInflate() {
|
||||
super.onFinishInflate()
|
||||
|
||||
content = requireViewById(R.id.content)
|
||||
arrow = requireViewById(R.id.arrow)
|
||||
content = requireViewById<android.widget.FrameLayout>(R.id.content)
|
||||
arrow = requireViewById<View>(R.id.arrow)
|
||||
arrow.background =
|
||||
RoundedArrowDrawable(
|
||||
arrowWidth,
|
||||
@@ -148,7 +151,6 @@ constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
// Lawnchair-TODO-Merge: This was disabled but enabled in 16r2, this function likely disable the gesture system during edu
|
||||
// findOnBackInvokedDispatcher()
|
||||
// ?.registerOnBackInvokedCallback(OnBackInvokedDispatcher.PRIORITY_DEFAULT, this)
|
||||
}
|
||||
|
||||
@@ -33,26 +33,22 @@ import android.view.accessibility.AccessibilityNodeInfo
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.IntDef
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.airbnb.lottie.LottieAnimationView
|
||||
import com.android.launcher3.LauncherPrefs
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.RemoveAnimationSettingsTracker
|
||||
import com.android.launcher3.Utilities
|
||||
import com.android.launcher3.config.FeatureFlags.enableTaskbarPinning
|
||||
import com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_EDU_OPEN
|
||||
import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
|
||||
import com.android.launcher3.util.DisplayController
|
||||
import com.android.launcher3.util.OnboardingPrefs.TASKBAR_EDU_TOOLTIP_STEP
|
||||
import com.android.launcher3.util.OnboardingPrefs.TASKBAR_SEARCH_EDU_SEEN
|
||||
import com.android.launcher3.util.ResourceBasedOverride
|
||||
import com.android.launcher3.views.ActivityContext
|
||||
import com.android.launcher3.views.BaseDragLayer
|
||||
import com.android.quickstep.util.ContextualSearchInvoker
|
||||
import com.android.quickstep.util.LottieAnimationColorUtils
|
||||
import com.android.wm.shell.shared.TypefaceUtils
|
||||
import com.android.wm.shell.shared.TypefaceUtils.FontFamily
|
||||
import java.io.PrintWriter
|
||||
|
||||
/** First EDU step for swiping up to show transient Taskbar. */
|
||||
@@ -83,11 +79,7 @@ open class TaskbarEduTooltipController(context: Context) :
|
||||
ResourceBasedOverride, LoggableTaskbarController {
|
||||
|
||||
protected val activityContext: TaskbarActivityContext = ActivityContext.lookupContext(context)
|
||||
open val shouldShowSearchEdu: Boolean
|
||||
get() =
|
||||
ContextualSearchInvoker(activityContext)
|
||||
.runContextualSearchInvocationChecksAndLogFailures()
|
||||
|
||||
open val shouldShowSearchEdu = false
|
||||
private val isTooltipEnabled: Boolean
|
||||
get() {
|
||||
return !Utilities.isRunningInTestHarness() &&
|
||||
@@ -95,7 +87,7 @@ open class TaskbarEduTooltipController(context: Context) :
|
||||
!activityContext.isTinyTaskbar
|
||||
}
|
||||
|
||||
val isTooltipOpen: Boolean
|
||||
private val isOpen: Boolean
|
||||
get() = tooltip?.isOpen ?: false
|
||||
|
||||
val isBeforeTooltipFeaturesStep: Boolean
|
||||
@@ -104,8 +96,7 @@ open class TaskbarEduTooltipController(context: Context) :
|
||||
private lateinit var controllers: TaskbarControllers
|
||||
|
||||
// Keep track of whether the user has seen the Search Edu
|
||||
@VisibleForTesting
|
||||
var userHasSeenSearchEdu: Boolean
|
||||
private var userHasSeenSearchEdu: Boolean
|
||||
get() {
|
||||
return TASKBAR_SEARCH_EDU_SEEN.get(activityContext)
|
||||
}
|
||||
@@ -130,31 +121,11 @@ open class TaskbarEduTooltipController(context: Context) :
|
||||
activityContext.dragLayer.post { maybeShowSearchEdu() }
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns off auto play of lottie animations if user has opted to remove animation else attaches
|
||||
* click listener to allow user to play or pause animations.
|
||||
*/
|
||||
fun handleEduAnimations(animationViews: List<LottieAnimationView>) {
|
||||
for (animationView in animationViews) {
|
||||
if (
|
||||
RemoveAnimationSettingsTracker.INSTANCE.get(animationView.context)
|
||||
.isRemoveAnimationEnabled()
|
||||
) {
|
||||
animationView.pauseAnimation()
|
||||
} else {
|
||||
animationView.setOnClickListener {
|
||||
if (animationView.isAnimating) animationView.pauseAnimation()
|
||||
else animationView.playAnimation()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Shows swipe EDU tooltip if it is the current [tooltipStep]. */
|
||||
fun maybeShowSwipeEdu() {
|
||||
if (
|
||||
!isTooltipEnabled ||
|
||||
!activityContext.isTransientTaskbar ||
|
||||
!DisplayController.isTransientTaskbar(activityContext) ||
|
||||
tooltipStep > TOOLTIP_STEP_SWIPE
|
||||
) {
|
||||
return
|
||||
@@ -163,13 +134,7 @@ open class TaskbarEduTooltipController(context: Context) :
|
||||
tooltipStep = TOOLTIP_STEP_FEATURES
|
||||
inflateTooltip(R.layout.taskbar_edu_swipe)
|
||||
tooltip?.run {
|
||||
TypefaceUtils.setTypeface(
|
||||
requireViewById(R.id.taskbar_edu_title),
|
||||
FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED,
|
||||
)
|
||||
val swipeAnimation = requireViewById<LottieAnimationView>(R.id.swipe_animation)
|
||||
swipeAnimation.supportLightTheme()
|
||||
handleEduAnimations(listOf(swipeAnimation))
|
||||
requireViewById<LottieAnimationView>(R.id.swipe_animation).supportLightTheme()
|
||||
show()
|
||||
}
|
||||
}
|
||||
@@ -198,8 +163,7 @@ open class TaskbarEduTooltipController(context: Context) :
|
||||
splitscreenAnim.supportLightTheme()
|
||||
suggestionsAnim.supportLightTheme()
|
||||
pinningAnim.supportLightTheme()
|
||||
handleEduAnimations(listOf(splitscreenAnim, suggestionsAnim, pinningAnim))
|
||||
if (activityContext.isTransientTaskbar) {
|
||||
if (DisplayController.isTransientTaskbar(activityContext)) {
|
||||
splitscreenAnim.setAnimation(R.raw.taskbar_edu_splitscreen_transient)
|
||||
suggestionsAnim.setAnimation(R.raw.taskbar_edu_suggestions_transient)
|
||||
pinningEdu.visibility = if (enableTaskbarPinning()) VISIBLE else GONE
|
||||
@@ -209,27 +173,10 @@ open class TaskbarEduTooltipController(context: Context) :
|
||||
pinningEdu.visibility = GONE
|
||||
}
|
||||
|
||||
TypefaceUtils.setTypeface(
|
||||
requireViewById(R.id.taskbar_edu_title),
|
||||
FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED,
|
||||
)
|
||||
TypefaceUtils.setTypeface(
|
||||
requireViewById(R.id.splitscreen_text),
|
||||
FontFamily.GSF_BODY_MEDIUM,
|
||||
)
|
||||
TypefaceUtils.setTypeface(
|
||||
requireViewById(R.id.suggestions_text),
|
||||
FontFamily.GSF_BODY_MEDIUM,
|
||||
)
|
||||
TypefaceUtils.setTypeface(
|
||||
requireViewById(R.id.pinning_text),
|
||||
FontFamily.GSF_BODY_MEDIUM,
|
||||
)
|
||||
|
||||
// Set up layout parameters.
|
||||
content.updateLayoutParams { width = MATCH_PARENT }
|
||||
updateLayoutParams<MarginLayoutParams> {
|
||||
if (activityContext.isTransientTaskbar) {
|
||||
if (DisplayController.isTransientTaskbar(activityContext)) {
|
||||
width =
|
||||
resources.getDimensionPixelSize(
|
||||
if (enableTaskbarPinning())
|
||||
@@ -262,7 +209,7 @@ open class TaskbarEduTooltipController(context: Context) :
|
||||
// for the original 2 edu steps) as a proxy to needing to show the separate pinning edu
|
||||
if (
|
||||
!enableTaskbarPinning() ||
|
||||
!activityContext.isTransientTaskbar ||
|
||||
!DisplayController.isTransientTaskbar(activityContext) ||
|
||||
!isTooltipEnabled ||
|
||||
tooltipStep > TOOLTIP_STEP_PINNING ||
|
||||
tooltipStep < TOOLTIP_STEP_FEATURES
|
||||
@@ -274,21 +221,11 @@ open class TaskbarEduTooltipController(context: Context) :
|
||||
|
||||
tooltip?.run {
|
||||
allowTouchDismissal = true
|
||||
TypefaceUtils.setTypeface(
|
||||
requireViewById(R.id.taskbar_edu_title),
|
||||
FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED,
|
||||
)
|
||||
TypefaceUtils.setTypeface(
|
||||
requireViewById(R.id.pinning_text),
|
||||
FontFamily.GSF_BODY_MEDIUM,
|
||||
)
|
||||
requireViewById<LottieAnimationView>(R.id.standalone_pinning_animation)
|
||||
.supportLightTheme()
|
||||
|
||||
val pinningAnim =
|
||||
requireViewById<LottieAnimationView>(R.id.standalone_pinning_animation)
|
||||
pinningAnim.supportLightTheme()
|
||||
handleEduAnimations(listOf(pinningAnim))
|
||||
updateLayoutParams<BaseDragLayer.LayoutParams> {
|
||||
if (activityContext.isTransientTaskbar) {
|
||||
if (DisplayController.isTransientTaskbar(activityContext)) {
|
||||
bottomMargin += activityContext.deviceProfile.taskbarHeight
|
||||
}
|
||||
// Unlike other tooltips, we want to align with taskbar divider rather than center.
|
||||
@@ -318,11 +255,10 @@ open class TaskbarEduTooltipController(context: Context) :
|
||||
fun maybeShowSearchEdu() {
|
||||
if (
|
||||
!enableTaskbarPinning() ||
|
||||
!activityContext.isPinnedTaskbar ||
|
||||
!DisplayController.isPinnedTaskbar(activityContext) ||
|
||||
!isTooltipEnabled ||
|
||||
!shouldShowSearchEdu ||
|
||||
userHasSeenSearchEdu ||
|
||||
!controllers.taskbarStashController.isTaskbarVisibleAndNotStashing
|
||||
userHasSeenSearchEdu
|
||||
) {
|
||||
return
|
||||
}
|
||||
@@ -330,20 +266,11 @@ open class TaskbarEduTooltipController(context: Context) :
|
||||
inflateTooltip(R.layout.taskbar_edu_search)
|
||||
tooltip?.run {
|
||||
allowTouchDismissal = true
|
||||
val searchEdu = requireViewById<LottieAnimationView>(R.id.search_edu_animation)
|
||||
searchEdu.supportLightTheme()
|
||||
handleEduAnimations(listOf(searchEdu))
|
||||
requireViewById<LottieAnimationView>(R.id.search_edu_animation).supportLightTheme()
|
||||
val eduSubtitle: TextView = requireViewById(R.id.search_edu_text)
|
||||
|
||||
TypefaceUtils.setTypeface(
|
||||
requireViewById(R.id.taskbar_edu_title),
|
||||
FontFamily.GSF_HEADLINE_SMALL_EMPHASIZED,
|
||||
)
|
||||
TypefaceUtils.setTypeface(eduSubtitle, FontFamily.GSF_BODY_SMALL)
|
||||
|
||||
showDisclosureText(eduSubtitle)
|
||||
updateLayoutParams<BaseDragLayer.LayoutParams> {
|
||||
if (activityContext.isTransientTaskbar) {
|
||||
if (DisplayController.isTransientTaskbar(activityContext)) {
|
||||
bottomMargin += activityContext.deviceProfile.taskbarHeight
|
||||
}
|
||||
// Unlike other tooltips, we want to align with the all apps button rather than
|
||||
@@ -422,19 +349,19 @@ open class TaskbarEduTooltipController(context: Context) :
|
||||
overlayContext.layoutInflater.inflate(
|
||||
R.layout.taskbar_edu_tooltip,
|
||||
overlayContext.dragLayer,
|
||||
false,
|
||||
false
|
||||
) as TaskbarEduTooltip
|
||||
|
||||
controllers.taskbarAutohideSuspendController.updateFlag(
|
||||
FLAG_AUTOHIDE_SUSPEND_EDU_OPEN,
|
||||
true,
|
||||
true
|
||||
)
|
||||
|
||||
tooltip.onCloseCallback = {
|
||||
this.tooltip = null
|
||||
controllers.taskbarAutohideSuspendController.updateFlag(
|
||||
FLAG_AUTOHIDE_SUSPEND_EDU_OPEN,
|
||||
false,
|
||||
false
|
||||
)
|
||||
controllers.taskbarStashController.updateAndAnimateTransientTaskbar(true)
|
||||
}
|
||||
@@ -449,7 +376,7 @@ open class TaskbarEduTooltipController(context: Context) :
|
||||
override fun performAccessibilityAction(
|
||||
host: View,
|
||||
action: Int,
|
||||
args: Bundle?,
|
||||
args: Bundle?
|
||||
): Boolean {
|
||||
if (action == R.id.close) {
|
||||
hide()
|
||||
@@ -467,13 +394,13 @@ open class TaskbarEduTooltipController(context: Context) :
|
||||
|
||||
override fun onInitializeAccessibilityNodeInfo(
|
||||
host: View,
|
||||
info: AccessibilityNodeInfo,
|
||||
info: AccessibilityNodeInfo
|
||||
) {
|
||||
super.onInitializeAccessibilityNodeInfo(host, info)
|
||||
info.addAction(
|
||||
AccessibilityNodeInfo.AccessibilityAction(
|
||||
R.id.close,
|
||||
host.context?.getText(R.string.taskbar_edu_close),
|
||||
host.context?.getText(R.string.taskbar_edu_close)
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -482,7 +409,7 @@ open class TaskbarEduTooltipController(context: Context) :
|
||||
override fun dumpLogs(prefix: String?, pw: PrintWriter?) {
|
||||
pw?.println(prefix + "TaskbarEduTooltipController:")
|
||||
pw?.println("$prefix\tisTooltipEnabled=$isTooltipEnabled")
|
||||
pw?.println("$prefix\tisOpen=$isTooltipOpen")
|
||||
pw?.println("$prefix\tisOpen=$isOpen")
|
||||
pw?.println("$prefix\ttooltipStep=$tooltipStep")
|
||||
}
|
||||
|
||||
@@ -492,7 +419,7 @@ open class TaskbarEduTooltipController(context: Context) :
|
||||
return ResourceBasedOverride.Overrides.getObject(
|
||||
TaskbarEduTooltipController::class.java,
|
||||
context,
|
||||
R.string.taskbar_edu_tooltip_controller_class,
|
||||
R.string.taskbar_edu_tooltip_controller_class
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-7
@@ -85,9 +85,6 @@ public class TaskbarForceVisibleImmersiveController implements TouchController {
|
||||
|
||||
/** Update values tracked via sysui flags. */
|
||||
public void updateSysuiFlags(@SystemUiStateFlags long sysuiFlags) {
|
||||
if (mContext.isPhoneMode()) {
|
||||
return;
|
||||
}
|
||||
mIsImmersiveMode = (sysuiFlags & SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY) == 0;
|
||||
if (mContext.isNavBarForceVisible()) {
|
||||
if (mIsImmersiveMode) {
|
||||
@@ -108,10 +105,8 @@ public class TaskbarForceVisibleImmersiveController implements TouchController {
|
||||
/** Clean up animations. */
|
||||
public void onDestroy() {
|
||||
startIconUndimming();
|
||||
if (mControllers != null) {
|
||||
mControllers.navbarButtonsViewController.setHomeButtonAccessibilityDelegate(null);
|
||||
mControllers.navbarButtonsViewController.setBackButtonAccessibilityDelegate(null);
|
||||
}
|
||||
mControllers.navbarButtonsViewController.setHomeButtonAccessibilityDelegate(null);
|
||||
mControllers.navbarButtonsViewController.setBackButtonAccessibilityDelegate(null);
|
||||
}
|
||||
|
||||
private void startIconUndimming() {
|
||||
|
||||
@@ -18,23 +18,29 @@ package com.android.launcher3.taskbar;
|
||||
import static android.view.MotionEvent.ACTION_HOVER_ENTER;
|
||||
import static android.view.MotionEvent.ACTION_HOVER_EXIT;
|
||||
import static android.view.View.ALPHA;
|
||||
import static android.view.View.SCALE_Y;
|
||||
import static android.view.accessibility.AccessibilityManager.FLAG_CONTENT_TEXT;
|
||||
|
||||
import static com.android.launcher3.AbstractFloatingView.TYPE_ACTION_POPUP;
|
||||
import static com.android.launcher3.AbstractFloatingView.TYPE_FOLDER;
|
||||
import static com.android.app.animation.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL_EXCEPT_ON_BOARD_POPUP;
|
||||
import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS;
|
||||
import static com.android.launcher3.views.ArrowTipView.TEXT_ALPHA;
|
||||
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.app.animation.Interpolators;
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.apppairs.AppPairIcon;
|
||||
import com.android.launcher3.compat.AccessibilityManagerCompat;
|
||||
import com.android.launcher3.folder.FolderIcon;
|
||||
import com.android.launcher3.views.ArrowTipView;
|
||||
|
||||
@@ -42,16 +48,19 @@ import com.android.launcher3.views.ArrowTipView;
|
||||
* Controls showing a tooltip in the taskbar above each icon when it is hovered.
|
||||
*/
|
||||
public class TaskbarHoverToolTipController implements View.OnHoverListener {
|
||||
// Short duration to reveal tooltip, as it is positioned in the x/y via a post() call in
|
||||
// parallel with the open animation. An instant animation could show in the wrong location.
|
||||
private static final int HOVER_TOOL_TIP_REVEAL_DURATION = 15;
|
||||
|
||||
private static final int HOVER_TOOL_TIP_REVEAL_DURATION = 250;
|
||||
private static final int HOVER_TOOL_TIP_EXIT_DURATION = 150;
|
||||
|
||||
private final Handler mHoverToolTipHandler = new Handler(Looper.getMainLooper());
|
||||
private final Runnable mRevealHoverToolTipRunnable = this::revealHoverToolTip;
|
||||
private final Runnable mHideHoverToolTipRunnable = this::hideHoverToolTip;
|
||||
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private final TaskbarView mTaskbarView;
|
||||
private final View mHoverView;
|
||||
private final ArrowTipView mHoverToolTipView;
|
||||
private final String mToolTipText;
|
||||
private final int mYOffset;
|
||||
|
||||
public TaskbarHoverToolTipController(TaskbarActivityContext activity, TaskbarView taskbarView,
|
||||
View hoverView) {
|
||||
@@ -64,8 +73,6 @@ public class TaskbarHoverToolTipController implements View.OnHoverListener {
|
||||
} else if (mHoverView instanceof FolderIcon
|
||||
&& ((FolderIcon) mHoverView).mInfo.title != null) {
|
||||
mToolTipText = ((FolderIcon) mHoverView).mInfo.title.toString();
|
||||
} else if (mHoverView instanceof AppPairIcon) {
|
||||
mToolTipText = ((AppPairIcon) mHoverView).getTitleTextView().getText().toString();
|
||||
} else {
|
||||
mToolTipText = null;
|
||||
}
|
||||
@@ -80,55 +87,90 @@ public class TaskbarHoverToolTipController implements View.OnHoverListener {
|
||||
R.dimen.taskbar_tooltip_horizontal_padding);
|
||||
mHoverToolTipView.findViewById(R.id.text).setPadding(horizontalPadding, verticalPadding,
|
||||
horizontalPadding, verticalPadding);
|
||||
mHoverToolTipView.setAlpha(0);
|
||||
mYOffset = arrowContextWrapper.getResources().getDimensionPixelSize(
|
||||
R.dimen.taskbar_tooltip_y_offset);
|
||||
|
||||
AnimatorSet hoverCloseAnimator = new AnimatorSet();
|
||||
ObjectAnimator textCloseAnimator = ObjectAnimator.ofInt(mHoverToolTipView, TEXT_ALPHA, 0);
|
||||
textCloseAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0, 0.33f));
|
||||
ObjectAnimator alphaCloseAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, ALPHA, 0);
|
||||
alphaCloseAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0.33f, 0.66f));
|
||||
ObjectAnimator scaleCloseAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, SCALE_Y, 0);
|
||||
scaleCloseAnimator.setInterpolator(Interpolators.STANDARD);
|
||||
hoverCloseAnimator.playTogether(
|
||||
textCloseAnimator,
|
||||
alphaCloseAnimator,
|
||||
scaleCloseAnimator);
|
||||
hoverCloseAnimator.setStartDelay(0);
|
||||
hoverCloseAnimator.setDuration(HOVER_TOOL_TIP_EXIT_DURATION);
|
||||
mHoverToolTipView.setCustomCloseAnimation(hoverCloseAnimator);
|
||||
|
||||
AnimatorSet hoverOpenAnimator = new AnimatorSet();
|
||||
ObjectAnimator textOpenAnimator =
|
||||
ObjectAnimator.ofInt(mHoverToolTipView, TEXT_ALPHA, 0, 255);
|
||||
textOpenAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0.15f, 0.75f));
|
||||
ObjectAnimator scaleOpenAnimator =
|
||||
ObjectAnimator.ofFloat(mHoverToolTipView, SCALE_Y, 0f, 1f);
|
||||
scaleOpenAnimator.setInterpolator(Interpolators.EMPHASIZED);
|
||||
ObjectAnimator alphaOpenAnimator = ObjectAnimator.ofFloat(mHoverToolTipView, ALPHA, 0f, 1f);
|
||||
hoverOpenAnimator.play(alphaOpenAnimator);
|
||||
alphaOpenAnimator.setInterpolator(Interpolators.clampToProgress(LINEAR, 0f, 0.33f));
|
||||
hoverOpenAnimator.playTogether(
|
||||
scaleOpenAnimator,
|
||||
textOpenAnimator,
|
||||
alphaOpenAnimator);
|
||||
hoverOpenAnimator.setDuration(HOVER_TOOL_TIP_REVEAL_DURATION);
|
||||
mHoverToolTipView.setCustomOpenAnimation(hoverOpenAnimator);
|
||||
|
||||
mHoverToolTipView.addOnLayoutChangeListener(
|
||||
(v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> {
|
||||
mHoverToolTipView.setPivotY(bottom);
|
||||
mHoverToolTipView.setY(mTaskbarView.getTop() - mYOffset - (bottom - top));
|
||||
mHoverToolTipView.setY(mTaskbarView.getTop() - (bottom - top));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onHover(View v, MotionEvent event) {
|
||||
boolean isAnyOtherFloatingViewOpen =
|
||||
AbstractFloatingView.hasOpenView(mActivity, TYPE_ALL_EXCEPT_ON_BOARD_POPUP);
|
||||
if (isAnyOtherFloatingViewOpen) {
|
||||
mHoverToolTipHandler.removeCallbacksAndMessages(null);
|
||||
}
|
||||
// If hover leaves a taskbar icon animate the tooltip closed.
|
||||
if (event.getAction() == ACTION_HOVER_EXIT) {
|
||||
mHoverToolTipView.close(/* animate= */ false);
|
||||
startHideHoverToolTip();
|
||||
mActivity.setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, false);
|
||||
} else if (event.getAction() == ACTION_HOVER_ENTER) {
|
||||
maybeRevealHoverToolTip();
|
||||
return true;
|
||||
} else if (!isAnyOtherFloatingViewOpen && event.getAction() == ACTION_HOVER_ENTER) {
|
||||
// If hovering above a taskbar icon starts, animate the tooltip open. Do not
|
||||
// reveal if any floating views such as folders or edu pop-ups are open.
|
||||
startRevealHoverToolTip();
|
||||
mActivity.setAutohideSuspendFlag(FLAG_AUTOHIDE_SUSPEND_HOVERING_ICONS, true);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void maybeRevealHoverToolTip() {
|
||||
private void startRevealHoverToolTip() {
|
||||
mHoverToolTipHandler.post(mRevealHoverToolTipRunnable);
|
||||
}
|
||||
|
||||
private void revealHoverToolTip() {
|
||||
if (mHoverView == null || mToolTipText == null) {
|
||||
return;
|
||||
}
|
||||
// Do not show tooltip if taskbar icons are transitioning to hotseat.
|
||||
if (mActivity.isIconAlignedWithHotseat()) {
|
||||
return;
|
||||
}
|
||||
if (mHoverView instanceof FolderIcon && !((FolderIcon) mHoverView).getIconVisible()) {
|
||||
return;
|
||||
}
|
||||
// Do not reveal if floating views such as folders or app pop-ups are open,
|
||||
// as these views will overlap and not look great.
|
||||
if (AbstractFloatingView.hasOpenView(mActivity, TYPE_FOLDER | TYPE_ACTION_POPUP)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Rect iconViewBounds = Utilities.getViewBounds(mHoverView);
|
||||
mHoverToolTipView.showAtLocation(mToolTipText, iconViewBounds.centerX(),
|
||||
mTaskbarView.getTop() - mYOffset, /* shouldAutoClose= */ false);
|
||||
mTaskbarView.getTop(), /* shouldAutoClose= */ false);
|
||||
}
|
||||
|
||||
private void startHideHoverToolTip() {
|
||||
int accessibilityHideTimeout = AccessibilityManagerCompat.getRecommendedTimeoutMillis(
|
||||
mActivity, /* originalTimeout= */ 0, FLAG_CONTENT_TEXT);
|
||||
mHoverToolTipHandler.postDelayed(mHideHoverToolTipRunnable, accessibilityHideTimeout);
|
||||
}
|
||||
|
||||
private void hideHoverToolTip() {
|
||||
mHoverToolTipView.close(/* animate = */ true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import android.graphics.Insets
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import android.graphics.Region
|
||||
import android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR
|
||||
import android.os.Binder
|
||||
import android.os.IBinder
|
||||
import android.view.DisplayInfo
|
||||
@@ -51,9 +52,11 @@ import com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATI
|
||||
import com.android.launcher3.config.FeatureFlags.enableTaskbarNoRecreate
|
||||
import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
|
||||
import com.android.launcher3.testing.shared.ResourceUtils
|
||||
import com.android.launcher3.util.DisplayController
|
||||
import com.android.launcher3.util.Executors
|
||||
import java.io.PrintWriter
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
import kotlin.math.max
|
||||
|
||||
/** Handles the insets that Taskbar provides to underlying apps and the IME. */
|
||||
class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTaskbarController {
|
||||
@@ -61,10 +64,7 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
companion object {
|
||||
private const val INDEX_LEFT = 0
|
||||
private const val INDEX_RIGHT = 1
|
||||
|
||||
private fun Region.addBoundsToRegion(bounds: Rect?) {
|
||||
bounds?.let { op(it, Region.Op.UNION) }
|
||||
}
|
||||
const val FLAG_INSETS_ROUNDED_CORNER = 1 shl 1
|
||||
}
|
||||
|
||||
/** The bottom insets taskbar provides to the IME when IME is visible. */
|
||||
@@ -80,7 +80,7 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
context.mainThreadHandler,
|
||||
Executors.UI_HELPER_EXECUTOR.handler,
|
||||
context,
|
||||
this::onTaskbarOrBubblebarWindowHeightOrInsetsChanged,
|
||||
this::onTaskbarOrBubblebarWindowHeightOrInsetsChanged
|
||||
)
|
||||
private val debugTouchableRegion = DebugTouchableRegion()
|
||||
|
||||
@@ -94,7 +94,11 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
onTaskbarOrBubblebarWindowHeightOrInsetsChanged()
|
||||
|
||||
context.addOnDeviceProfileChangeListener(deviceProfileChangeListener)
|
||||
gestureNavSettingsObserver.registerForCallingUser()
|
||||
try {
|
||||
gestureNavSettingsObserver.registerForCallingUser()
|
||||
} catch (t: Throwable) {
|
||||
// Ignore
|
||||
}
|
||||
}
|
||||
|
||||
fun onDestroy() {
|
||||
@@ -103,8 +107,7 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
}
|
||||
|
||||
fun onTaskbarOrBubblebarWindowHeightOrInsetsChanged() {
|
||||
val taskbarStashController = controllers.taskbarStashController
|
||||
val tappableHeight = taskbarStashController.tappableHeightToReportToApps
|
||||
val tappableHeight = controllers.taskbarStashController.tappableHeightToReportToApps
|
||||
// We only report tappableElement height for unstashed, persistent taskbar,
|
||||
// which is also when we draw the rounded corners above taskbar.
|
||||
val insetsRoundedCornerFlag =
|
||||
@@ -118,7 +121,7 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
if (enableTaskbarNoRecreate() && controllers.sharedState != null) {
|
||||
getProvidedInsets(
|
||||
controllers.sharedState!!.insetsFrameProviders,
|
||||
insetsRoundedCornerFlag,
|
||||
insetsRoundedCornerFlag
|
||||
)
|
||||
} else {
|
||||
getProvidedInsets(insetsRoundedCornerFlag)
|
||||
@@ -130,32 +133,47 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
}
|
||||
}
|
||||
|
||||
val bubbleControllers = controllers.bubbleControllers.getOrNull()
|
||||
val taskbarTouchableHeight = taskbarStashController.touchableHeight
|
||||
val taskbarTouchableHeight = controllers.taskbarStashController.touchableHeight
|
||||
val bubblesTouchableHeight =
|
||||
bubbleControllers?.bubbleStashController?.getTouchableHeight() ?: 0
|
||||
// reset touch bounds
|
||||
defaultTouchableRegion.setEmpty()
|
||||
if (bubbleControllers != null) {
|
||||
val bubbleBarViewController = bubbleControllers.bubbleBarViewController
|
||||
val isBubbleBarVisible = bubbleControllers.bubbleStashController.isBubbleBarVisible()
|
||||
val isAnimatingNewBubble = bubbleBarViewController.isAnimatingNewBubble
|
||||
// if bubble bar is visible or animating new bubble, add bar bounds to the touch region
|
||||
if (isBubbleBarVisible || isAnimatingNewBubble) {
|
||||
defaultTouchableRegion.addBoundsToRegion(bubbleBarViewController.bubbleBarBounds)
|
||||
defaultTouchableRegion.addBoundsToRegion(bubbleBarViewController.flyoutBounds)
|
||||
if (controllers.bubbleControllers.isPresent) {
|
||||
controllers.bubbleControllers.get().bubbleStashController.touchableHeight
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
val touchableHeight = max(taskbarTouchableHeight, bubblesTouchableHeight)
|
||||
|
||||
if (
|
||||
taskbarStashController.isInApp ||
|
||||
controllers.uiController.isInOverviewUi ||
|
||||
context.showLockedTaskbarOnHome()
|
||||
controllers.bubbleControllers.isPresent &&
|
||||
controllers.bubbleControllers.get().bubbleStashController.isBubblesShowingOnHome
|
||||
) {
|
||||
// only add the taskbar touch region if not on home
|
||||
val bottom = windowLayoutParams.height
|
||||
val top = bottom - taskbarTouchableHeight
|
||||
val right = context.deviceProfile.widthPx
|
||||
defaultTouchableRegion.addBoundsToRegion(Rect(/* left= */ 0, top, right, bottom))
|
||||
val iconBounds =
|
||||
controllers.bubbleControllers.get().bubbleBarViewController.bubbleBarBounds
|
||||
defaultTouchableRegion.set(
|
||||
iconBounds.left,
|
||||
iconBounds.top,
|
||||
iconBounds.right,
|
||||
iconBounds.bottom
|
||||
)
|
||||
} else {
|
||||
defaultTouchableRegion.set(
|
||||
0,
|
||||
windowLayoutParams.height - touchableHeight,
|
||||
context.deviceProfile.widthPx,
|
||||
windowLayoutParams.height
|
||||
)
|
||||
|
||||
// if there's an animating bubble add it to the touch region so that it's clickable
|
||||
val isAnimatingNewBubble =
|
||||
controllers.bubbleControllers
|
||||
.getOrNull()
|
||||
?.bubbleBarViewController
|
||||
?.isAnimatingNewBubble
|
||||
?: false
|
||||
if (isAnimatingNewBubble) {
|
||||
val iconBounds =
|
||||
controllers.bubbleControllers.get().bubbleBarViewController.bubbleBarBounds
|
||||
defaultTouchableRegion.op(iconBounds, Region.Op.UNION)
|
||||
}
|
||||
}
|
||||
|
||||
// Pre-calculate insets for different providers across different rotations for this gravity
|
||||
@@ -180,7 +198,7 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
*/
|
||||
private fun getProvidedInsets(
|
||||
providedInsets: Array<InsetsFrameProvider>,
|
||||
insetsRoundedCornerFlag: Int,
|
||||
insetsRoundedCornerFlag: Int
|
||||
): Array<InsetsFrameProvider> {
|
||||
val navBarsFlag =
|
||||
(if (context.isGestureNav) FLAG_SUPPRESS_SCRIM else 0) or insetsRoundedCornerFlag
|
||||
@@ -206,14 +224,14 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
InsetsFrameProvider(insetsOwner, 0, navigationBars())
|
||||
.setFlags(
|
||||
navBarsFlag,
|
||||
FLAG_SUPPRESS_SCRIM or FLAG_ANIMATE_RESIZING or FLAG_INSETS_ROUNDED_CORNER,
|
||||
FLAG_SUPPRESS_SCRIM or FLAG_ANIMATE_RESIZING or FLAG_INSETS_ROUNDED_CORNER
|
||||
),
|
||||
InsetsFrameProvider(insetsOwner, 0, tappableElement()),
|
||||
InsetsFrameProvider(insetsOwner, 0, mandatorySystemGestures()),
|
||||
InsetsFrameProvider(insetsOwner, INDEX_LEFT, systemGestures())
|
||||
.setSource(SOURCE_DISPLAY),
|
||||
InsetsFrameProvider(insetsOwner, INDEX_RIGHT, systemGestures())
|
||||
.setSource(SOURCE_DISPLAY),
|
||||
.setSource(SOURCE_DISPLAY)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -225,19 +243,20 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
provider.insetsSize = getInsetsForGravityWithCutout(contentHeight, gravity, endRotation)
|
||||
} else if (provider.type == mandatorySystemGestures()) {
|
||||
if (context.isThreeButtonNav) {
|
||||
provider.insetsSize =
|
||||
getInsetsForGravityWithCutout(contentHeight, gravity, endRotation)
|
||||
provider.insetsSize = getInsetsForGravityWithCutout(contentHeight, gravity,
|
||||
endRotation)
|
||||
} else {
|
||||
val gestureHeight =
|
||||
ResourceUtils.getNavbarSize(
|
||||
ResourceUtils.getNavbarSize(
|
||||
ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE,
|
||||
context.resources,
|
||||
)
|
||||
val isPinnedTaskbar =
|
||||
context.deviceProfile.isTaskbarPresent && !context.isTransientTaskbar
|
||||
val mandatoryGestureHeight = if (isPinnedTaskbar) contentHeight else gestureHeight
|
||||
provider.insetsSize =
|
||||
getInsetsForGravityWithCutout(mandatoryGestureHeight, gravity, endRotation)
|
||||
context.resources)
|
||||
val isPinnedTaskbar = context.deviceProfile.isTaskbarPresent
|
||||
&& !context.deviceProfile.isTransientTaskbar
|
||||
val mandatoryGestureHeight =
|
||||
if (isPinnedTaskbar) contentHeight
|
||||
else gestureHeight
|
||||
provider.insetsSize = getInsetsForGravityWithCutout(mandatoryGestureHeight, gravity,
|
||||
endRotation)
|
||||
}
|
||||
} else if (provider.type == tappableElement()) {
|
||||
provider.insetsSize = getInsetsForGravity(tappableHeight, gravity)
|
||||
@@ -256,7 +275,7 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
// When in gesture nav, report the stashed height to the IME, to allow hiding the
|
||||
// IME navigation bar.
|
||||
val imeInsetsSize =
|
||||
if (context.isGestureNav) {
|
||||
if (ENABLE_HIDE_IME_CAPTION_BAR && context.isGestureNav) {
|
||||
getInsetsForGravity(controllers.taskbarStashController.stashedHeight, gravity)
|
||||
} else {
|
||||
getInsetsForGravity(taskbarHeightForIme, gravity)
|
||||
@@ -270,8 +289,8 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
// override below (insetsSizeOverrides must have the same length and
|
||||
// types after the window is added according to
|
||||
// WindowManagerService#relayoutWindow)
|
||||
provider.insetsSize,
|
||||
),
|
||||
provider.insetsSize
|
||||
)
|
||||
)
|
||||
// Use 0 tappableElement insets for the VoiceInteractionWindow when gesture nav is enabled.
|
||||
val visInsetsSizeForTappableElement =
|
||||
@@ -282,7 +301,7 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, imeInsetsSize),
|
||||
InsetsFrameProvider.InsetsSizeOverride(
|
||||
TYPE_VOICE_INTERACTION,
|
||||
visInsetsSizeForTappableElement,
|
||||
visInsetsSizeForTappableElement
|
||||
),
|
||||
)
|
||||
if (
|
||||
@@ -344,21 +363,24 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
*/
|
||||
fun updateInsetsTouchability(insetsInfo: ViewTreeObserver.InternalInsetsInfo) {
|
||||
insetsInfo.touchableRegion.setEmpty()
|
||||
// Always have nav buttons be touchable
|
||||
controllers.navbarButtonsViewController.addVisibleButtonsRegion(
|
||||
context.dragLayer,
|
||||
insetsInfo.touchableRegion
|
||||
)
|
||||
debugTouchableRegion.lastSetTouchableBounds.set(insetsInfo.touchableRegion.bounds)
|
||||
|
||||
val bubbleBarVisible =
|
||||
controllers.bubbleControllers.isPresent &&
|
||||
controllers.bubbleControllers.get().bubbleBarViewController.isBubbleBarVisible()
|
||||
var insetsIsTouchableRegion = true
|
||||
// Prevents the taskbar from taking touches and conflicting with setup wizard
|
||||
if (
|
||||
context.isPhoneButtonNavMode &&
|
||||
context.isUserSetupComplete &&
|
||||
(!controllers.navbarButtonsViewController.isImeVisible ||
|
||||
!controllers.navbarButtonsViewController.isImeRenderingNavButtons)
|
||||
) {
|
||||
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_FRAME)
|
||||
insetsIsTouchableRegion = false
|
||||
debugTouchableRegion.lastSetTouchableReason =
|
||||
"Phone button nav mode: Fullscreen touchable, IME not affecting nav buttons"
|
||||
} else if (context.dragLayer.alpha < AlphaUpdateListener.ALPHA_CUTOFF_THRESHOLD) {
|
||||
// Let touches pass through us.
|
||||
insetsInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION)
|
||||
@@ -390,7 +412,10 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
bubbleBarVisible
|
||||
) {
|
||||
// Taskbar has some touchable elements, take over the full taskbar area
|
||||
if (controllers.uiController.isInOverviewUi && context.isTransientTaskbar) {
|
||||
if (
|
||||
controllers.uiController.isInOverviewUi &&
|
||||
DisplayController.isTransientTaskbar(context)
|
||||
) {
|
||||
val region =
|
||||
controllers.taskbarActivityContext.dragLayer.lastDrawnTransientRect.toRegion()
|
||||
val bubbleBarBounds =
|
||||
@@ -406,7 +431,7 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
|
||||
// Include the bounds of the bubble bar in the touchable region if they exist.
|
||||
if (bubbleBarBounds != null) {
|
||||
region.addBoundsToRegion(bubbleBarBounds)
|
||||
region.op(bubbleBarBounds, Region.Op.UNION)
|
||||
}
|
||||
insetsInfo.touchableRegion.set(region)
|
||||
debugTouchableRegion.lastSetTouchableReason = "Transient Taskbar is in Overview"
|
||||
@@ -423,12 +448,6 @@ class TaskbarInsetsController(val context: TaskbarActivityContext) : LoggableTas
|
||||
debugTouchableRegion.lastSetTouchableReason =
|
||||
"Icons are not visible, but other components such as 3 buttons might be"
|
||||
}
|
||||
// Always have nav buttons be touchable
|
||||
controllers.navbarButtonsViewController.addVisibleButtonsRegion(
|
||||
context.dragLayer,
|
||||
insetsInfo.touchableRegion,
|
||||
)
|
||||
debugTouchableRegion.lastSetTouchableBounds.set(insetsInfo.touchableRegion.bounds)
|
||||
context.excludeFromMagnificationRegion(insetsIsTouchableRegion)
|
||||
}
|
||||
|
||||
|
||||
@@ -16,26 +16,16 @@
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static com.android.app.animation.Interpolators.EMPHASIZED;
|
||||
import static com.android.app.animation.Interpolators.FINAL_FRAME;
|
||||
import static com.android.app.animation.Interpolators.INSTANT;
|
||||
import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
|
||||
import static com.android.launcher3.Hotseat.ALPHA_CHANNEL_TASKBAR_ALIGNMENT;
|
||||
import static com.android.launcher3.Hotseat.ALPHA_CHANNEL_TASKBAR_STASH;
|
||||
import static com.android.launcher3.LauncherState.HOTSEAT_ICONS;
|
||||
import static com.android.launcher3.Utilities.isRtl;
|
||||
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
|
||||
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_OVERVIEW;
|
||||
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_STASHED_LAUNCHER_STATE;
|
||||
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_STASHED_FOR_BUBBLES;
|
||||
import static com.android.launcher3.taskbar.TaskbarStashController.UNLOCK_TRANSITION_MEMOIZATION_MS;
|
||||
import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_HOME;
|
||||
import static com.android.launcher3.taskbar.bubbles.BubbleBarView.FADE_IN_ANIM_ALPHA_DURATION_MS;
|
||||
import static com.android.launcher3.taskbar.bubbles.BubbleBarView.FADE_OUT_ANIM_POSITION_DURATION_MS;
|
||||
import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
|
||||
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
|
||||
import static com.android.quickstep.fallback.RecentsStateUtilsKt.toLauncherState;
|
||||
import static com.android.quickstep.util.SystemUiFlagUtils.isTaskbarHidden;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_AWAKE;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DEVICE_DREAMING;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_WAKEFULNESS_MASK;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.WAKEFULNESS_AWAKE;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
@@ -43,43 +33,32 @@ import android.animation.AnimatorSet;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.app.animation.Interpolators;
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Hotseat;
|
||||
import com.android.launcher3.Hotseat.HotseatQsbAlphaId;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.QuickstepTransitionManager;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.anim.AnimatorListeners;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.statemanager.StateManager;
|
||||
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController.BubbleLauncherState;
|
||||
import com.android.launcher3.uioverrides.QuickstepLauncher;
|
||||
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
|
||||
import com.android.quickstep.RecentsAnimationCallbacks;
|
||||
import com.android.quickstep.RecentsAnimationController;
|
||||
import com.android.quickstep.fallback.RecentsState;
|
||||
import com.android.quickstep.fallback.window.RecentsDisplayModel;
|
||||
import com.android.quickstep.fallback.window.RecentsWindowFlags;
|
||||
import com.android.quickstep.fallback.window.RecentsWindowManager;
|
||||
import com.android.quickstep.util.ScalingWorkspaceRevealAnim;
|
||||
import com.android.quickstep.util.SystemUiFlagUtils;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.systemui.animation.ViewRootSync;
|
||||
import com.android.systemui.shared.recents.model.ThumbnailData;
|
||||
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
|
||||
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.HashMap;
|
||||
import java.util.StringJoiner;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Track LauncherState, RecentsAnimation, resumed state for task bar in one place here and animate
|
||||
@@ -164,23 +143,16 @@ public class TaskbarLauncherStateController {
|
||||
private AnimatedFloat mTaskbarBackgroundAlpha;
|
||||
private AnimatedFloat mTaskbarAlpha;
|
||||
private AnimatedFloat mTaskbarCornerRoundness;
|
||||
private MultiProperty mTaskbarAlphaForHome;
|
||||
private @Nullable Animator mHotseatTranslationXAnimation;
|
||||
private MultiProperty mIconAlphaForHome;
|
||||
private QuickstepLauncher mLauncher;
|
||||
|
||||
private boolean mIsDestroyed = false;
|
||||
private Integer mPrevState;
|
||||
private int mState;
|
||||
private LauncherState mLauncherState = LauncherState.NORMAL;
|
||||
private boolean mSkipNextRecentsAnimEnd;
|
||||
|
||||
// Time when FLAG_TASKBAR_HIDDEN was last cleared, SystemClock.elapsedRealtime (milliseconds).
|
||||
private long mLastRemoveTaskbarHiddenTimeMs = 0;
|
||||
/**
|
||||
* Time when FLAG_DEVICE_LOCKED was last cleared, plus
|
||||
* {@link TaskbarStashController#UNLOCK_TRANSITION_MEMOIZATION_MS}
|
||||
*/
|
||||
private long mLastUnlockTransitionTimeout;
|
||||
private long mLastUnlockTimeMs = 0;
|
||||
|
||||
private @Nullable TaskBarRecentsAnimationListener mTaskBarRecentsAnimationListener;
|
||||
|
||||
@@ -188,15 +160,11 @@ public class TaskbarLauncherStateController {
|
||||
|
||||
private boolean mShouldDelayLauncherStateAnim;
|
||||
|
||||
private @Nullable BubbleBarLocation mBubbleBarLocation;
|
||||
|
||||
// We skip any view synchronizations during init/destroy.
|
||||
private boolean mCanSyncViews;
|
||||
|
||||
private boolean mIsQsbInline;
|
||||
|
||||
private RecentsAnimationCallbacks mRecentsAnimationCallbacks;
|
||||
|
||||
private final DeviceProfile.OnDeviceProfileChangeListener mOnDeviceProfileChangeListener =
|
||||
new DeviceProfile.OnDeviceProfileChangeListener() {
|
||||
@Override
|
||||
@@ -204,18 +172,16 @@ public class TaskbarLauncherStateController {
|
||||
if (mIsQsbInline && !dp.isQsbInline) {
|
||||
// We only modify QSB alpha if isQsbInline = true. If we switch to a DP
|
||||
// where isQsbInline = false, then we need to reset the alpha.
|
||||
mLauncher.getHotseat().setQsbAlpha(1f, ALPHA_CHANNEL_TASKBAR_ALIGNMENT);
|
||||
mLauncher.getHotseat().setQsbAlpha(1f);
|
||||
}
|
||||
mIsQsbInline = dp.isQsbInline;
|
||||
TaskbarLauncherStateController.this.updateIconAlphaForHome(
|
||||
mTaskbarAlphaForHome.getValue(), ALPHA_CHANNEL_TASKBAR_ALIGNMENT);
|
||||
TaskbarLauncherStateController.this.onBubbleBarLocationChanged(
|
||||
mBubbleBarLocation, /* animate = */ false);
|
||||
mIconAlphaForHome.getValue());
|
||||
}
|
||||
};
|
||||
|
||||
private final StateManager.StateListener<LauncherState> mStateListener =
|
||||
new StateManager.StateListener<>() {
|
||||
new StateManager.StateListener<LauncherState>() {
|
||||
|
||||
@Override
|
||||
public void onStateTransitionStart(LauncherState toState) {
|
||||
@@ -229,11 +195,7 @@ public class TaskbarLauncherStateController {
|
||||
updateStateForFlag(FLAG_LAUNCHER_IN_STATE_TRANSITION, true);
|
||||
if (!mShouldDelayLauncherStateAnim) {
|
||||
if (toState == LauncherState.NORMAL) {
|
||||
TaskbarActivityContext activity = mControllers.taskbarActivityContext;
|
||||
boolean isPinnedTaskbarAndNotInDesktopMode =
|
||||
!activity.isInDesktopMode() && activity.isPinnedTaskbar();
|
||||
applyState(QuickstepTransitionManager.getTaskbarToHomeDuration(
|
||||
isPinnedTaskbarAndNotInDesktopMode));
|
||||
applyState(QuickstepTransitionManager.getTaskbarToHomeDuration());
|
||||
} else {
|
||||
applyState();
|
||||
}
|
||||
@@ -249,20 +211,6 @@ public class TaskbarLauncherStateController {
|
||||
}
|
||||
};
|
||||
|
||||
private final StateManager.StateListener<RecentsState> mRecentsStateListener =
|
||||
new StateManager.StateListener<>() {
|
||||
|
||||
@Override
|
||||
public void onStateTransitionStart(RecentsState toState) {
|
||||
mStateListener.onStateTransitionStart(toLauncherState(toState));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStateTransitionComplete(RecentsState finalState) {
|
||||
mStateListener.onStateTransitionComplete(toLauncherState(finalState));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback for when launcher state transition completes after user swipes to home.
|
||||
* @param finalState The final state of the transition.
|
||||
@@ -291,43 +239,31 @@ public class TaskbarLauncherStateController {
|
||||
.getTaskbarBackgroundAlpha();
|
||||
mTaskbarAlpha = mControllers.taskbarDragLayerController.getTaskbarAlpha();
|
||||
mTaskbarCornerRoundness = mControllers.getTaskbarCornerRoundness();
|
||||
mTaskbarAlphaForHome = mControllers.taskbarViewController
|
||||
mIconAlphaForHome = mControllers.taskbarViewController
|
||||
.getTaskbarIconAlpha().get(ALPHA_INDEX_HOME);
|
||||
|
||||
resetIconAlignment();
|
||||
|
||||
if (!mControllers.taskbarActivityContext.isPhoneMode()) {
|
||||
mLauncher.getStateManager().addStateListener(mStateListener);
|
||||
runForRecentsWindowManager(recentsWindowManager ->
|
||||
recentsWindowManager.getStateManager().addStateListener(mRecentsStateListener));
|
||||
}
|
||||
mLauncher.getStateManager().addStateListener(mStateListener);
|
||||
mLauncherState = launcher.getStateManager().getState();
|
||||
updateStateForSysuiFlags(sysuiStateFlags, /*applyState*/ false);
|
||||
|
||||
applyState(0);
|
||||
|
||||
mCanSyncViews = !mControllers.taskbarActivityContext.isPhoneMode();
|
||||
mCanSyncViews = true;
|
||||
mLauncher.addOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
|
||||
updateOverviewDragState(mLauncherState);
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
mIsDestroyed = true;
|
||||
mCanSyncViews = false;
|
||||
|
||||
if (mRecentsAnimationCallbacks != null) {
|
||||
mRecentsAnimationCallbacks.removeListener(mTaskBarRecentsAnimationListener);
|
||||
mRecentsAnimationCallbacks = null;
|
||||
}
|
||||
|
||||
mIconAlignment.finishAnimation();
|
||||
|
||||
mLauncher.getHotseat().setIconsAlpha(1f, ALPHA_CHANNEL_TASKBAR_ALIGNMENT);
|
||||
mLauncher.getHotseat().setIconsAlpha(1f);
|
||||
mLauncher.getStateManager().removeStateListener(mStateListener);
|
||||
runForRecentsWindowManager(recentsWindowManager ->
|
||||
recentsWindowManager.getStateManager().removeStateListener(mRecentsStateListener));
|
||||
|
||||
mCanSyncViews = !mControllers.taskbarActivityContext.isPhoneMode();
|
||||
mCanSyncViews = true;
|
||||
mLauncher.removeOnDeviceProfileChangeListener(mOnDeviceProfileChangeListener);
|
||||
}
|
||||
|
||||
@@ -342,7 +278,6 @@ public class TaskbarLauncherStateController {
|
||||
// If going to overview, stash the task bar
|
||||
// If going home, align the icons to hotseat
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
mRecentsAnimationCallbacks = callbacks;
|
||||
|
||||
// Update stashed flags first to ensure goingToUnstashedLauncherState() returns correctly.
|
||||
TaskbarStashController stashController = mControllers.taskbarStashController;
|
||||
@@ -354,7 +289,6 @@ public class TaskbarLauncherStateController {
|
||||
stashController.updateStateForFlag(FLAG_IN_APP, false);
|
||||
|
||||
updateStateForFlag(FLAG_TRANSITION_TO_VISIBLE, true);
|
||||
mLauncherState = toState;
|
||||
animatorSet.play(stashController.createApplyStateAnimator(duration));
|
||||
animatorSet.play(applyState(duration, false));
|
||||
|
||||
@@ -364,15 +298,12 @@ public class TaskbarLauncherStateController {
|
||||
}
|
||||
mTaskBarRecentsAnimationListener = new TaskBarRecentsAnimationListener(callbacks);
|
||||
callbacks.addListener(mTaskBarRecentsAnimationListener);
|
||||
RecentsView recentsView = mControllers.uiController.getRecentsView();
|
||||
if (recentsView != null) {
|
||||
recentsView.setTaskLaunchListener(() -> mTaskBarRecentsAnimationListener
|
||||
.endGestureStateOverride(true, false /*canceled*/));
|
||||
recentsView.setTaskLaunchCancelledRunnable(() -> {
|
||||
updateStateForUserFinishedToApp(false /* finishedToApp */);
|
||||
});
|
||||
}
|
||||
((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(() ->
|
||||
mTaskBarRecentsAnimationListener.endGestureStateOverride(true, false /*canceled*/));
|
||||
|
||||
((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchCancelledRunnable(() -> {
|
||||
updateStateForUserFinishedToApp(false /* finishedToApp */);
|
||||
});
|
||||
return animatorSet;
|
||||
}
|
||||
|
||||
@@ -414,7 +345,14 @@ public class TaskbarLauncherStateController {
|
||||
|
||||
updateStateForFlag(FLAG_DEVICE_LOCKED, SystemUiFlagUtils.isLocked(systemUiStateFlags));
|
||||
|
||||
updateStateForFlag(FLAG_TASKBAR_HIDDEN, isTaskbarHidden(systemUiStateFlags));
|
||||
// Taskbar is hidden whenever the device is dreaming. The dreaming state includes the
|
||||
// interactive dreams, AoD, screen off. Since the SYSUI_STATE_DEVICE_DREAMING only kicks in
|
||||
// when the device is asleep, the second condition extends ensures that the transition from
|
||||
// and to the WAKEFULNESS_ASLEEP state also hide the taskbar, and improves the taskbar
|
||||
// hide/reveal animation timings.
|
||||
boolean isTaskbarHidden = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_DEVICE_DREAMING)
|
||||
|| (systemUiStateFlags & SYSUI_STATE_WAKEFULNESS_MASK) != WAKEFULNESS_AWAKE;
|
||||
updateStateForFlag(FLAG_TASKBAR_HIDDEN, isTaskbarHidden);
|
||||
|
||||
if (applyState) {
|
||||
applyState();
|
||||
@@ -427,7 +365,10 @@ public class TaskbarLauncherStateController {
|
||||
* @param launcherState The current state launcher is in
|
||||
*/
|
||||
private void updateOverviewDragState(LauncherState launcherState) {
|
||||
boolean disallowLongClick = mLauncher.isSplitSelectionActive() || mIsAnimatingToLauncher;
|
||||
boolean disallowLongClick =
|
||||
FeatureFlags.enableSplitContextually()
|
||||
? mLauncher.isSplitSelectionActive()
|
||||
: launcherState == LauncherState.OVERVIEW_SPLIT_SELECT;
|
||||
com.android.launcher3.taskbar.Utilities.setOverviewDragState(
|
||||
mControllers, launcherState.disallowTaskbarGlobalDrag(),
|
||||
disallowLongClick, launcherState.allowTaskbarInitialSplitSelection());
|
||||
@@ -466,7 +407,7 @@ public class TaskbarLauncherStateController {
|
||||
}
|
||||
|
||||
public Animator applyState(long duration, boolean start) {
|
||||
if (mIsDestroyed || mControllers.taskbarActivityContext.isPhoneMode()) {
|
||||
if (mControllers.taskbarActivityContext.isDestroyed()) {
|
||||
return null;
|
||||
}
|
||||
Animator animator = null;
|
||||
@@ -494,32 +435,23 @@ public class TaskbarLauncherStateController {
|
||||
|
||||
private Animator onStateChangeApplied(int changedFlags, long duration, boolean start) {
|
||||
final boolean isInLauncher = isInLauncher();
|
||||
final boolean isInOverview = mControllers.uiController.isInOverviewUi();
|
||||
final boolean isIconAlignedWithHotseat = isIconAlignedWithHotseat();
|
||||
final float toAlignment = isIconAlignedWithHotseat ? 1 : 0;
|
||||
boolean handleOpenFloatingViews = false;
|
||||
boolean isPinnedTaskbar =
|
||||
mControllers.taskbarActivityContext.isPinnedTaskbar();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onStateChangeApplied - isInLauncher: " + isInLauncher
|
||||
+ ", mLauncherState: " + mLauncherState
|
||||
+ ", toAlignment: " + toAlignment);
|
||||
}
|
||||
mControllers.bubbleControllers.ifPresent(controllers -> {
|
||||
// Show the bubble bar when on launcher home (hotseat icons visible) or in overview
|
||||
boolean onOverview = isInLauncher && mLauncherState == LauncherState.OVERVIEW;
|
||||
boolean hotseatIconsVisible = isInLauncher && mLauncherState.areElementsVisible(
|
||||
mLauncher, HOTSEAT_ICONS);
|
||||
BubbleLauncherState state = onOverview
|
||||
? BubbleLauncherState.OVERVIEW
|
||||
: hotseatIconsVisible
|
||||
? BubbleLauncherState.HOME
|
||||
: BubbleLauncherState.IN_APP;
|
||||
controllers.bubbleStashController.setLauncherState(state);
|
||||
// Show the bubble bar when on launcher home or in overview.
|
||||
boolean onHome = isInLauncher && mLauncherState == LauncherState.NORMAL;
|
||||
boolean onOverview = mLauncherState == LauncherState.OVERVIEW;
|
||||
controllers.bubbleStashController.setBubblesShowingOnHome(onHome);
|
||||
controllers.bubbleStashController.setBubblesShowingOnOverview(onOverview);
|
||||
});
|
||||
|
||||
TaskbarStashController stashController = mControllers.taskbarStashController;
|
||||
stashController.updateStateForFlag(FLAG_IN_OVERVIEW,
|
||||
mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_OVERVIEW,
|
||||
mLauncherState == LauncherState.OVERVIEW);
|
||||
|
||||
AnimatorSet animatorSet = new AnimatorSet();
|
||||
@@ -538,8 +470,7 @@ public class TaskbarLauncherStateController {
|
||||
// We're changing state to home, should close open popups e.g. Taskbar AllApps
|
||||
handleOpenFloatingViews = true;
|
||||
}
|
||||
if (mLauncherState == LauncherState.OVERVIEW
|
||||
&& !mControllers.taskbarActivityContext.isPhoneMode()) {
|
||||
if (mLauncherState == LauncherState.OVERVIEW) {
|
||||
// Calling to update the insets in TaskbarInsetController#updateInsetsTouchability
|
||||
mControllers.taskbarActivityContext.notifyUpdateLayoutParams();
|
||||
}
|
||||
@@ -551,6 +482,8 @@ public class TaskbarLauncherStateController {
|
||||
public void onAnimationStart(Animator animation) {
|
||||
mIsAnimatingToLauncher = isInLauncher;
|
||||
|
||||
TaskbarStashController stashController =
|
||||
mControllers.taskbarStashController;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onAnimationStart - FLAG_IN_APP: " + !isInLauncher);
|
||||
}
|
||||
@@ -566,8 +499,6 @@ public class TaskbarLauncherStateController {
|
||||
|
||||
// Handle closing open popups when going home/overview
|
||||
handleOpenFloatingViews = true;
|
||||
} else {
|
||||
stashController.applyState();
|
||||
}
|
||||
|
||||
if (handleOpenFloatingViews && isInLauncher) {
|
||||
@@ -576,7 +507,7 @@ public class TaskbarLauncherStateController {
|
||||
|
||||
if (hasAnyFlag(changedFlags, FLAG_TASKBAR_HIDDEN) && !hasAnyFlag(FLAG_TASKBAR_HIDDEN)) {
|
||||
// Take note of the current time, as the taskbar is made visible again.
|
||||
mLastRemoveTaskbarHiddenTimeMs = SystemClock.elapsedRealtime();
|
||||
mLastUnlockTimeMs = SystemClock.elapsedRealtime();
|
||||
}
|
||||
|
||||
boolean isHidden = hasAnyFlag(FLAG_TASKBAR_HIDDEN);
|
||||
@@ -602,8 +533,7 @@ public class TaskbarLauncherStateController {
|
||||
// with a fingerprint reader. This should only be done when the device was woken
|
||||
// up via fingerprint reader, however since this information is currently not
|
||||
// available, opting to always delay the fade-in a bit.
|
||||
long durationSinceLastUnlockMs = SystemClock.elapsedRealtime()
|
||||
- mLastRemoveTaskbarHiddenTimeMs;
|
||||
long durationSinceLastUnlockMs = SystemClock.elapsedRealtime() - mLastUnlockTimeMs;
|
||||
taskbarVisibility.setStartDelay(
|
||||
Math.max(0, TASKBAR_SHOW_DELAY_MS - durationSinceLastUnlockMs));
|
||||
}
|
||||
@@ -611,18 +541,10 @@ public class TaskbarLauncherStateController {
|
||||
}
|
||||
|
||||
float backgroundAlpha = isInLauncher && isTaskbarAlignedWithHotseat() ? 0 : 1;
|
||||
AnimatedFloat taskbarBgOffset =
|
||||
mControllers.taskbarDragLayerController.getTaskbarBackgroundOffset();
|
||||
boolean showTaskbar = shouldShowTaskbar(mControllers.taskbarActivityContext, isInLauncher,
|
||||
isInOverview);
|
||||
float taskbarBgOffsetEnd = showTaskbar ? 0f : 1f;
|
||||
float taskbarBgOffsetStart = showTaskbar ? 1f : 0f;
|
||||
|
||||
// Don't animate if background has reached desired value.
|
||||
if (mTaskbarBackgroundAlpha.isAnimating()
|
||||
|| mTaskbarBackgroundAlpha.value != backgroundAlpha
|
||||
|| taskbarBgOffset.isAnimatingToValue(taskbarBgOffsetStart)
|
||||
|| taskbarBgOffset.value != taskbarBgOffsetEnd) {
|
||||
|| mTaskbarBackgroundAlpha.value != backgroundAlpha) {
|
||||
mTaskbarBackgroundAlpha.cancelAnimation();
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "onStateChangeApplied - taskbarBackgroundAlpha - "
|
||||
@@ -633,48 +555,30 @@ public class TaskbarLauncherStateController {
|
||||
boolean isInLauncherIconNotAligned = isInLauncher && !isIconAlignedWithHotseat;
|
||||
boolean notInLauncherIconNotAligned = !isInLauncher && !isIconAlignedWithHotseat;
|
||||
boolean isInLauncherIconIsAligned = isInLauncher && isIconAlignedWithHotseat;
|
||||
// When Hotseat icons are not on top don't change duration or add start delay.
|
||||
// This will keep the duration in sync for icon alignment and background fade in/out.
|
||||
// For example, launching app from launcher all apps.
|
||||
boolean isHotseatIconOnTopWhenAligned =
|
||||
mControllers.uiController.isHotseatIconOnTopWhenAligned();
|
||||
|
||||
float startDelay = 0;
|
||||
// We want to delay the background from fading in so that the icons have time to move
|
||||
// into the bounds of the background before it appears.
|
||||
if (isInLauncherIconNotAligned) {
|
||||
startDelay = duration * TASKBAR_BG_ALPHA_LAUNCHER_NOT_ALIGNED_DELAY_MULT;
|
||||
} else if (notInLauncherIconNotAligned && isHotseatIconOnTopWhenAligned) {
|
||||
} else if (notInLauncherIconNotAligned) {
|
||||
startDelay = duration * TASKBAR_BG_ALPHA_NOT_LAUNCHER_NOT_ALIGNED_DELAY_MULT;
|
||||
}
|
||||
float newDuration = duration - startDelay;
|
||||
if (isInLauncherIconIsAligned && isHotseatIconOnTopWhenAligned) {
|
||||
if (isInLauncherIconIsAligned) {
|
||||
// Make the background fade out faster so that it is gone by the time the
|
||||
// icons move outside of the bounds of the background.
|
||||
newDuration = duration * TASKBAR_BG_ALPHA_LAUNCHER_IS_ALIGNED_DURATION_MULT;
|
||||
}
|
||||
Animator taskbarBackgroundAlpha = mTaskbarBackgroundAlpha.animateToValue(
|
||||
backgroundAlpha);
|
||||
if (isPinnedTaskbar) {
|
||||
setupPinnedTaskbarAnimation(animatorSet, showTaskbar, taskbarBgOffset,
|
||||
taskbarBgOffsetStart, taskbarBgOffsetEnd, duration, taskbarBackgroundAlpha);
|
||||
} else {
|
||||
taskbarBackgroundAlpha.setDuration((long) newDuration);
|
||||
taskbarBackgroundAlpha.setStartDelay((long) startDelay);
|
||||
}
|
||||
Animator taskbarBackgroundAlpha = mTaskbarBackgroundAlpha
|
||||
.animateToValue(backgroundAlpha)
|
||||
.setDuration((long) newDuration);
|
||||
taskbarBackgroundAlpha.setStartDelay((long) startDelay);
|
||||
animatorSet.play(taskbarBackgroundAlpha);
|
||||
}
|
||||
|
||||
float cornerRoundness = isInLauncher ? 0 : 1;
|
||||
|
||||
if (mControllers.taskbarDesktopModeController.isInDesktopModeAndNotInOverview(
|
||||
mControllers.taskbarActivityContext.getDisplayId())
|
||||
&& mControllers.getSharedState() != null) {
|
||||
cornerRoundness =
|
||||
mControllers.taskbarDesktopModeController.getTaskbarCornerRoundness(
|
||||
mControllers.getSharedState().showCornerRadiusInDesktopMode);
|
||||
}
|
||||
|
||||
// Don't animate if corner roundness has reached desired value.
|
||||
if (mTaskbarCornerRoundness.isAnimating()
|
||||
|| mTaskbarCornerRoundness.value != cornerRoundness) {
|
||||
@@ -692,15 +596,6 @@ public class TaskbarLauncherStateController {
|
||||
boolean isUnlockTransition =
|
||||
hasAnyFlag(changedFlags, FLAG_DEVICE_LOCKED) && !hasAnyFlag(FLAG_DEVICE_LOCKED);
|
||||
if (isUnlockTransition) {
|
||||
// the launcher might not be resumed at the time the device is considered
|
||||
// unlocked (when the keyguard goes away), but possibly shortly afterwards.
|
||||
// To play the unlock transition at the time the unstash animation actually happens,
|
||||
// this memoizes the state transition for UNLOCK_TRANSITION_MEMOIZATION_MS.
|
||||
mLastUnlockTransitionTimeout =
|
||||
SystemClock.elapsedRealtime() + UNLOCK_TRANSITION_MEMOIZATION_MS;
|
||||
}
|
||||
boolean isInUnlockTimeout = SystemClock.elapsedRealtime() < mLastUnlockTransitionTimeout;
|
||||
if (isUnlockTransition || isInUnlockTimeout) {
|
||||
// When transitioning to unlocked, ensure the hotseat is fully visible from the
|
||||
// beginning. The hotseat itself is animated by LauncherUnlockAnimationController.
|
||||
mIconAlignment.cancelAnimation();
|
||||
@@ -716,11 +611,8 @@ public class TaskbarLauncherStateController {
|
||||
} else if (mIconAlignment.isAnimatingToValue(toAlignment)
|
||||
|| mIconAlignment.isSettledOnValue(toAlignment)) {
|
||||
// Already at desired value, but make sure we run the callback at the end.
|
||||
animatorSet.addListener(AnimatorListeners.forEndCallback(() -> {
|
||||
if (!mIconAlignment.isAnimating()) {
|
||||
onIconAlignmentRatioChanged();
|
||||
}
|
||||
}));
|
||||
animatorSet.addListener(AnimatorListeners.forEndCallback(
|
||||
this::onIconAlignmentRatioChanged));
|
||||
} else {
|
||||
mIconAlignment.cancelAnimation();
|
||||
ObjectAnimator iconAlignAnim = mIconAlignment
|
||||
@@ -731,19 +623,10 @@ public class TaskbarLauncherStateController {
|
||||
+ mIconAlignment.value
|
||||
+ " -> " + toAlignment + ": " + duration);
|
||||
}
|
||||
if (!isPinnedTaskbar) {
|
||||
if (hasAnyFlag(FLAG_TASKBAR_HIDDEN)) {
|
||||
iconAlignAnim.setInterpolator(FINAL_FRAME);
|
||||
} else {
|
||||
animatorSet.play(iconAlignAnim);
|
||||
}
|
||||
}
|
||||
animatorSet.play(iconAlignAnim);
|
||||
}
|
||||
|
||||
Interpolator interpolator = enableScalingRevealHomeAnimation() && !isPinnedTaskbar
|
||||
? ScalingWorkspaceRevealAnim.SCALE_INTERPOLATOR : EMPHASIZED;
|
||||
|
||||
animatorSet.setInterpolator(interpolator);
|
||||
animatorSet.setInterpolator(EMPHASIZED);
|
||||
|
||||
if (start) {
|
||||
animatorSet.start();
|
||||
@@ -751,75 +634,12 @@ public class TaskbarLauncherStateController {
|
||||
return animatorSet;
|
||||
}
|
||||
|
||||
private static boolean shouldShowTaskbar(TaskbarActivityContext activityContext,
|
||||
boolean isInLauncher, boolean isInOverview) {
|
||||
if (activityContext.showDesktopTaskbarForFreeformDisplay()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (activityContext.showLockedTaskbarOnHome() && isInLauncher) {
|
||||
return true;
|
||||
}
|
||||
return !isInLauncher || isInOverview;
|
||||
}
|
||||
|
||||
private void setupPinnedTaskbarAnimation(AnimatorSet animatorSet, boolean showTaskbar,
|
||||
AnimatedFloat taskbarBgOffset, float taskbarBgOffsetStart, float taskbarBgOffsetEnd,
|
||||
long duration, Animator taskbarBackgroundAlpha) {
|
||||
float targetAlpha = !showTaskbar ? 1 : 0;
|
||||
mLauncher.getHotseat().setIconsAlpha(targetAlpha, ALPHA_CHANNEL_TASKBAR_ALIGNMENT);
|
||||
if (mIsQsbInline) {
|
||||
mLauncher.getHotseat().setQsbAlpha(targetAlpha,
|
||||
ALPHA_CHANNEL_TASKBAR_ALIGNMENT);
|
||||
}
|
||||
|
||||
if ((taskbarBgOffset.value != taskbarBgOffsetEnd && !taskbarBgOffset.isAnimating())
|
||||
|| taskbarBgOffset.isAnimatingToValue(taskbarBgOffsetStart)) {
|
||||
taskbarBgOffset.cancelAnimation();
|
||||
Animator taskbarIconAlpha = mTaskbarAlphaForHome.animateToValue(
|
||||
showTaskbar ? 1f : 0f);
|
||||
AnimatedFloat taskbarIconTranslationYForHome =
|
||||
mControllers.taskbarViewController.mTaskbarIconTranslationYForHome;
|
||||
ObjectAnimator taskbarBackgroundOffset = taskbarBgOffset.animateToValue(
|
||||
taskbarBgOffsetStart,
|
||||
taskbarBgOffsetEnd);
|
||||
ObjectAnimator taskbarIconsYTranslation = null;
|
||||
float taskbarHeight =
|
||||
mControllers.taskbarActivityContext.getDeviceProfile().taskbarHeight;
|
||||
if (showTaskbar) {
|
||||
taskbarIconsYTranslation = taskbarIconTranslationYForHome.animateToValue(
|
||||
taskbarHeight, 0);
|
||||
} else {
|
||||
taskbarIconsYTranslation = taskbarIconTranslationYForHome.animateToValue(0,
|
||||
taskbarHeight);
|
||||
}
|
||||
|
||||
taskbarIconAlpha.setDuration(duration);
|
||||
taskbarIconsYTranslation.setDuration(duration);
|
||||
taskbarBackgroundOffset.setDuration(duration);
|
||||
|
||||
animatorSet.play(taskbarIconAlpha);
|
||||
animatorSet.play(taskbarIconsYTranslation);
|
||||
animatorSet.play(taskbarBackgroundOffset);
|
||||
}
|
||||
taskbarBackgroundAlpha.setInterpolator(showTaskbar ? INSTANT : FINAL_FRAME);
|
||||
taskbarBackgroundAlpha.setDuration(duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the taskbar is aligned with the hotseat in the current/target launcher state.
|
||||
*
|
||||
* This refers to the intended state - a transition to this state might be in progress.
|
||||
*/
|
||||
public boolean isTaskbarAlignedWithHotseat() {
|
||||
if (mControllers.taskbarActivityContext.showDesktopTaskbarForFreeformDisplay()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mControllers.taskbarActivityContext.showLockedTaskbarOnHome() && isInLauncher()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return mLauncherState.isTaskbarAlignedWithHotseat(mLauncher);
|
||||
}
|
||||
|
||||
@@ -831,7 +651,8 @@ public class TaskbarLauncherStateController {
|
||||
boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
|
||||
boolean willStashVisually = isInStashedState
|
||||
&& mControllers.taskbarStashController.supportsVisualStashing();
|
||||
boolean isTaskbarAlignedWithHotseat = isTaskbarAlignedWithHotseat();
|
||||
boolean isTaskbarAlignedWithHotseat =
|
||||
mLauncherState.isTaskbarAlignedWithHotseat(mLauncher);
|
||||
return isTaskbarAlignedWithHotseat && !willStashVisually;
|
||||
} else {
|
||||
return false;
|
||||
@@ -850,15 +671,6 @@ public class TaskbarLauncherStateController {
|
||||
return mLauncherState.isRecentsViewVisible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current mLauncherState. Note that this could represent RecentsState as well, as
|
||||
* we convert those to equivalent LauncherStates even if Launcher Activity is not actually in
|
||||
* those states (for the case where the state is represented in a separate Window instead).
|
||||
*/
|
||||
public LauncherState getLauncherState() {
|
||||
return mLauncherState;
|
||||
}
|
||||
|
||||
private void playStateTransitionAnim(AnimatorSet animatorSet, long duration,
|
||||
boolean committed) {
|
||||
boolean isInStashedState = mLauncherState.isTaskbarStashed(mLauncher);
|
||||
@@ -871,17 +683,14 @@ public class TaskbarLauncherStateController {
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
if (isInStashedState && committed) {
|
||||
// Reset hotseat alpha to default
|
||||
mLauncher.getHotseat().setIconsAlpha(1, ALPHA_CHANNEL_TASKBAR_ALIGNMENT);
|
||||
mLauncher.getHotseat().setIconsAlpha(1);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
float hotseatIconsAlpha = mLauncher.getHotseat()
|
||||
.getIconsAlpha(ALPHA_CHANNEL_TASKBAR_ALIGNMENT)
|
||||
.getValue();
|
||||
if (hotseatIconsAlpha > 0) {
|
||||
updateIconAlphaForHome(hotseatIconsAlpha, ALPHA_CHANNEL_TASKBAR_ALIGNMENT);
|
||||
if (mLauncher.getHotseat().getIconsAlpha() > 0) {
|
||||
updateIconAlphaForHome(mLauncher.getHotseat().getIconsAlpha());
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -910,36 +719,6 @@ public class TaskbarLauncherStateController {
|
||||
}
|
||||
}
|
||||
|
||||
protected void stashHotseat(boolean stash) {
|
||||
// align taskbar with the hotseat icons before performing any animation
|
||||
mControllers.taskbarViewController.setLauncherIconAlignment(/* alignmentRatio = */ 1,
|
||||
mLauncher.getDeviceProfile());
|
||||
TaskbarStashController stashController = mControllers.taskbarStashController;
|
||||
stashController.updateStateForFlag(FLAG_STASHED_FOR_BUBBLES, stash);
|
||||
Runnable swapHotseatWithTaskbar = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
updateIconAlphaForHome(stash ? 1 : 0, ALPHA_CHANNEL_TASKBAR_STASH);
|
||||
}
|
||||
};
|
||||
if (stash) {
|
||||
stashController.applyState();
|
||||
// if we stashing the hotseat we need to immediately swap it with the animating taskbar
|
||||
swapHotseatWithTaskbar.run();
|
||||
} else {
|
||||
// if we revert stashing make swap after taskbar animation is complete
|
||||
stashController.applyState(/* postApplyAction = */ swapHotseatWithTaskbar);
|
||||
}
|
||||
}
|
||||
|
||||
protected void unStashHotseatInstantly() {
|
||||
TaskbarStashController stashController = mControllers.taskbarStashController;
|
||||
stashController.updateStateForFlag(FLAG_STASHED_FOR_BUBBLES, false);
|
||||
stashController.applyState(/* duration = */ 0);
|
||||
updateIconAlphaForHome(/* taskbarAlpha = */ 0,
|
||||
ALPHA_CHANNEL_TASKBAR_STASH, /* updateTaskbarAlpha = */ false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets and updates the icon alignment.
|
||||
*/
|
||||
@@ -949,7 +728,7 @@ public class TaskbarLauncherStateController {
|
||||
}
|
||||
|
||||
private void onIconAlignmentRatioChanged() {
|
||||
float currentValue = mTaskbarAlphaForHome.getValue();
|
||||
float currentValue = mIconAlphaForHome.getValue();
|
||||
boolean taskbarWillBeVisible = mIconAlignment.value < 1;
|
||||
boolean firstFrameVisChanged = (taskbarWillBeVisible && Float.compare(currentValue, 1) != 0)
|
||||
|| (!taskbarWillBeVisible && Float.compare(currentValue, 0) != 0);
|
||||
@@ -957,33 +736,24 @@ public class TaskbarLauncherStateController {
|
||||
mControllers.taskbarViewController.setLauncherIconAlignment(
|
||||
mIconAlignment.value, mLauncher.getDeviceProfile());
|
||||
mControllers.navbarButtonsViewController.updateTaskbarAlignment(mIconAlignment.value);
|
||||
// Switch taskbar and hotseat in last frame and if taskbar is not hidden for bubbles
|
||||
boolean isHiddenForBubbles = mControllers.taskbarStashController.isHiddenForBubbles();
|
||||
updateIconAlphaForHome(taskbarWillBeVisible ? 1 : 0, ALPHA_CHANNEL_TASKBAR_ALIGNMENT,
|
||||
/* updateTaskbarAlpha = */ !isHiddenForBubbles);
|
||||
// Switch taskbar and hotseat in last frame
|
||||
updateIconAlphaForHome(taskbarWillBeVisible ? 1 : 0);
|
||||
|
||||
// Sync the first frame where we swap taskbar and hotseat.
|
||||
if (firstFrameVisChanged && mCanSyncViews && !Utilities.isRunningInTestHarness()) {
|
||||
ViewRootSync.synchronizeNextDraw(mLauncher.getHotseat(),
|
||||
mControllers.taskbarActivityContext.getDragLayer(),
|
||||
() -> {});
|
||||
() -> {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void updateIconAlphaForHome(float taskbarAlpha, @HotseatQsbAlphaId int alphaChannel) {
|
||||
updateIconAlphaForHome(taskbarAlpha, alphaChannel, /* updateTaskbarAlpha = */ true);
|
||||
}
|
||||
|
||||
private void updateIconAlphaForHome(float taskbarAlpha,
|
||||
@HotseatQsbAlphaId int alphaChannel,
|
||||
boolean updateTaskbarAlpha) {
|
||||
if (mIsDestroyed) {
|
||||
private void updateIconAlphaForHome(float alpha) {
|
||||
if (mControllers.taskbarActivityContext.isDestroyed()) {
|
||||
return;
|
||||
}
|
||||
if (updateTaskbarAlpha) {
|
||||
mTaskbarAlphaForHome.setValue(taskbarAlpha);
|
||||
}
|
||||
boolean hotseatVisible = taskbarAlpha == 0
|
||||
mIconAlphaForHome.setValue(alpha);
|
||||
boolean hotseatVisible = alpha == 0
|
||||
|| mControllers.taskbarActivityContext.isPhoneMode()
|
||||
|| (!mControllers.uiController.isHotseatIconOnTopWhenAligned()
|
||||
&& mIconAlignment.value > 0);
|
||||
@@ -991,71 +761,12 @@ public class TaskbarLauncherStateController {
|
||||
* Hide Launcher Hotseat icons when Taskbar icons have opacity. Both icon sets
|
||||
* should not be visible at the same time.
|
||||
*/
|
||||
float targetAlpha = hotseatVisible ? 1 : 0;
|
||||
mLauncher.getHotseat().setIconsAlpha(targetAlpha, alphaChannel);
|
||||
mLauncher.getHotseat().setIconsAlpha(hotseatVisible ? 1 : 0);
|
||||
if (mIsQsbInline) {
|
||||
mLauncher.getHotseat().setQsbAlpha(targetAlpha, alphaChannel);
|
||||
mLauncher.getHotseat().setQsbAlpha(hotseatVisible ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
/** Updates launcher home screen appearance accordingly to the bubble bar location. */
|
||||
public void onBubbleBarLocationChanged(@Nullable BubbleBarLocation location, boolean animate) {
|
||||
mBubbleBarLocation = location;
|
||||
if (location == null) {
|
||||
// bubble bar is not present, hence no location, resetting the hotseat
|
||||
updateHotseatAndQsbTranslationX(/* targetValue = */ 0, animate);
|
||||
mBubbleBarLocation = null;
|
||||
return;
|
||||
}
|
||||
DeviceProfile deviceProfile = mLauncher.getDeviceProfile();
|
||||
if (!deviceProfile.shouldAdjustHotseatOnNavBarLocationUpdate(
|
||||
mControllers.taskbarActivityContext)) {
|
||||
return;
|
||||
}
|
||||
boolean isBubblesOnLeft = location.isOnLeft(isRtl(mLauncher.getResources()));
|
||||
int targetX = deviceProfile
|
||||
.getHotseatTranslationXForNavBar(mLauncher, isBubblesOnLeft);
|
||||
updateHotseatAndQsbTranslationX(targetX, animate);
|
||||
}
|
||||
|
||||
/** Used to translate hotseat and QSB to make room for bubbles. */
|
||||
private void updateHotseatAndQsbTranslationX(float targetValue, boolean animate) {
|
||||
// cancel existing animation
|
||||
if (mHotseatTranslationXAnimation != null) {
|
||||
mHotseatTranslationXAnimation.cancel();
|
||||
mHotseatTranslationXAnimation = null;
|
||||
}
|
||||
Hotseat hotseat = mLauncher.getHotseat();
|
||||
AnimatorSet translationXAnimation = new AnimatorSet();
|
||||
MultiProperty iconsTranslationX = mLauncher.getHotseat()
|
||||
.getIconsTranslationX(Hotseat.ICONS_TRANSLATION_X_NAV_BAR_ALIGNMENT);
|
||||
if (animate) {
|
||||
translationXAnimation.playTogether(iconsTranslationX.animateToValue(targetValue));
|
||||
} else {
|
||||
iconsTranslationX.setValue(targetValue);
|
||||
}
|
||||
float qsbTargetX = 0;
|
||||
if (mIsQsbInline) {
|
||||
qsbTargetX = targetValue;
|
||||
}
|
||||
MultiProperty qsbTranslationX = hotseat.getQsbTranslationX();
|
||||
if (qsbTranslationX != null) {
|
||||
if (animate) {
|
||||
translationXAnimation.playTogether(qsbTranslationX.animateToValue(qsbTargetX));
|
||||
} else {
|
||||
qsbTranslationX.setValue(qsbTargetX);
|
||||
}
|
||||
}
|
||||
if (!animate) {
|
||||
return;
|
||||
}
|
||||
mHotseatTranslationXAnimation = translationXAnimation;
|
||||
translationXAnimation.setStartDelay(FADE_OUT_ANIM_POSITION_DURATION_MS);
|
||||
translationXAnimation.setDuration(FADE_IN_ANIM_ALPHA_DURATION_MS);
|
||||
translationXAnimation.setInterpolator(Interpolators.EMPHASIZED);
|
||||
translationXAnimation.start();
|
||||
}
|
||||
|
||||
private final class TaskBarRecentsAnimationListener implements
|
||||
RecentsAnimationCallbacks.RecentsAnimationListener {
|
||||
private final RecentsAnimationCallbacks mCallbacks;
|
||||
@@ -1072,12 +783,7 @@ public class TaskbarLauncherStateController {
|
||||
|
||||
@Override
|
||||
public void onRecentsAnimationFinished(RecentsAnimationController controller) {
|
||||
endGestureStateOverride(!controller.getFinishTargetIsLauncher(),
|
||||
controller.getLauncherIsVisibleAtFinish(), false /*canceled*/);
|
||||
}
|
||||
|
||||
private void endGestureStateOverride(boolean finishedToApp, boolean canceled) {
|
||||
endGestureStateOverride(finishedToApp, finishedToApp, canceled);
|
||||
endGestureStateOverride(!controller.getFinishTargetIsLauncher(), false /*canceled*/);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1087,45 +793,29 @@ public class TaskbarLauncherStateController {
|
||||
*
|
||||
* @param finishedToApp {@code true} if the recents animation finished to showing an app and
|
||||
* not workspace or overview
|
||||
* @param launcherIsVisible {code true} if launcher is visible at finish
|
||||
* @param canceled {@code true} if the recents animation was canceled instead of
|
||||
* finishing
|
||||
* to completion
|
||||
* @param canceled {@code true} if the recents animation was canceled instead of finishing
|
||||
* to completion
|
||||
*/
|
||||
private void endGestureStateOverride(boolean finishedToApp, boolean launcherIsVisible,
|
||||
boolean canceled) {
|
||||
private void endGestureStateOverride(boolean finishedToApp, boolean canceled) {
|
||||
mCallbacks.removeListener(this);
|
||||
mTaskBarRecentsAnimationListener = null;
|
||||
RecentsView recentsView = mControllers.uiController.getRecentsView();
|
||||
if (recentsView != null) {
|
||||
recentsView.setTaskLaunchListener(null);
|
||||
}
|
||||
((RecentsView) mLauncher.getOverviewPanel()).setTaskLaunchListener(null);
|
||||
|
||||
if (mSkipNextRecentsAnimEnd && !canceled) {
|
||||
mSkipNextRecentsAnimEnd = false;
|
||||
return;
|
||||
}
|
||||
updateStateForUserFinishedToApp(finishedToApp, launcherIsVisible);
|
||||
updateStateForUserFinishedToApp(finishedToApp);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see #updateStateForUserFinishedToApp(boolean, boolean)
|
||||
* Updates the visible state immediately to ensure a seamless handoff.
|
||||
* @param finishedToApp True iff user is in an app.
|
||||
*/
|
||||
private void updateStateForUserFinishedToApp(boolean finishedToApp) {
|
||||
updateStateForUserFinishedToApp(finishedToApp, !finishedToApp);
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the visible state immediately to ensure a seamless handoff.
|
||||
*
|
||||
* @param finishedToApp True iff user is in an app.
|
||||
* @param launcherIsVisible True iff launcher is still visible (ie. transparent app)
|
||||
*/
|
||||
private void updateStateForUserFinishedToApp(boolean finishedToApp,
|
||||
boolean launcherIsVisible) {
|
||||
// Update the visible state immediately to ensure a seamless handoff
|
||||
boolean launcherVisible = !finishedToApp || launcherIsVisible;
|
||||
boolean launcherVisible = !finishedToApp;
|
||||
updateStateForFlag(FLAG_TRANSITION_TO_VISIBLE, false);
|
||||
updateStateForFlag(FLAG_VISIBLE, launcherVisible);
|
||||
applyState();
|
||||
@@ -1134,24 +824,10 @@ public class TaskbarLauncherStateController {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "endGestureStateOverride - FLAG_IN_APP: " + finishedToApp);
|
||||
}
|
||||
controller.updateStateForFlag(FLAG_IN_APP, finishedToApp && !launcherIsVisible);
|
||||
controller.updateStateForFlag(FLAG_IN_APP, finishedToApp);
|
||||
controller.applyState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to run a callback on the RecentsWindowManager (if it exists).
|
||||
*/
|
||||
private void runForRecentsWindowManager(Consumer<RecentsWindowManager> callback) {
|
||||
if (RecentsWindowFlags.getEnableOverviewInWindow()) {
|
||||
final TaskbarActivityContext taskbarContext = mControllers.taskbarActivityContext;
|
||||
RecentsWindowManager recentsWindowManager = RecentsDisplayModel.getINSTANCE()
|
||||
.get(taskbarContext).getRecentsWindowManager(taskbarContext.getDisplayId());
|
||||
if (recentsWindowManager != null) {
|
||||
callback.accept(recentsWindowManager);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String getStateString(int flags) {
|
||||
StringJoiner result = new StringJoiner("|");
|
||||
appendFlag(result, flags, FLAG_VISIBLE, "flag_visible");
|
||||
@@ -1175,9 +851,8 @@ public class TaskbarLauncherStateController {
|
||||
pw.println(String.format(
|
||||
"%s\tmTaskbarBackgroundAlpha=%.2f", prefix, mTaskbarBackgroundAlpha.value));
|
||||
pw.println(String.format(
|
||||
"%s\tmTaskbarAlphaForHome=%.2f", prefix, mTaskbarAlphaForHome.getValue()));
|
||||
pw.println(String.format("%s\tmPrevState=%s", prefix,
|
||||
mPrevState == null ? null : getStateString(mPrevState)));
|
||||
"%s\tmIconAlphaForHome=%.2f", prefix, mIconAlphaForHome.getValue()));
|
||||
pw.println(String.format("%s\tmPrevState=%s", prefix, getStateString(mPrevState)));
|
||||
pw.println(String.format("%s\tmState=%s", prefix, getStateString(mState)));
|
||||
pw.println(String.format("%s\tmLauncherState=%s", prefix, mLauncherState));
|
||||
pw.println(String.format(
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -15,10 +15,12 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static com.android.window.flags2.Flags.enableDesktopWindowingMode;
|
||||
import static com.android.window.flags2.Flags.enableDesktopWindowingTaskbarRunningApps;
|
||||
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
@@ -26,6 +28,8 @@ import com.android.launcher3.model.BgDataModel;
|
||||
import com.android.launcher3.model.BgDataModel.FixedContainerItems;
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.statehandlers.DesktopVisibilityController;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.IntArray;
|
||||
import com.android.launcher3.util.IntSet;
|
||||
@@ -33,12 +37,14 @@ import com.android.launcher3.util.ItemInfoMatcher;
|
||||
import com.android.launcher3.util.LauncherBindableItemsContainer;
|
||||
import com.android.launcher3.util.PackageUserKey;
|
||||
import com.android.launcher3.util.Preconditions;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
import com.android.quickstep.LauncherActivityInterface;
|
||||
import com.android.quickstep.RecentsModel;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -48,7 +54,7 @@ import java.util.function.Predicate;
|
||||
* Launcher model Callbacks for rendering taskbar.
|
||||
*/
|
||||
public class TaskbarModelCallbacks implements
|
||||
BgDataModel.Callbacks, LauncherBindableItemsContainer {
|
||||
BgDataModel.Callbacks, LauncherBindableItemsContainer, RecentsModel.RunningTasksListener {
|
||||
|
||||
private final SparseArray<ItemInfo> mHotseatItems = new SparseArray<>();
|
||||
private List<ItemInfo> mPredictedItems = Collections.emptyList();
|
||||
@@ -62,7 +68,8 @@ public class TaskbarModelCallbacks implements
|
||||
// Used to defer any UI updates during the SUW unstash animation.
|
||||
private boolean mDeferUpdatesForSUW;
|
||||
private Runnable mDeferredUpdates;
|
||||
private boolean mBindingItems = false;
|
||||
private final DesktopVisibilityController.DesktopVisibilityListener mDesktopVisibilityListener =
|
||||
visible -> updateRunningApps();
|
||||
|
||||
public TaskbarModelCallbacks(
|
||||
TaskbarActivityContext context, TaskbarView container) {
|
||||
@@ -72,18 +79,51 @@ public class TaskbarModelCallbacks implements
|
||||
|
||||
public void init(TaskbarControllers controllers) {
|
||||
mControllers = controllers;
|
||||
if (mControllers.taskbarRecentAppsController.getCanShowRunningApps()) {
|
||||
RecentsModel.INSTANCE.get(mContext).registerRunningTasksListener(this);
|
||||
|
||||
if (shouldShowRunningAppsInDesktopMode()) {
|
||||
DesktopVisibilityController desktopVisibilityController =
|
||||
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
|
||||
if (desktopVisibilityController != null) {
|
||||
desktopVisibilityController.registerDesktopVisibilityListener(
|
||||
mDesktopVisibilityListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters listeners in this class.
|
||||
*/
|
||||
public void unregisterListeners() {
|
||||
RecentsModel.INSTANCE.get(mContext).unregisterRunningTasksListener();
|
||||
|
||||
if (shouldShowRunningAppsInDesktopMode()) {
|
||||
DesktopVisibilityController desktopVisibilityController =
|
||||
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
|
||||
if (desktopVisibilityController != null) {
|
||||
desktopVisibilityController.unregisterDesktopVisibilityListener(
|
||||
mDesktopVisibilityListener);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldShowRunningAppsInDesktopMode() {
|
||||
// TODO(b/335401172): unify DesktopMode checks in Launcher
|
||||
return enableDesktopWindowingMode() && enableDesktopWindowingTaskbarRunningApps();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startBinding() {
|
||||
mBindingItems = true;
|
||||
mContext.setBindingItems(true);
|
||||
mHotseatItems.clear();
|
||||
mPredictedItems = Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finishBindingItems(IntSet pagesBoundFirst) {
|
||||
mBindingItems = false;
|
||||
mContext.setBindingItems(false);
|
||||
commitItemsToUI();
|
||||
}
|
||||
|
||||
@@ -115,21 +155,26 @@ public class TaskbarModelCallbacks implements
|
||||
return modified;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void bindItemsUpdated(Set<ItemInfo> updates) {
|
||||
updateContainerItems(updates, mContext);
|
||||
public void bindWorkspaceItemsChanged(List<WorkspaceItemInfo> updated) {
|
||||
updateWorkspaceItems(updated, mContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View mapOverItems(@NonNull ItemOperator op) {
|
||||
public void bindRestoreItemsChange(HashSet<ItemInfo> updates) {
|
||||
updateRestoreItems(updates, mContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mapOverItems(ItemOperator op) {
|
||||
final int itemCount = mContainer.getChildCount();
|
||||
for (int itemIdx = 0; itemIdx < itemCount; itemIdx++) {
|
||||
View item = mContainer.getChildAt(itemIdx);
|
||||
if (item.getTag() instanceof ItemInfo itemInfo && op.evaluate(itemInfo, item)) {
|
||||
return item;
|
||||
if (op.evaluate((ItemInfo) item.getTag(), item)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -170,7 +215,7 @@ public class TaskbarModelCallbacks implements
|
||||
}
|
||||
|
||||
private void commitItemsToUI() {
|
||||
if (mBindingItems) {
|
||||
if (mContext.isBindingItems()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -187,26 +232,26 @@ public class TaskbarModelCallbacks implements
|
||||
predictionNextIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
final TaskbarRecentAppsController recentAppsController =
|
||||
mControllers.taskbarRecentAppsController;
|
||||
hotseatItemInfos = recentAppsController.updateHotseatItemInfos(hotseatItemInfos);
|
||||
hotseatItemInfos = mControllers.taskbarRecentAppsController
|
||||
.updateHotseatItemInfos(hotseatItemInfos);
|
||||
Set<String> runningPackages = mControllers.taskbarRecentAppsController.getRunningApps();
|
||||
Set<String> minimizedPackages = mControllers.taskbarRecentAppsController.getMinimizedApps();
|
||||
|
||||
if (mDeferUpdatesForSUW) {
|
||||
ItemInfo[] finalHotseatItemInfos = hotseatItemInfos;
|
||||
mDeferredUpdates = () ->
|
||||
commitHotseatItemUpdates(finalHotseatItemInfos,
|
||||
recentAppsController.getShownTasks());
|
||||
commitHotseatItemUpdates(finalHotseatItemInfos, runningPackages,
|
||||
minimizedPackages);
|
||||
} else {
|
||||
commitHotseatItemUpdates(hotseatItemInfos, recentAppsController.getShownTasks());
|
||||
commitHotseatItemUpdates(hotseatItemInfos, runningPackages, minimizedPackages);
|
||||
}
|
||||
}
|
||||
|
||||
private void commitHotseatItemUpdates(
|
||||
ItemInfo[] hotseatItemInfos, List<GroupTask> recentTasks) {
|
||||
mContainer.updateItems(hotseatItemInfos, recentTasks);
|
||||
mControllers.taskbarViewController.updateIconViewsRunningStates();
|
||||
mControllers.taskbarPopupController.setHotseatInfosList(mHotseatItems);
|
||||
private void commitHotseatItemUpdates(ItemInfo[] hotseatItemInfos, Set<String> runningPackages,
|
||||
Set<String> minimizedPackages) {
|
||||
mContainer.updateHotseatItems(hotseatItemInfos);
|
||||
mControllers.taskbarViewController.updateIconViewsRunningStates(runningPackages,
|
||||
minimizedPackages);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -225,11 +270,21 @@ public class TaskbarModelCallbacks implements
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRunningTasksChanged() {
|
||||
updateRunningApps();
|
||||
}
|
||||
|
||||
/** Called when there's a change in running apps to update the UI. */
|
||||
public void commitRunningAppsToUI() {
|
||||
commitItemsToUI();
|
||||
}
|
||||
|
||||
/** Call TaskbarRecentAppsController to update running apps with mHotseatItems. */
|
||||
public void updateRunningApps() {
|
||||
mControllers.taskbarRecentAppsController.updateRunningApps();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindDeepShortcutMap(HashMap<ComponentKey, Integer> deepShortcutMapCopy) {
|
||||
mControllers.taskbarPopupController.setDeepShortcutMap(deepShortcutMapCopy);
|
||||
@@ -241,7 +296,7 @@ public class TaskbarModelCallbacks implements
|
||||
Map<PackageUserKey, Integer> packageUserKeytoUidMap) {
|
||||
Preconditions.assertUIThread();
|
||||
mControllers.taskbarAllAppsController.setApps(apps, flags, packageUserKeytoUidMap);
|
||||
mControllers.taskbarPopupController.setApps(apps);
|
||||
mControllers.taskbarRecentAppsController.setApps(apps);
|
||||
}
|
||||
|
||||
protected void dumpLogs(String prefix, PrintWriter pw) {
|
||||
|
||||
@@ -16,9 +16,6 @@
|
||||
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static android.view.KeyEvent.ACTION_DOWN;
|
||||
import static android.view.KeyEvent.ACTION_UP;
|
||||
|
||||
import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS;
|
||||
import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_KEY;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_A11Y_BUTTON_LONGPRESS;
|
||||
@@ -27,24 +24,19 @@ import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCH
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_BACK_BUTTON_TAP;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_HOME_BUTTON_TAP;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS;
|
||||
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.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;
|
||||
import static com.android.window.flags.Flags.predictiveBackThreeButtonNav;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Log;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.Flags;
|
||||
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -56,8 +48,7 @@ import com.android.launcher3.testing.TestLogging;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.TaskUtils;
|
||||
import com.android.quickstep.util.ContextualSearchInvoker;
|
||||
import com.android.systemui.contextualeducation.GestureType;
|
||||
import com.android.quickstep.util.AssistUtils;
|
||||
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
@@ -79,7 +70,6 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
|
||||
private long mLastScreenPinLongPress;
|
||||
private boolean mScreenPinned;
|
||||
private boolean mAssistantLongPressEnabled;
|
||||
private int mLastSentBackAction = ACTION_UP;
|
||||
|
||||
@Override
|
||||
public void dumpLogs(String prefix, PrintWriter pw) {
|
||||
@@ -87,8 +77,6 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
|
||||
|
||||
pw.println(prefix + "\tmLastScreenPinLongPress=" + mLastScreenPinLongPress);
|
||||
pw.println(prefix + "\tmScreenPinned=" + mScreenPinned);
|
||||
pw.println(prefix + "\tmLastSentBackAction="
|
||||
+ KeyEvent.actionToString(mLastSentBackAction));
|
||||
}
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@@ -120,7 +108,7 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
|
||||
private final TaskbarNavButtonCallbacks mCallbacks;
|
||||
private final SystemUiProxy mSystemUiProxy;
|
||||
private final Handler mHandler;
|
||||
private final ContextualSearchInvoker mContextualSearchInvoker;
|
||||
private final AssistUtils mAssistUtils;
|
||||
@Nullable private StatsLogManager mStatsLogManager;
|
||||
|
||||
private final Runnable mResetLongPress = this::resetScreenUnpin;
|
||||
@@ -130,48 +118,36 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
|
||||
TaskbarNavButtonCallbacks callbacks,
|
||||
SystemUiProxy systemUiProxy,
|
||||
Handler handler,
|
||||
ContextualSearchInvoker contextualSearchInvoker) {
|
||||
AssistUtils assistUtils) {
|
||||
mContext = context;
|
||||
mCallbacks = callbacks;
|
||||
mSystemUiProxy = systemUiProxy;
|
||||
mHandler = handler;
|
||||
mContextualSearchInvoker = contextualSearchInvoker;
|
||||
mAssistUtils = assistUtils;
|
||||
}
|
||||
|
||||
public void onButtonClick(@TaskbarButton int buttonType, View view) {
|
||||
if (buttonType == BUTTON_SPACE) {
|
||||
return;
|
||||
}
|
||||
boolean predictiveBackThreeButtonNav;
|
||||
try {
|
||||
predictiveBackThreeButtonNav = predictiveBackThreeButtonNav();
|
||||
} catch (Throwable t) {
|
||||
predictiveBackThreeButtonNav = false;
|
||||
}
|
||||
if (predictiveBackThreeButtonNav && mLastSentBackAction == ACTION_DOWN) {
|
||||
Log.i(TAG, "Button click ignored while back button is pressed");
|
||||
// prevent interactions with other buttons while back button is pressed
|
||||
return;
|
||||
}
|
||||
// Provide the same haptic feedback that the system offers for virtual keys.
|
||||
view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
|
||||
switch (buttonType) {
|
||||
case BUTTON_BACK:
|
||||
executeBack(/* keyEvent */ null);
|
||||
logEvent(LAUNCHER_TASKBAR_BACK_BUTTON_TAP);
|
||||
executeBack();
|
||||
break;
|
||||
case BUTTON_HOME:
|
||||
logEvent(LAUNCHER_TASKBAR_HOME_BUTTON_TAP);
|
||||
mSystemUiProxy.updateContextualEduStats(/* isTrackpadGesture= */ false,
|
||||
GestureType.HOME);
|
||||
navigateHome();
|
||||
break;
|
||||
case BUTTON_RECENTS:
|
||||
logEvent(LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP);
|
||||
mSystemUiProxy.updateContextualEduStats(/* isTrackpadGesture= */ false,
|
||||
GestureType.OVERVIEW);
|
||||
navigateToOverview();
|
||||
break;
|
||||
case BUTTON_IME_SWITCH:
|
||||
logEvent(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP);
|
||||
onImeSwitcherPress();
|
||||
showIMESwitcher();
|
||||
break;
|
||||
case BUTTON_A11Y:
|
||||
logEvent(LAUNCHER_TASKBAR_A11Y_BUTTON_TAP);
|
||||
@@ -190,27 +166,8 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
|
||||
if (buttonType == BUTTON_SPACE) {
|
||||
return false;
|
||||
}
|
||||
boolean predictiveBackThreeButtonNav;
|
||||
try {
|
||||
predictiveBackThreeButtonNav = predictiveBackThreeButtonNav();
|
||||
} catch (Throwable t) {
|
||||
predictiveBackThreeButtonNav = false;
|
||||
}
|
||||
if (predictiveBackThreeButtonNav && mLastSentBackAction == ACTION_DOWN
|
||||
&& buttonType != BUTTON_BACK && buttonType != BUTTON_RECENTS) {
|
||||
// prevent interactions with other buttons while back button is pressed (except back
|
||||
// and recents button for screen-unpin action).
|
||||
Log.i(TAG, "Button long click ignored while back button is pressed");
|
||||
return false;
|
||||
}
|
||||
|
||||
// Provide the same haptic feedback that the system offers for long press.
|
||||
// The haptic feedback from long pressing on the home button is handled by circle to search.
|
||||
// There are no haptics for long pressing the back button if predictive back is enabled
|
||||
if (buttonType != BUTTON_HOME
|
||||
&& (!predictiveBackThreeButtonNav || buttonType != BUTTON_BACK)) {
|
||||
view.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||
}
|
||||
// Provide the same haptic feedback that the system offers for virtual keys.
|
||||
view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
|
||||
switch (buttonType) {
|
||||
case BUTTON_HOME:
|
||||
logEvent(LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS);
|
||||
@@ -222,19 +179,11 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
|
||||
return true;
|
||||
case BUTTON_BACK:
|
||||
logEvent(LAUNCHER_TASKBAR_BACK_BUTTON_LONGPRESS);
|
||||
backRecentsLongpress(buttonType);
|
||||
return true;
|
||||
return backRecentsLongpress(buttonType);
|
||||
case BUTTON_RECENTS:
|
||||
logEvent(LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS);
|
||||
backRecentsLongpress(buttonType);
|
||||
return true;
|
||||
return backRecentsLongpress(buttonType);
|
||||
case BUTTON_IME_SWITCH:
|
||||
if (Flags.imeSwitcherRevamp()) {
|
||||
logEvent(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_LONGPRESS);
|
||||
onImeSwitcherLongPress();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
@@ -304,10 +253,6 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
|
||||
}
|
||||
|
||||
private void resetScreenUnpin() {
|
||||
// if only back button was long pressed, navigate back like a single click back behavior.
|
||||
if (mLongPressedButtons == BUTTON_BACK) {
|
||||
executeBack(null);
|
||||
}
|
||||
mLongPressedButtons = 0;
|
||||
mLastScreenPinLongPress = 0;
|
||||
}
|
||||
@@ -350,41 +295,14 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
|
||||
mCallbacks.onToggleOverview();
|
||||
}
|
||||
|
||||
public void hideOverview() {
|
||||
mCallbacks.onHideOverview();
|
||||
private void executeBack() {
|
||||
mSystemUiProxy.onBackPressed();
|
||||
}
|
||||
|
||||
void sendBackKeyEvent(int action, boolean cancelled) {
|
||||
if (action == mLastSentBackAction) {
|
||||
// There must always be an alternating sequence of ACTION_DOWN and ACTION_UP events
|
||||
return;
|
||||
}
|
||||
long time = SystemClock.uptimeMillis();
|
||||
KeyEvent keyEvent = new KeyEvent(time, time, action, KeyEvent.KEYCODE_BACK, 0);
|
||||
if (cancelled) {
|
||||
keyEvent.cancel();
|
||||
}
|
||||
executeBack(keyEvent);
|
||||
}
|
||||
|
||||
private void executeBack(@Nullable KeyEvent keyEvent) {
|
||||
if (keyEvent == null || (keyEvent.getAction() == ACTION_UP && !keyEvent.isCanceled())) {
|
||||
logEvent(LAUNCHER_TASKBAR_BACK_BUTTON_TAP);
|
||||
mSystemUiProxy.updateContextualEduStats(/* isTrackpadGesture= */ false,
|
||||
GestureType.BACK);
|
||||
}
|
||||
mSystemUiProxy.onBackEvent(keyEvent);
|
||||
mLastSentBackAction = keyEvent != null ? keyEvent.getAction() : ACTION_UP;
|
||||
}
|
||||
|
||||
private void onImeSwitcherPress() {
|
||||
private void showIMESwitcher() {
|
||||
mSystemUiProxy.onImeSwitcherPressed();
|
||||
}
|
||||
|
||||
private void onImeSwitcherLongPress() {
|
||||
mSystemUiProxy.onImeSwitcherLongPress();
|
||||
}
|
||||
|
||||
private void notifyA11yClick(boolean longClick) {
|
||||
if (longClick) {
|
||||
mSystemUiProxy.notifyAccessibilityButtonLongClicked();
|
||||
@@ -397,9 +315,8 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
|
||||
if (mScreenPinned || !mAssistantLongPressEnabled) {
|
||||
return;
|
||||
}
|
||||
// Attempt to start Contextual Search, otherwise fall back to SysUi's implementation.
|
||||
if (!mContextualSearchInvoker.tryStartAssistOverride(
|
||||
INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS)) {
|
||||
// Attempt to start Assist with AssistUtils, otherwise fall back to SysUi's implementation.
|
||||
if (!mAssistUtils.tryStartAssistOverride(INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS)) {
|
||||
Bundle args = new Bundle();
|
||||
args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
|
||||
mSystemUiProxy.startAssistant(args);
|
||||
@@ -421,8 +338,5 @@ public class TaskbarNavButtonController implements TaskbarControllers.LoggableTa
|
||||
|
||||
/** Callback invoked when the overview button is pressed. */
|
||||
default void onToggleOverview() {}
|
||||
|
||||
/** Callback invoken when a visible overview needs to be hidden. */
|
||||
default void onHideOverview() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,11 +30,12 @@ import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASK
|
||||
import com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_UNPINNED
|
||||
import com.android.launcher3.taskbar.TaskbarDividerPopupView.Companion.createAndPopulate
|
||||
import java.io.PrintWriter
|
||||
import kotlin.jvm.optionals.getOrNull
|
||||
|
||||
/** Controls taskbar pinning through a popup view. */
|
||||
class TaskbarPinningController(private val context: TaskbarActivityContext) :
|
||||
TaskbarControllers.LoggableTaskbarController {
|
||||
class TaskbarPinningController(
|
||||
private val context: TaskbarActivityContext,
|
||||
private val isInDesktopModeProvider: () -> Boolean,
|
||||
) : TaskbarControllers.LoggableTaskbarController {
|
||||
|
||||
private lateinit var controllers: TaskbarControllers
|
||||
private lateinit var taskbarSharedState: TaskbarSharedState
|
||||
@@ -57,11 +58,7 @@ class TaskbarPinningController(private val context: TaskbarActivityContext) :
|
||||
return
|
||||
}
|
||||
val shouldPinTaskbar =
|
||||
if (
|
||||
controllers.taskbarDesktopModeController.isInDesktopModeAndNotInOverview(
|
||||
context.displayId
|
||||
)
|
||||
) {
|
||||
if (isInDesktopModeProvider()) {
|
||||
!launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)
|
||||
} else {
|
||||
!launcherPrefs.get(TASKBAR_PINNING)
|
||||
@@ -81,10 +78,10 @@ class TaskbarPinningController(private val context: TaskbarActivityContext) :
|
||||
}
|
||||
}
|
||||
|
||||
fun showPinningView(view: View, horizontalPosition: Float = -1f) {
|
||||
fun showPinningView(view: View) {
|
||||
context.isTaskbarWindowFullscreen = true
|
||||
view.post {
|
||||
val popupView = getPopupView(view, horizontalPosition)
|
||||
val popupView = getPopupView(view)
|
||||
popupView.requestFocus()
|
||||
popupView.onCloseCallback = onCloseCallback
|
||||
context.onPopupVisibilityChanged(true)
|
||||
@@ -94,8 +91,8 @@ class TaskbarPinningController(private val context: TaskbarActivityContext) :
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
fun getPopupView(view: View, horizontalPosition: Float = -1f): TaskbarDividerPopupView<*> {
|
||||
return createAndPopulate(view, context, horizontalPosition)
|
||||
fun getPopupView(view: View): TaskbarDividerPopupView<*> {
|
||||
return createAndPopulate(view, context)
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
@@ -122,13 +119,9 @@ class TaskbarPinningController(private val context: TaskbarActivityContext) :
|
||||
dragLayerController.taskbarBackgroundProgress.animateToValue(animateToValue),
|
||||
taskbarViewController.taskbarIconTranslationYForPinning.animateToValue(animateToValue),
|
||||
taskbarViewController.taskbarIconScaleForPinning.animateToValue(animateToValue),
|
||||
taskbarViewController.taskbarIconTranslationXForPinning.animateToValue(animateToValue),
|
||||
taskbarViewController.taskbarIconTranslationXForPinning.animateToValue(animateToValue)
|
||||
)
|
||||
controllers.bubbleControllers.getOrNull()?.bubbleBarViewController?.let {
|
||||
// if bubble bar is not visible no need to add it`s animations
|
||||
if (!it.isBubbleBarVisible) return@let
|
||||
animatorSet.playTogether(it.bubbleBarPinning.animateToValue(animateToValue))
|
||||
}
|
||||
|
||||
animatorSet.interpolator = Interpolators.EMPHASIZED
|
||||
return animatorSet
|
||||
}
|
||||
@@ -141,14 +134,10 @@ class TaskbarPinningController(private val context: TaskbarActivityContext) :
|
||||
@VisibleForTesting
|
||||
fun recreateTaskbarAndUpdatePinningValue() {
|
||||
updateIsAnimatingTaskbarPinningAndNotifyTaskbarDragLayer(false)
|
||||
if (
|
||||
controllers.taskbarDesktopModeController.isInDesktopModeAndNotInOverview(
|
||||
context.displayId
|
||||
)
|
||||
) {
|
||||
if (isInDesktopModeProvider()) {
|
||||
launcherPrefs.put(
|
||||
TASKBAR_PINNING_IN_DESKTOP_MODE,
|
||||
!launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE),
|
||||
!launcherPrefs.get(TASKBAR_PINNING_IN_DESKTOP_MODE)
|
||||
)
|
||||
} else {
|
||||
launcherPrefs.put(TASKBAR_PINNING, !launcherPrefs.get(TASKBAR_PINNING))
|
||||
|
||||
@@ -15,29 +15,26 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_ALL_APPS;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
|
||||
import static com.android.launcher3.model.data.AppInfo.COMPONENT_KEY_COMPARATOR;
|
||||
import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventForPosition;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.graphics.Point;
|
||||
import android.util.Pair;
|
||||
import android.util.SparseArray;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.logging.InstanceId;
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.BubbleTextView;
|
||||
import com.android.launcher3.Flags;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.model.data.AppInfo;
|
||||
import com.android.launcher3.dot.FolderDotInfo;
|
||||
import com.android.launcher3.folder.Folder;
|
||||
import com.android.launcher3.folder.FolderIcon;
|
||||
import com.android.launcher3.model.data.FolderInfo;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.notification.NotificationListener;
|
||||
@@ -47,21 +44,19 @@ import com.android.launcher3.popup.SystemShortcut;
|
||||
import com.android.launcher3.shortcuts.DeepShortcutView;
|
||||
import com.android.launcher3.splitscreen.SplitShortcut;
|
||||
import com.android.launcher3.util.ComponentKey;
|
||||
import com.android.launcher3.util.LauncherBindableItemsContainer;
|
||||
import com.android.launcher3.util.PackageUserKey;
|
||||
import com.android.launcher3.util.ShortcutUtil;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.util.LogUtils;
|
||||
import com.android.quickstep.util.SingleTask;
|
||||
import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
|
||||
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
@@ -74,24 +69,16 @@ public class TaskbarPopupController implements TaskbarControllers.LoggableTaskba
|
||||
private static final SystemShortcut.Factory<BaseTaskbarContext>
|
||||
APP_INFO = SystemShortcut.AppInfo::new;
|
||||
|
||||
private static final SystemShortcut.Factory<BaseTaskbarContext>
|
||||
BUBBLE = SystemShortcut.BubbleShortcut::new;
|
||||
|
||||
private final TaskbarActivityContext mContext;
|
||||
private final PopupDataProvider mPopupDataProvider;
|
||||
|
||||
// Initialized in init.
|
||||
private TaskbarControllers mControllers;
|
||||
private boolean mAllowInitialSplitSelection;
|
||||
private AppInfo[] mAppInfosList = AppInfo.EMPTY_ARRAY;
|
||||
// Saves the ItemInfos in the hotseat without the predicted items.
|
||||
private SparseArray<ItemInfo> mHotseatInfosList;
|
||||
private ManageWindowsTaskbarShortcut<BaseTaskbarContext> mManageWindowsTaskbarShortcut;
|
||||
|
||||
|
||||
public TaskbarPopupController(TaskbarActivityContext context) {
|
||||
mContext = context;
|
||||
mPopupDataProvider = new PopupDataProvider(mContext);
|
||||
mPopupDataProvider = new PopupDataProvider(this::updateNotificationDots);
|
||||
}
|
||||
|
||||
public void init(TaskbarControllers controllers) {
|
||||
@@ -113,23 +100,43 @@ public class TaskbarPopupController implements TaskbarControllers.LoggableTaskba
|
||||
mPopupDataProvider.setDeepShortcutMap(deepShortcutMapCopy);
|
||||
}
|
||||
|
||||
/** Closes the multi-instance menu if it is enabled and currently open. */
|
||||
public void maybeCloseMultiInstanceMenu() {
|
||||
if (Flags.enableMultiInstanceMenuTaskbar() && mManageWindowsTaskbarShortcut != null) {
|
||||
mManageWindowsTaskbarShortcut.closeMultiInstanceMenu();
|
||||
cleanUpMultiInstanceMenuReference();
|
||||
}
|
||||
}
|
||||
|
||||
/** Releases the reference to the Taskbar multi-instance menu */
|
||||
public void cleanUpMultiInstanceMenuReference() {
|
||||
mManageWindowsTaskbarShortcut = null;
|
||||
}
|
||||
|
||||
public void setAllowInitialSplitSelection(boolean allowInitialSplitSelection) {
|
||||
mAllowInitialSplitSelection = allowInitialSplitSelection;
|
||||
}
|
||||
|
||||
private void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
|
||||
final PackageUserKey packageUserKey = new PackageUserKey(null, null);
|
||||
Predicate<ItemInfo> matcher = info -> !packageUserKey.updateFromItemInfo(info)
|
||||
|| updatedDots.test(packageUserKey);
|
||||
|
||||
LauncherBindableItemsContainer.ItemOperator op = (info, v) -> {
|
||||
if (info instanceof WorkspaceItemInfo && v instanceof BubbleTextView) {
|
||||
if (matcher.test(info)) {
|
||||
((BubbleTextView) v).applyDotState(info, true /* animate */);
|
||||
}
|
||||
} else if (info instanceof FolderInfo && v instanceof FolderIcon) {
|
||||
FolderInfo fi = (FolderInfo) info;
|
||||
if (fi.anyMatch(matcher)) {
|
||||
FolderDotInfo folderDotInfo = new FolderDotInfo();
|
||||
for (ItemInfo si : fi.getContents()) {
|
||||
folderDotInfo.addDotInfo(mPopupDataProvider.getDotInfoForItem(si));
|
||||
}
|
||||
((FolderIcon) v).setDotInfo(folderDotInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// process all the shortcuts
|
||||
return false;
|
||||
};
|
||||
|
||||
mControllers.taskbarViewController.mapOverItems(op);
|
||||
Folder folder = Folder.getOpen(mContext);
|
||||
if (folder != null) {
|
||||
folder.iterateOverItems(op);
|
||||
}
|
||||
mControllers.taskbarAllAppsController.updateNotificationDots(updatedDots);
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the notifications and deep shortcuts associated with a Taskbar {@param icon}.
|
||||
* @return the container if shown or null.
|
||||
@@ -141,35 +148,22 @@ public class TaskbarPopupController implements TaskbarControllers.LoggableTaskba
|
||||
icon.clearFocus();
|
||||
return null;
|
||||
}
|
||||
|
||||
ItemInfo itemInfo;
|
||||
if (icon.getTag() instanceof ItemInfo item && ShortcutUtil.supportsShortcuts(item)) {
|
||||
itemInfo = item;
|
||||
} else if (icon.getTag() instanceof SingleTask task) {
|
||||
itemInfo = SingleTask.Companion.createTaskItemInfo(task);
|
||||
} else {
|
||||
ItemInfo item = (ItemInfo) icon.getTag();
|
||||
if (!ShortcutUtil.supportsShortcuts(item)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
PopupContainerWithArrow<BaseTaskbarContext> container;
|
||||
int deepShortcutCount = mPopupDataProvider.getShortcutCountForItem(itemInfo);
|
||||
int deepShortcutCount = mPopupDataProvider.getShortcutCountForItem(item);
|
||||
// TODO(b/198438631): add support for INSTALL shortcut factory
|
||||
List<SystemShortcut> systemShortcuts = getSystemShortcuts()
|
||||
.map(s -> s.getShortcut(context, itemInfo, icon))
|
||||
.map(s -> s.getShortcut(context, item, icon))
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// TODO(b/375648361): Revisit to see if this can be implemented within getSystemShortcuts().
|
||||
if (Flags.enablePinningAppWithContextMenu()) {
|
||||
SystemShortcut shortcut = createPinShortcut(context, itemInfo, icon);
|
||||
if (shortcut != null) {
|
||||
systemShortcuts.add(0, shortcut);
|
||||
}
|
||||
}
|
||||
|
||||
container = (PopupContainerWithArrow) context.getLayoutInflater().inflate(
|
||||
R.layout.popup_container, context.getDragLayer(), false);
|
||||
container.populateAndShowRows(icon, itemInfo, deepShortcutCount, systemShortcuts);
|
||||
container.populateAndShowRows(icon, deepShortcutCount, systemShortcuts);
|
||||
|
||||
// TODO (b/198438631): configure for taskbar/context
|
||||
container.setPopupItemDragHandler(new TaskbarPopupItemDragHandler());
|
||||
@@ -187,45 +181,11 @@ public class TaskbarPopupController implements TaskbarControllers.LoggableTaskba
|
||||
|
||||
// Create a Stream of all applicable system shortcuts
|
||||
private Stream<SystemShortcut.Factory> getSystemShortcuts() {
|
||||
// append split options to APP_INFO shortcut if not in Desktop Windowing mode, the order
|
||||
// here will reflect in the popup
|
||||
ArrayList<SystemShortcut.Factory> shortcuts = new ArrayList<>();
|
||||
shortcuts.add(APP_INFO);
|
||||
if (!mControllers.taskbarDesktopModeController
|
||||
.isInDesktopModeAndNotInOverview(mContext.getDisplayId())) {
|
||||
shortcuts.addAll(mControllers.uiController.getSplitMenuOptions().toList());
|
||||
}
|
||||
if (BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
|
||||
shortcuts.add(BUBBLE);
|
||||
}
|
||||
|
||||
if (Flags.enableMultiInstanceMenuTaskbar()
|
||||
&& DesktopModeStatus.canEnterDesktopMode(mContext)
|
||||
&& !mControllers.taskbarStashController.isInOverview()) {
|
||||
maybeCloseMultiInstanceMenu();
|
||||
shortcuts.addAll(getMultiInstanceMenuOptions().toList());
|
||||
}
|
||||
return shortcuts.stream();
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private SystemShortcut createPinShortcut(BaseTaskbarContext target, ItemInfo itemInfo,
|
||||
BubbleTextView originalView) {
|
||||
// Predicted items use {@code HotseatPredictionController.PinPrediction} shortcut to pin.
|
||||
if (itemInfo.isPredictedItem()) {
|
||||
return null;
|
||||
}
|
||||
if (itemInfo.container == CONTAINER_HOTSEAT) {
|
||||
return new PinToTaskbarShortcut<>(target, itemInfo, originalView, false,
|
||||
mHotseatInfosList);
|
||||
}
|
||||
if (mHotseatInfosList.size()
|
||||
< mContext.getTaskbarSpecsEvaluator().getNumShownHotseatIcons()) {
|
||||
return new PinToTaskbarShortcut<>(target, itemInfo, originalView, true,
|
||||
mHotseatInfosList);
|
||||
}
|
||||
|
||||
return null;
|
||||
// append split options to APP_INFO shortcut, the order here will reflect in the popup
|
||||
return Stream.concat(
|
||||
Stream.of(APP_INFO),
|
||||
mControllers.uiController.getSplitMenuOptions()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -288,83 +248,7 @@ public class TaskbarPopupController implements TaskbarControllers.LoggableTaskba
|
||||
originalView, position, mAllowInitialSplitSelection);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the list of AppInfos to be able to pull from later
|
||||
*/
|
||||
public void setApps(AppInfo[] apps) {
|
||||
mAppInfosList = apps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds and returns an AppInfo object from a list, using its ComponentKey for identification.
|
||||
* Based off of {@link com.android.launcher3.allapps.AllAppsStore#getApp(ComponentKey)}
|
||||
* since we cannot access AllAppsStore from here.
|
||||
*/
|
||||
public AppInfo getApp(ComponentKey key) {
|
||||
if (key == null) {
|
||||
return null;
|
||||
}
|
||||
AppInfo tempInfo = new AppInfo();
|
||||
tempInfo.componentName = key.componentName;
|
||||
tempInfo.user = key.user;
|
||||
int index = Arrays.binarySearch(mAppInfosList, tempInfo, COMPONENT_KEY_COMPARATOR);
|
||||
return index < 0 ? null : mAppInfosList[index];
|
||||
}
|
||||
|
||||
public void setHotseatInfosList(SparseArray<ItemInfo> info) {
|
||||
mHotseatInfosList = info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stream of Multi Instance menu options if an app supports it.
|
||||
*/
|
||||
Stream<SystemShortcut.Factory<BaseTaskbarContext>> getMultiInstanceMenuOptions() {
|
||||
SystemShortcut.Factory<BaseTaskbarContext> f1 = createNewWindowShortcutFactory();
|
||||
SystemShortcut.Factory<BaseTaskbarContext> f2 = createManageWindowsShortcutFactory();
|
||||
return f1 != null ? Stream.of(f1, f2) : Stream.empty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a factory function representing a "New Window" menu item only if the calling app
|
||||
* supports multi-instance.
|
||||
* @return A factory function to be used in populating the long-press menu.
|
||||
*/
|
||||
SystemShortcut.Factory<BaseTaskbarContext> createNewWindowShortcutFactory() {
|
||||
return (context, itemInfo, originalView) -> {
|
||||
if (shouldShowMultiInstanceOptions(itemInfo)) {
|
||||
return new NewWindowTaskbarShortcut<>(context, itemInfo, originalView);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a factory function representing a "Manage Windows" menu item only if the calling app
|
||||
* supports multi-instance. This menu item shows the open instances of the calling app.
|
||||
* @return A factory function to be used in populating the long-press menu.
|
||||
*/
|
||||
public SystemShortcut.Factory<BaseTaskbarContext> createManageWindowsShortcutFactory() {
|
||||
return (context, itemInfo, originalView) -> {
|
||||
if (shouldShowMultiInstanceOptions(itemInfo)) {
|
||||
mManageWindowsTaskbarShortcut = new ManageWindowsTaskbarShortcut<>(
|
||||
context, itemInfo, originalView, mControllers);
|
||||
return mManageWindowsTaskbarShortcut;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether to show multi-instance options for a given item.
|
||||
*/
|
||||
private boolean shouldShowMultiInstanceOptions(ItemInfo itemInfo) {
|
||||
ComponentKey key = itemInfo.getComponentKey();
|
||||
AppInfo app = getApp(key);
|
||||
return app != null && app.supportsMultiInstance()
|
||||
&& itemInfo.container != CONTAINER_ALL_APPS;
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* A single menu item ("Split left," "Split right," or "Split top") that executes a split
|
||||
* from the taskbar, as if the user performed a drag and drop split.
|
||||
* Includes an onClick method that initiates the actual split.
|
||||
|
||||
@@ -15,23 +15,18 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar
|
||||
|
||||
import android.content.Context
|
||||
import android.window.DesktopModeFlags
|
||||
import android.app.ActivityManager.RunningTaskInfo
|
||||
import android.app.WindowConfiguration
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import com.android.launcher3.BubbleTextView.RunningAppState
|
||||
import com.android.launcher3.Flags
|
||||
import com.android.launcher3.Flags.enableRecentsInTaskbar
|
||||
import com.android.launcher3.model.data.AppInfo
|
||||
import com.android.launcher3.model.data.ItemInfo
|
||||
import com.android.launcher3.model.data.TaskItemInfo
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo
|
||||
import com.android.launcher3.statehandlers.DesktopVisibilityController
|
||||
import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController
|
||||
import com.android.launcher3.util.CancellableTask
|
||||
import com.android.quickstep.RecentsFilterState
|
||||
import com.android.quickstep.RecentsModel
|
||||
import com.android.quickstep.util.DesktopTask
|
||||
import com.android.quickstep.util.GroupTask
|
||||
import com.android.quickstep.util.SingleTask
|
||||
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
|
||||
import com.android.window.flags2.Flags.enableDesktopWindowingMode
|
||||
import com.android.window.flags2.Flags.enableDesktopWindowingTaskbarRunningApps
|
||||
import java.io.PrintWriter
|
||||
|
||||
/**
|
||||
@@ -39,406 +34,153 @@ import java.io.PrintWriter
|
||||
* - When in Fullscreen mode: show the N most recent Tasks
|
||||
* - When in Desktop Mode: show the currently running (open) Tasks
|
||||
*/
|
||||
class TaskbarRecentAppsController(context: Context, private val recentsModel: RecentsModel) :
|
||||
LoggableTaskbarController {
|
||||
class TaskbarRecentAppsController(
|
||||
private val recentsModel: RecentsModel,
|
||||
// Pass a provider here instead of the actual DesktopVisibilityController instance since that
|
||||
// instance might not be available when this constructor is called.
|
||||
private val desktopVisibilityControllerProvider: () -> DesktopVisibilityController?,
|
||||
) : LoggableTaskbarController {
|
||||
|
||||
var canShowRunningApps =
|
||||
DesktopModeStatus.canEnterDesktopMode(context) &&
|
||||
DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_TASKBAR_RUNNING_APPS.isTrue
|
||||
@VisibleForTesting
|
||||
set(isEnabledFromTest) {
|
||||
field = isEnabledFromTest
|
||||
if (!field && !canShowRecentApps) {
|
||||
recentsModel.unregisterRecentTasksChangedListener()
|
||||
}
|
||||
}
|
||||
// TODO(b/335401172): unify DesktopMode checks in Launcher.
|
||||
val canShowRunningApps =
|
||||
enableDesktopWindowingMode() && enableDesktopWindowingTaskbarRunningApps()
|
||||
|
||||
// TODO(b/343532825): Add a setting to disable Recents even when the flag is on.
|
||||
var canShowRecentApps = enableRecentsInTaskbar()
|
||||
var isEnabled: Boolean = enableRecentsInTaskbar() || canShowRunningApps
|
||||
@VisibleForTesting
|
||||
set(isEnabledFromTest) {
|
||||
set(isEnabledFromTest){
|
||||
field = isEnabledFromTest
|
||||
if (!field && !canShowRunningApps) {
|
||||
recentsModel.unregisterRecentTasksChangedListener()
|
||||
}
|
||||
}
|
||||
|
||||
// Initialized in init.
|
||||
private lateinit var controllers: TaskbarControllers
|
||||
|
||||
var shownHotseatItems: List<ItemInfo> = emptyList()
|
||||
private set
|
||||
private var apps: Array<AppInfo>? = null
|
||||
private var allRunningDesktopAppInfos: List<AppInfo>? = null
|
||||
private var allMinimizedDesktopAppInfos: List<AppInfo>? = null
|
||||
|
||||
private var allRecentTasks: List<GroupTask> = emptyList()
|
||||
private var desktopTask: DesktopTask? = null
|
||||
// Keeps track of the order in which running tasks appear.
|
||||
private var orderedRunningTaskIds = emptyList<Int>()
|
||||
var shownTasks: List<GroupTask> = emptyList()
|
||||
private set
|
||||
private val desktopVisibilityController: DesktopVisibilityController?
|
||||
get() = desktopVisibilityControllerProvider()
|
||||
|
||||
val shownTaskIds: List<Int>
|
||||
get() = shownTasks.flatMap { shownTask -> shownTask.tasks }.map { it.key.id }
|
||||
private val isInDesktopMode: Boolean
|
||||
get() = desktopVisibilityController?.areDesktopTasksVisible() ?: false
|
||||
|
||||
/**
|
||||
* The task-state of an app, i.e. whether the app has a task and what state that task is in.
|
||||
*
|
||||
* @property taskId The ID of the task if one exists (i.e. if the state is RUNNING or
|
||||
* MINIMIZED), null otherwise (NOT_RUNNING).
|
||||
*/
|
||||
data class TaskState(val runningAppState: RunningAppState, val taskId: Int? = null)
|
||||
|
||||
/**
|
||||
* Returns the state of the most active Desktop task represented by the given [ItemInfo].
|
||||
*
|
||||
* If there are several tasks represented by the same [ItemInfo] we return the most active one,
|
||||
* i.e. we return [DesktopAppState.RUNNING] over [DesktopAppState.MINIMIZED], and
|
||||
* [DesktopAppState.MINIMIZED] over [DesktopAppState.NOT_RUNNING].
|
||||
*/
|
||||
fun getDesktopItemState(itemInfo: ItemInfo?): TaskState {
|
||||
val packageName =
|
||||
itemInfo?.getTargetPackage() ?: return TaskState(RunningAppState.NOT_RUNNING)
|
||||
return getDesktopTaskState(packageName, itemInfo.user.identifier)
|
||||
}
|
||||
|
||||
private fun getDesktopTaskState(packageName: String, userId: Int): TaskState {
|
||||
val tasks = desktopTask?.tasks ?: return TaskState(RunningAppState.NOT_RUNNING)
|
||||
val appTasks =
|
||||
tasks.filter { task ->
|
||||
packageName == task.key.packageName && task.key.userId == userId
|
||||
}
|
||||
val runningTask = appTasks.find { getRunningAppState(it.key.id) == RunningAppState.RUNNING }
|
||||
if (runningTask != null) {
|
||||
return TaskState(RunningAppState.RUNNING, runningTask.key.id)
|
||||
}
|
||||
val minimizedTask =
|
||||
appTasks.find { getRunningAppState(it.key.id) == RunningAppState.MINIMIZED }
|
||||
if (minimizedTask != null) {
|
||||
return TaskState(RunningAppState.MINIMIZED, taskId = minimizedTask.key.id)
|
||||
}
|
||||
return TaskState(RunningAppState.NOT_RUNNING)
|
||||
}
|
||||
|
||||
/** Get the [RunningAppState] for the given task. */
|
||||
fun getRunningAppState(taskId: Int): RunningAppState {
|
||||
return when (taskId) {
|
||||
in minimizedTaskIds -> RunningAppState.MINIMIZED
|
||||
in runningTaskIds -> RunningAppState.RUNNING
|
||||
else -> RunningAppState.NOT_RUNNING
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
val runningTaskIds: Set<Int>
|
||||
/**
|
||||
* Returns the task IDs of apps that should be indicated as "running" to the user.
|
||||
* Specifically, we return all the open tasks if we are in Desktop mode, else emptySet().
|
||||
*/
|
||||
val runningApps: Set<String>
|
||||
get() {
|
||||
if (
|
||||
!canShowRunningApps ||
|
||||
!controllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()
|
||||
) {
|
||||
if (!isEnabled || !isInDesktopMode) {
|
||||
return emptySet()
|
||||
}
|
||||
val tasks = desktopTask?.tasks ?: return emptySet()
|
||||
return tasks.map { task -> task.key.id }.toSet()
|
||||
return allRunningDesktopAppInfos?.mapNotNull { it.targetPackage }?.toSet() ?: emptySet()
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
val minimizedTaskIds: Set<Int>
|
||||
/**
|
||||
* Returns the task IDs for the tasks that should be indicated as "minimized" to the user.
|
||||
*/
|
||||
val minimizedApps: Set<String>
|
||||
get() {
|
||||
if (
|
||||
!canShowRunningApps ||
|
||||
!controllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()
|
||||
) {
|
||||
if (!isInDesktopMode) {
|
||||
return emptySet()
|
||||
}
|
||||
val desktopTasks = desktopTask?.tasks ?: return emptySet()
|
||||
return desktopTasks.filter { !it.isVisible }.map { task -> task.key.id }.toSet()
|
||||
return allMinimizedDesktopAppInfos?.mapNotNull { it.targetPackage }?.toSet()
|
||||
?: emptySet()
|
||||
}
|
||||
|
||||
private val recentTasksChangedListener =
|
||||
RecentsModel.RecentTasksChangedListener { reloadRecentTasksIfNeeded() }
|
||||
|
||||
private val iconLoadRequests: MutableSet<CancellableTask<*>> = HashSet()
|
||||
|
||||
// TODO(b/343291428): add TaskVisualsChangListener as well (for calendar/clock?)
|
||||
|
||||
// Used to keep track of the last requested task list ID, so that we do not request to load the
|
||||
// tasks again if we have already requested it and the task list has not changed
|
||||
private var taskListChangeId = -1
|
||||
|
||||
fun init(taskbarControllers: TaskbarControllers) {
|
||||
controllers = taskbarControllers
|
||||
if (canShowRunningApps || canShowRecentApps) {
|
||||
recentsModel.registerRecentTasksChangedListener(recentTasksChangedListener)
|
||||
controllers.runAfterInit { reloadRecentTasksIfNeeded() }
|
||||
}
|
||||
}
|
||||
|
||||
fun onDestroy() {
|
||||
recentsModel.unregisterRecentTasksChangedListener()
|
||||
iconLoadRequests.forEach { it.cancel() }
|
||||
iconLoadRequests.clear()
|
||||
apps = null
|
||||
}
|
||||
|
||||
/** Stores the current [AppInfo] instances, no-op except in desktop environment. */
|
||||
fun setApps(apps: Array<AppInfo>?) {
|
||||
this.apps = apps
|
||||
}
|
||||
|
||||
/** Called to update hotseatItems, in order to de-dupe them from Recent/Running tasks later. */
|
||||
// TODO(next CL): add new section of Tasks instead of changing Hotseat items
|
||||
fun updateHotseatItemInfos(hotseatItems: Array<ItemInfo?>): Array<ItemInfo?> {
|
||||
// Ignore predicted apps - we show running or recent apps instead.
|
||||
val showDesktopTasks =
|
||||
controllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()
|
||||
val removePredictions =
|
||||
(showDesktopTasks && canShowRunningApps) || (!showDesktopTasks && canShowRecentApps)
|
||||
if (!removePredictions) {
|
||||
shownHotseatItems = hotseatItems.filterNotNull()
|
||||
onRecentsOrHotseatChanged()
|
||||
if (!isEnabled || !isInDesktopMode) {
|
||||
return hotseatItems
|
||||
}
|
||||
shownHotseatItems =
|
||||
val newHotseatItemInfos =
|
||||
hotseatItems
|
||||
.filterNotNull()
|
||||
// Ignore predicted apps - we show running apps instead
|
||||
.filter { itemInfo -> !itemInfo.isPredictedItem }
|
||||
.toMutableList()
|
||||
|
||||
if (showDesktopTasks && canShowRunningApps) {
|
||||
shownHotseatItems =
|
||||
updateHotseatItemsFromRunningTasks(
|
||||
getOrderedAndWrappedDesktopTasks(),
|
||||
shownHotseatItems,
|
||||
)
|
||||
}
|
||||
|
||||
onRecentsOrHotseatChanged()
|
||||
|
||||
return shownHotseatItems.toTypedArray()
|
||||
}
|
||||
|
||||
private fun getOrderedAndWrappedDesktopTasks(): List<SingleTask> {
|
||||
val tasks = desktopTask?.tasks ?: emptyList()
|
||||
// We wrap each task in the Desktop as a `SingleTask`.
|
||||
val orderFromId = orderedRunningTaskIds.withIndex().associate { (index, id) -> id to index }
|
||||
val sortedTasks = tasks.sortedWith(compareBy(nullsLast()) { orderFromId[it.key.id] })
|
||||
return sortedTasks.map { SingleTask(it) }
|
||||
}
|
||||
|
||||
private fun reloadRecentTasksIfNeeded() {
|
||||
if (!recentsModel.isTaskListValid(taskListChangeId)) {
|
||||
taskListChangeId =
|
||||
recentsModel.getTasks(
|
||||
{ tasks ->
|
||||
allRecentTasks = tasks
|
||||
val oldRunningTaskdIds = runningTaskIds
|
||||
val oldMinimizedTaskIds = minimizedTaskIds
|
||||
desktopTask = allRecentTasks.filterIsInstance<DesktopTask>().firstOrNull()
|
||||
val runningTasksChanged = oldRunningTaskdIds != runningTaskIds
|
||||
val minimizedTasksChanged = oldMinimizedTaskIds != minimizedTaskIds
|
||||
if (
|
||||
onRecentsOrHotseatChanged() ||
|
||||
runningTasksChanged ||
|
||||
minimizedTasksChanged
|
||||
) {
|
||||
controllers.taskbarViewController.commitRunningAppsToUI()
|
||||
}
|
||||
},
|
||||
RecentsFilterState.EMPTY_FILTER,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates [shownTasks] when Recents or Hotseat changes.
|
||||
*
|
||||
* @return Whether [shownTasks] changed.
|
||||
*/
|
||||
private fun onRecentsOrHotseatChanged(): Boolean {
|
||||
val oldShownTasks = shownTasks
|
||||
orderedRunningTaskIds = updateOrderedRunningTaskIds()
|
||||
shownTasks =
|
||||
if (controllers.taskbarDesktopModeController.shouldShowDesktopTasksInTaskbar()) {
|
||||
computeShownRunningTasks()
|
||||
} else {
|
||||
computeShownRecentTasks()
|
||||
val runningDesktopAppInfos =
|
||||
allRunningDesktopAppInfos?.let {
|
||||
getRunningDesktopAppInfosExceptHotseatApps(it, newHotseatItemInfos.toList())
|
||||
}
|
||||
val shownTasksChanged = oldShownTasks != shownTasks
|
||||
if (!shownTasksChanged) {
|
||||
return shownTasksChanged
|
||||
if (runningDesktopAppInfos != null) {
|
||||
newHotseatItemInfos.addAll(runningDesktopAppInfos)
|
||||
}
|
||||
|
||||
for (groupTask in shownTasks) {
|
||||
for (task in groupTask.tasks) {
|
||||
val cancellableTask =
|
||||
recentsModel.iconCache.getIconInBackground(task) {
|
||||
icon,
|
||||
contentDescription,
|
||||
title ->
|
||||
task.icon = icon
|
||||
task.titleDescription = contentDescription
|
||||
task.title = title
|
||||
controllers.taskbarViewController.onTaskUpdated(task)
|
||||
}
|
||||
if (cancellableTask != null) {
|
||||
iconLoadRequests.add(cancellableTask)
|
||||
}
|
||||
}
|
||||
}
|
||||
return shownTasksChanged
|
||||
return newHotseatItemInfos.toTypedArray()
|
||||
}
|
||||
|
||||
private fun updateOrderedRunningTaskIds(): MutableList<Int> {
|
||||
val desktopTasksAsList = getOrderedAndWrappedDesktopTasks().map { it.task }
|
||||
val desktopTaskIds = desktopTasksAsList.map { it.key.id }
|
||||
var newOrder =
|
||||
orderedRunningTaskIds
|
||||
.filter { it in desktopTaskIds } // Only keep the tasks that are still running
|
||||
.toMutableList()
|
||||
// Add new tasks not already listed
|
||||
newOrder.addAll(desktopTaskIds.filter { it !in newOrder })
|
||||
return newOrder
|
||||
private fun getRunningDesktopAppInfosExceptHotseatApps(
|
||||
allRunningDesktopAppInfos: List<AppInfo>,
|
||||
hotseatItems: List<ItemInfo>
|
||||
): List<ItemInfo> {
|
||||
val hotseatPackages = hotseatItems.map { it.targetPackage }
|
||||
return allRunningDesktopAppInfos
|
||||
.filter { appInfo -> !hotseatPackages.contains(appInfo.targetPackage) }
|
||||
.map { WorkspaceItemInfo(it) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the list of running tasks to be shown in the recent apps section of the taskbar in
|
||||
* desktop mode, taking into account deduplication against hotseat items and existing tasks.
|
||||
*/
|
||||
private fun computeShownRunningTasks(): List<GroupTask> {
|
||||
if (!canShowRunningApps) {
|
||||
private fun getDesktopRunningTasks(): List<RunningTaskInfo> =
|
||||
recentsModel.runningTasks.filter { taskInfo: RunningTaskInfo ->
|
||||
taskInfo.windowingMode == WindowConfiguration.WINDOWING_MODE_FREEFORM
|
||||
}
|
||||
|
||||
// TODO(b/335398876) fetch app icons from Tasks instead of AppInfos
|
||||
private fun getAppInfosFromRunningTasks(tasks: List<RunningTaskInfo>): List<AppInfo> {
|
||||
// Early return if apps is empty, since we then have no AppInfo to compare to
|
||||
if (apps == null) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val desktopTasks = getOrderedAndWrappedDesktopTasks()
|
||||
|
||||
val newShownTasks =
|
||||
if (Flags.enableMultiInstanceMenuTaskbar()) {
|
||||
val deduplicatedDesktopTasks =
|
||||
desktopTasks.distinctBy { Pair(it.task.key.packageName, it.task.key.userId) }
|
||||
|
||||
shownTasks
|
||||
.filter {
|
||||
it is SingleTask &&
|
||||
it.task.key.id in deduplicatedDesktopTasks.map { it.task.key.id }
|
||||
}
|
||||
.toMutableList()
|
||||
.apply {
|
||||
addAll(
|
||||
deduplicatedDesktopTasks.filter { currentTask ->
|
||||
val currentTaskKey = currentTask.task.key
|
||||
currentTaskKey.id !in shownTaskIds &&
|
||||
shownHotseatItems.none { hotseatItem ->
|
||||
currentTask.containsPackage(
|
||||
hotseatItem.targetPackage,
|
||||
hotseatItem.user.identifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
val desktopTaskIds = desktopTasks.map { it.task.key.id }
|
||||
val shownHotseatItemTaskIds =
|
||||
shownHotseatItems.mapNotNull { it as? TaskItemInfo }.map { it.taskId }
|
||||
|
||||
shownTasks
|
||||
.filter { it is SingleTask && it.task.key.id in desktopTaskIds }
|
||||
.toMutableList()
|
||||
.apply {
|
||||
addAll(
|
||||
desktopTasks.filter { desktopTask ->
|
||||
desktopTask.task.key.id !in shownTaskIds
|
||||
}
|
||||
)
|
||||
removeAll { it is SingleTask && it.task.key.id in shownHotseatItemTaskIds }
|
||||
}
|
||||
}
|
||||
|
||||
return newShownTasks
|
||||
val packageNames = tasks.map { it.realActivity?.packageName }.distinct().filterNotNull()
|
||||
return packageNames
|
||||
.map { packageName -> apps?.find { app -> packageName == app.targetPackage } }
|
||||
.filterNotNull()
|
||||
}
|
||||
|
||||
private fun computeShownRecentTasks(): List<GroupTask> {
|
||||
if (!canShowRecentApps || allRecentTasks.isEmpty()) {
|
||||
return emptyList()
|
||||
/** Called to update the list of currently running apps, no-op except in desktop environment. */
|
||||
fun updateRunningApps() {
|
||||
if (!isEnabled || !isInDesktopMode) {
|
||||
return controllers.taskbarViewController.commitRunningAppsToUI()
|
||||
}
|
||||
// Remove the current task.
|
||||
val allRecentTasks = allRecentTasks.subList(0, allRecentTasks.size - 1)
|
||||
var shownTasks = dedupeHotseatTasks(allRecentTasks, shownHotseatItems)
|
||||
if (shownTasks.size > MAX_RECENT_TASKS) {
|
||||
// Remove any tasks older than MAX_RECENT_TASKS.
|
||||
shownTasks = shownTasks.subList(shownTasks.size - MAX_RECENT_TASKS, shownTasks.size)
|
||||
}
|
||||
return shownTasks
|
||||
val runningTasks = getDesktopRunningTasks()
|
||||
val runningAppInfo = getAppInfosFromRunningTasks(runningTasks)
|
||||
allRunningDesktopAppInfos = runningAppInfo
|
||||
updateMinimizedApps(runningTasks, runningAppInfo)
|
||||
controllers.taskbarViewController.commitRunningAppsToUI()
|
||||
}
|
||||
|
||||
private fun dedupeHotseatTasks(
|
||||
groupTasks: List<GroupTask>,
|
||||
shownHotseatItems: List<ItemInfo>,
|
||||
): List<GroupTask> {
|
||||
// TODO: b/393476333 - Check the behavior of the Taskbar recents section when empty desks
|
||||
// become supported.
|
||||
return if (Flags.enableMultiInstanceMenuTaskbar()) {
|
||||
groupTasks.filter { groupTask ->
|
||||
// Keep tasks that are group tasks or unique package name/user combinations
|
||||
when (groupTask) {
|
||||
is SingleTask ->
|
||||
shownHotseatItems.none {
|
||||
groupTask.containsPackage(it.targetPackage, it.user.identifier)
|
||||
}
|
||||
|
||||
else -> true
|
||||
private fun updateMinimizedApps(
|
||||
runningTasks: List<RunningTaskInfo>,
|
||||
runningAppInfo: List<AppInfo>,
|
||||
) {
|
||||
val allRunningAppTasks =
|
||||
runningAppInfo
|
||||
.mapNotNull { appInfo -> appInfo.targetPackage?.let { appInfo to it } }
|
||||
.associate { (appInfo, targetPackage) ->
|
||||
appInfo to
|
||||
runningTasks
|
||||
.filter { it.realActivity?.packageName == targetPackage }
|
||||
.map { it.taskId }
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val hotseatPackages = shownHotseatItems.map { it.targetPackage }
|
||||
groupTasks.filter { groupTask ->
|
||||
when (groupTask) {
|
||||
is SingleTask -> hotseatPackages.none { groupTask.containsPackage(it) }
|
||||
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
}
|
||||
val minimizedTaskIds = runningTasks.associate { it.taskId to !it.isVisible }
|
||||
allMinimizedDesktopAppInfos =
|
||||
allRunningAppTasks
|
||||
.filterValues { taskIds -> taskIds.all { minimizedTaskIds[it] ?: false } }
|
||||
.keys
|
||||
.toList()
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the hotseat items updated so that any item that points to a package+user with a
|
||||
* running task also references that task.
|
||||
*/
|
||||
private fun updateHotseatItemsFromRunningTasks(
|
||||
groupTasks: List<GroupTask>,
|
||||
shownHotseatItems: List<ItemInfo>,
|
||||
): List<ItemInfo> =
|
||||
shownHotseatItems.map { itemInfo ->
|
||||
if (itemInfo is TaskItemInfo) {
|
||||
itemInfo
|
||||
} else {
|
||||
val foundTask =
|
||||
groupTasks
|
||||
.flatMap { it.tasks }
|
||||
.find { task ->
|
||||
task.key.packageName == itemInfo.targetPackage &&
|
||||
task.key.userId == itemInfo.user.identifier
|
||||
} ?: return@map itemInfo
|
||||
TaskItemInfo(foundTask.key.id, itemInfo as WorkspaceItemInfo)
|
||||
}
|
||||
}
|
||||
|
||||
override fun dumpLogs(prefix: String, pw: PrintWriter) {
|
||||
pw.println("$prefix TaskbarRecentAppsController:")
|
||||
pw.println("$prefix\tisEnabled=$isEnabled")
|
||||
pw.println("$prefix\tcanShowRunningApps=$canShowRunningApps")
|
||||
pw.println("$prefix\tcanShowRecentApps=$canShowRecentApps")
|
||||
pw.println("$prefix\tshownHotseatItems=${shownHotseatItems.map{item->item.targetPackage}}")
|
||||
pw.println("$prefix\tallRecentTasks=${allRecentTasks.map { it.packageNames }}")
|
||||
pw.println("$prefix\tdesktopTask=${desktopTask?.packageNames}")
|
||||
pw.println("$prefix\tshownTasks=${shownTasks.map { it.packageNames }}")
|
||||
pw.println("$prefix\trunningTaskIds=$runningTaskIds")
|
||||
pw.println("$prefix\tminimizedTaskIds=$minimizedTaskIds")
|
||||
}
|
||||
|
||||
private val GroupTask.packageNames: List<String>
|
||||
get() = tasks.map { task -> task.key.packageName }
|
||||
|
||||
private companion object {
|
||||
const val MAX_RECENT_TASKS = 2
|
||||
// TODO(next CL): add more logs
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,17 +20,15 @@ import static android.view.View.VISIBLE;
|
||||
import static com.android.launcher3.taskbar.bubbles.BubbleBarController.isBubbleBarEnabled;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED;
|
||||
import static com.android.wm.shell.common.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
|
||||
import static com.android.wm.shell.shared.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA;
|
||||
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.view.animation.Interpolator;
|
||||
import android.view.animation.PathInterpolator;
|
||||
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleControllers;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
|
||||
|
||||
@@ -67,7 +65,6 @@ public class TaskbarScrimViewController implements TaskbarControllers.LoggableTa
|
||||
*/
|
||||
public void init(TaskbarControllers controllers) {
|
||||
mControllers = controllers;
|
||||
onTaskbarVisibilityChanged(mControllers.taskbarViewController.getTaskbarVisibility());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,7 +75,7 @@ public class TaskbarScrimViewController implements TaskbarControllers.LoggableTa
|
||||
public void onTaskbarVisibilityChanged(int visibility) {
|
||||
mTaskbarVisible = visibility == VISIBLE;
|
||||
if (shouldShowScrim()) {
|
||||
showScrim(true, computeScrimAlpha(), false /* skipAnim */);
|
||||
showScrim(true, getScrimAlpha(), false /* skipAnim */);
|
||||
} else if (mScrimView.getScrimAlpha() > 0f) {
|
||||
showScrim(false, 0, false /* skipAnim */);
|
||||
}
|
||||
@@ -88,42 +85,26 @@ public class TaskbarScrimViewController implements TaskbarControllers.LoggableTa
|
||||
* Updates the scrim state based on the flags.
|
||||
*/
|
||||
public void updateStateForSysuiFlags(@SystemUiStateFlags long stateFlags, boolean skipAnim) {
|
||||
if (mActivity.isPhoneMode()) {
|
||||
// There is no scrim for the bar in the phone mode.
|
||||
return;
|
||||
}
|
||||
boolean isTransient = mActivity.isTransientTaskbar();
|
||||
if (isBubbleBarEnabled() && isTransient) {
|
||||
if (isBubbleBarEnabled() && DisplayController.isTransientTaskbar(mActivity)) {
|
||||
// These scrims aren't used if bubble bar & transient taskbar are active.
|
||||
return;
|
||||
}
|
||||
mSysUiStateFlags = stateFlags;
|
||||
showScrim(shouldShowScrim(), computeScrimAlpha(), skipAnim);
|
||||
showScrim(shouldShowScrim(), getScrimAlpha(), skipAnim);
|
||||
}
|
||||
|
||||
private boolean shouldShowScrim() {
|
||||
final boolean bubblesExpanded = (mSysUiStateFlags & SYSUI_STATE_BUBBLES_EXPANDED) != 0;
|
||||
boolean isShadeVisible = (mSysUiStateFlags & SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE) != 0;
|
||||
BubbleControllers bubbleControllers = mActivity.getBubbleControllers();
|
||||
boolean isBubbleControllersPresented = bubbleControllers != null;
|
||||
// when the taskbar is in persistent mode, we hide the task bar icons on bubble bar expand,
|
||||
// which makes the taskbar invisible, so need to check if the bubble bar is not on home
|
||||
// to show the scrim view
|
||||
boolean showScrimForBubbles = bubblesExpanded
|
||||
&& !mTaskbarVisible
|
||||
&& isBubbleControllersPresented
|
||||
&& !mActivity.isTransientTaskbar()
|
||||
&& !bubbleControllers.bubbleStashController.isBubblesShowingOnHome();
|
||||
return bubblesExpanded && !mControllers.navbarButtonsViewController.isImeVisible()
|
||||
&& !isShadeVisible
|
||||
&& !mControllers.taskbarStashController.isStashed()
|
||||
&& (mTaskbarVisible || showScrimForBubbles)
|
||||
&& !mControllers.taskbarStashController.isHiddenForBubbles();
|
||||
&& mTaskbarVisible;
|
||||
}
|
||||
|
||||
private float computeScrimAlpha() {
|
||||
boolean isTransient = mActivity.isTransientTaskbar();
|
||||
final boolean isPersistentTaskBarVisible = mTaskbarVisible && !isTransient;
|
||||
private float getScrimAlpha() {
|
||||
final boolean isPersistentTaskBarVisible =
|
||||
mTaskbarVisible && !DisplayController.isTransientTaskbar(mScrimView.getContext());
|
||||
final boolean manageMenuExpanded =
|
||||
(mSysUiStateFlags & SYSUI_STATE_BUBBLES_MANAGE_MENU_EXPANDED) != 0;
|
||||
if (isPersistentTaskBarVisible && manageMenuExpanded) {
|
||||
@@ -142,7 +123,7 @@ public class TaskbarScrimViewController implements TaskbarControllers.LoggableTa
|
||||
mScrimView.setOnClickListener(showScrim ? (view) -> onClick() : null);
|
||||
mScrimView.setClickable(showScrim);
|
||||
if (skipAnim) {
|
||||
mScrimAlpha.updateValue(alpha);
|
||||
mScrimView.setScrimAlpha(alpha);
|
||||
} else {
|
||||
ObjectAnimator anim = mScrimAlpha.animateToValue(showScrim ? alpha : 0);
|
||||
anim.setInterpolator(showScrim ? SCRIM_ALPHA_IN : SCRIM_ALPHA_OUT);
|
||||
@@ -155,7 +136,7 @@ public class TaskbarScrimViewController implements TaskbarControllers.LoggableTa
|
||||
}
|
||||
|
||||
private void onClick() {
|
||||
SystemUiProxy.INSTANCE.get(mActivity).onBackEvent(null);
|
||||
SystemUiProxy.INSTANCE.get(mActivity).onBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -169,14 +150,4 @@ public class TaskbarScrimViewController implements TaskbarControllers.LoggableTa
|
||||
|
||||
pw.println(prefix + "\tmScrimAlpha.value=" + mScrimAlpha.value);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
TaskbarScrimView getScrimView() {
|
||||
return mScrimView;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
float getScrimAlpha() {
|
||||
return mScrimAlpha.value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,10 +30,6 @@ import android.os.IBinder;
|
||||
import android.view.InsetsFrameProvider;
|
||||
|
||||
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
|
||||
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
|
||||
import com.android.wm.shell.shared.bubbles.BubbleInfo;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* State shared across different taskbar instance
|
||||
@@ -60,30 +56,14 @@ public class TaskbarSharedState {
|
||||
// TaskbarManager#onNavButtonsDarkIntensityChanged()
|
||||
public float navButtonsDarkIntensity;
|
||||
|
||||
// TaskbarManager#onTransitionModeUpdated()
|
||||
public int barMode;
|
||||
|
||||
// TaskbarManager#onNavigationBarLumaSamplingEnabled()
|
||||
public int mLumaSamplingDisplayId = DEFAULT_DISPLAY;
|
||||
public boolean mIsLumaSamplingEnabled = true;
|
||||
|
||||
public boolean setupUIVisible = false;
|
||||
|
||||
public boolean wallpaperVisible = false;
|
||||
|
||||
public boolean allAppsVisible = false;
|
||||
|
||||
public BubbleBarLocation bubbleBarLocation;
|
||||
|
||||
public List<BubbleInfo> bubbleInfoItems;
|
||||
|
||||
public List<BubbleInfo> suppressedBubbleInfoItems;
|
||||
|
||||
/** Returns whether there are a saved bubbles. */
|
||||
public boolean hasSavedBubbles() {
|
||||
return bubbleInfoItems != null && !bubbleInfoItems.isEmpty();
|
||||
}
|
||||
|
||||
// LauncherTaskbarUIController#mTaskbarInAppDisplayProgressMultiProp
|
||||
public float[] inAppDisplayProgressMultiPropValues = new float[DISPLAY_PROGRESS_COUNT];
|
||||
|
||||
@@ -117,8 +97,5 @@ public class TaskbarSharedState {
|
||||
|
||||
// To track if taskbar was stashed / unstashed between configuration changes (which recreates
|
||||
// the task bar).
|
||||
public boolean taskbarWasStashedAuto = true;
|
||||
|
||||
// should show corner radius on persistent taskbar when in desktop mode.
|
||||
public boolean showCornerRadiusInDesktopMode = false;
|
||||
public Boolean taskbarWasStashedAuto = true;
|
||||
}
|
||||
|
||||
+5
-23
@@ -22,7 +22,6 @@ import static com.android.launcher3.util.SplitConfigurationOptions.getLogEventFo
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.util.Pair;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
@@ -39,7 +38,6 @@ import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
import com.android.launcher3.util.ShortcutUtil;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.util.LogUtils;
|
||||
import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@@ -52,7 +50,6 @@ public class TaskbarShortcutMenuAccessibilityDelegate
|
||||
|
||||
public static final int MOVE_TO_TOP_OR_LEFT = R.id.action_move_to_top_or_left;
|
||||
public static final int MOVE_TO_BOTTOM_OR_RIGHT = R.id.action_move_to_bottom_or_right;
|
||||
public static final int CREATE_APPLICATION_BUBBLE = R.id.action_create_application_bubble;
|
||||
|
||||
private final LauncherApps mLauncherApps;
|
||||
private final StatsLogManager mStatsLogManager;
|
||||
@@ -70,9 +67,6 @@ public class TaskbarShortcutMenuAccessibilityDelegate
|
||||
MOVE_TO_BOTTOM_OR_RIGHT,
|
||||
R.string.move_drop_target_bottom_or_right,
|
||||
KeyEvent.KEYCODE_R));
|
||||
mActions.put(CREATE_APPLICATION_BUBBLE, new LauncherAction(
|
||||
CREATE_APPLICATION_BUBBLE, R.string.open_app_as_a_bubble,
|
||||
KeyEvent.KEYCODE_L));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -82,27 +76,11 @@ public class TaskbarShortcutMenuAccessibilityDelegate
|
||||
}
|
||||
out.add(mActions.get(MOVE_TO_TOP_OR_LEFT));
|
||||
out.add(mActions.get(MOVE_TO_BOTTOM_OR_RIGHT));
|
||||
if (BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
|
||||
out.add(mActions.get(CREATE_APPLICATION_BUBBLE));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean performAction(View host, ItemInfo item, int action, boolean fromKeyboard) {
|
||||
if (action == DEEP_SHORTCUTS) {
|
||||
mContext.showPopupMenuForIcon((BubbleTextView) host);
|
||||
return true;
|
||||
} else if (action == CREATE_APPLICATION_BUBBLE) {
|
||||
if (item.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT
|
||||
&& item instanceof WorkspaceItemInfo) {
|
||||
ShortcutInfo shortcutInfo = ((WorkspaceItemInfo) item).getDeepShortcutInfo();
|
||||
SystemUiProxy.INSTANCE.get(mContext).showShortcutBubble(shortcutInfo);
|
||||
return true;
|
||||
} else if (item.getIntent() != null && item.getIntent().getPackage() != null) {
|
||||
SystemUiProxy.INSTANCE.get(mContext).showAppBubble(item.getIntent(), item.user);
|
||||
return true;
|
||||
}
|
||||
} else if (item instanceof ItemInfoWithIcon
|
||||
if (item instanceof ItemInfoWithIcon
|
||||
&& (action == MOVE_TO_TOP_OR_LEFT || action == MOVE_TO_BOTTOM_OR_RIGHT)) {
|
||||
ItemInfoWithIcon info = (ItemInfoWithIcon) item;
|
||||
int side = action == MOVE_TO_TOP_OR_LEFT
|
||||
@@ -133,6 +111,10 @@ public class TaskbarShortcutMenuAccessibilityDelegate
|
||||
item.user.getIdentifier(), new Intent(), side, null,
|
||||
instanceIds.first);
|
||||
}
|
||||
return true;
|
||||
} else if (action == DEEP_SHORTCUTS) {
|
||||
mContext.showPopupMenuForIcon((BubbleTextView) host);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.anim.SpringAnimationBuilder;
|
||||
import com.android.launcher3.taskbar.TaskbarControllers.LoggableTaskbarController;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
@@ -46,7 +47,7 @@ public class TaskbarSpringOnStashController implements LoggableTaskbarController
|
||||
|
||||
public TaskbarSpringOnStashController(TaskbarActivityContext context) {
|
||||
mContext = context;
|
||||
mIsTransientTaskbar = context.isTransientTaskbar();
|
||||
mIsTransientTaskbar = DisplayController.isTransientTaskbar(mContext);
|
||||
mStartVelocityPxPerS = context.getResources()
|
||||
.getDimension(R.dimen.transient_taskbar_stash_spring_velocity_dp_per_s);
|
||||
}
|
||||
|
||||
@@ -23,20 +23,16 @@ import static com.android.app.animation.Interpolators.INSTANT;
|
||||
import static com.android.app.animation.Interpolators.LINEAR;
|
||||
import static com.android.internal.jank.InteractionJankMonitor.Configuration;
|
||||
import static com.android.launcher3.Flags.enableScalingRevealHomeAnimation;
|
||||
import static com.android.launcher3.Flags.syncAppLaunchWithTaskbarStash;
|
||||
import static com.android.launcher3.QuickstepTransitionManager.PINNED_TASKBAR_TRANSITION_DURATION;
|
||||
import static com.android.launcher3.config.FeatureFlags.ENABLE_TASKBAR_NAVBAR_UNIFICATION;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_HIDE;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TRANSIENT_TASKBAR_SHOW;
|
||||
import static com.android.launcher3.taskbar.TaskbarActivityContext.ENABLE_TASKBAR_BEHIND_SHADE;
|
||||
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
|
||||
import static com.android.launcher3.util.FlagDebugUtils.appendFlag;
|
||||
import static com.android.launcher3.util.FlagDebugUtils.formatFlagChange;
|
||||
import static com.android.quickstep.util.SystemActionConstants.SYSTEM_ACTION_ID_TASKBAR;
|
||||
import static com.android.quickstep.util.SystemUiFlagUtils.isTaskbarHidden;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BUBBLES_EXPANDED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_DIALOG_SHOWING;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
|
||||
|
||||
@@ -64,9 +60,11 @@ import com.android.launcher3.Alarm;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.anim.AnimationSuccessListener;
|
||||
import com.android.launcher3.anim.AnimatorListeners;
|
||||
import com.android.launcher3.statehandlers.DesktopVisibilityController;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.MultiPropertyFactory.MultiProperty;
|
||||
import com.android.quickstep.LauncherActivityInterface;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.util.SystemUiFlagUtils;
|
||||
|
||||
@@ -84,11 +82,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
private static final String TAG = "TaskbarStashController";
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
/**
|
||||
* Def. value for @param shouldBubblesFollow in
|
||||
* {@link #updateAndAnimateTransientTaskbar(boolean)} */
|
||||
public static boolean SHOULD_BUBBLES_FOLLOW_DEFAULT_VALUE = true;
|
||||
|
||||
public static final int FLAG_IN_APP = 1 << 0;
|
||||
public static final int FLAG_STASHED_IN_APP_SYSUI = 1 << 1; // shade open, ...
|
||||
public static final int FLAG_STASHED_IN_APP_SETUP = 1 << 2; // setup wizard and AllSetActivity
|
||||
@@ -101,14 +94,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
public static final int FLAG_STASHED_SYSUI = 1 << 9; // app pinning,...
|
||||
public static final int FLAG_STASHED_DEVICE_LOCKED = 1 << 10; // device is locked: keyguard, ...
|
||||
public static final int FLAG_IN_OVERVIEW = 1 << 11; // launcher is in overview
|
||||
// An internal no-op flag to determine whether we should delay the taskbar background animation
|
||||
private static final int FLAG_DELAY_TASKBAR_BG_TAG = 1 << 12;
|
||||
public static final int FLAG_STASHED_FOR_BUBBLES = 1 << 13; // show handle for stashed hotseat
|
||||
public static final int FLAG_TASKBAR_HIDDEN = 1 << 14; // taskbar hidden during dream, etc...
|
||||
// taskbar should always be stashed for bubble bar on phone
|
||||
public static final int FLAG_STASHED_BUBBLE_BAR_ON_PHONE = 1 << 15;
|
||||
|
||||
public static final int FLAG_IGNORE_IN_APP = 1 << 16; // used to sync with app launch animation
|
||||
|
||||
// If any of these flags are enabled, isInApp should return true.
|
||||
private static final int FLAGS_IN_APP = FLAG_IN_APP | FLAG_IN_SETUP;
|
||||
@@ -130,30 +115,26 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
|
||||
// If any of these flags are enabled, the taskbar must be stashed.
|
||||
private static final int FLAGS_FORCE_STASHED = FLAG_STASHED_SYSUI | FLAG_STASHED_DEVICE_LOCKED
|
||||
| FLAG_STASHED_IN_TASKBAR_ALL_APPS | FLAG_STASHED_SMALL_SCREEN
|
||||
| FLAG_STASHED_FOR_BUBBLES | FLAG_STASHED_BUBBLE_BAR_ON_PHONE;
|
||||
| FLAG_STASHED_IN_TASKBAR_ALL_APPS | FLAG_STASHED_SMALL_SCREEN;
|
||||
|
||||
/**
|
||||
* How long to stash/unstash when manually invoked via long press.
|
||||
*
|
||||
* Use {@link #getStashDuration()} to query duration
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final long TASKBAR_STASH_DURATION = InsetsController.ANIMATION_DURATION_RESIZE;
|
||||
private static final long TASKBAR_STASH_DURATION = InsetsController.ANIMATION_DURATION_RESIZE;
|
||||
|
||||
/**
|
||||
* How long to stash/unstash transient taskbar.
|
||||
*
|
||||
* Use {@link #getStashDuration()} to query duration.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final long TRANSIENT_TASKBAR_STASH_DURATION = 417;
|
||||
private static final long TRANSIENT_TASKBAR_STASH_DURATION = 417;
|
||||
|
||||
/**
|
||||
* How long to stash/unstash when keyboard is appearing/disappearing.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
static final long TASKBAR_STASH_DURATION_FOR_IME = 80;
|
||||
private static final long TASKBAR_STASH_DURATION_FOR_IME = 80;
|
||||
|
||||
/**
|
||||
* The scale TaskbarView animates to when being stashed.
|
||||
@@ -169,12 +150,12 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
/**
|
||||
* How long to delay the icon/stash handle alpha.
|
||||
*/
|
||||
public static final long TASKBAR_STASH_ALPHA_START_DELAY = 33;
|
||||
private static final long TASKBAR_STASH_ALPHA_START_DELAY = 33;
|
||||
|
||||
/**
|
||||
* How long the icon/stash handle alpha animation plays.
|
||||
*/
|
||||
public static final long TRANSIENT_TASKBAR_STASH_ALPHA_DURATION = 50;
|
||||
private static final long TASKBAR_STASH_ALPHA_DURATION = 50;
|
||||
|
||||
/**
|
||||
* How long to delay the icon/stash handle alpha for the home to app taskbar animation.
|
||||
@@ -196,7 +177,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
|
||||
// Duration for which an unlock event is considered "current", as other events are received
|
||||
// asynchronously.
|
||||
public static final long UNLOCK_TRANSITION_MEMOIZATION_MS = 200;
|
||||
private static final long UNLOCK_TRANSITION_MEMOIZATION_MS = 200;
|
||||
|
||||
/**
|
||||
* The default stash animation, morphing the taskbar into the navbar.
|
||||
@@ -220,13 +201,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
*/
|
||||
private static final int TRANSITION_UNSTASH_SUW_MANUAL = 3;
|
||||
|
||||
/**
|
||||
* total duration of entering dream state animation, which we use as start delay to
|
||||
* applyState() when SYSUI_STATE_DEVICE_DREAMING flag is present. Keep this in sync with
|
||||
* DreamAnimationController.TOTAL_ANIM_DURATION.
|
||||
*/
|
||||
private static final int SKIP_TOTAL_DREAM_ANIM_DURATION = 450;
|
||||
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(value = {
|
||||
TRANSITION_DEFAULT,
|
||||
@@ -263,17 +237,15 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
|
||||
private @Nullable AnimatorSet mAnimator;
|
||||
private boolean mIsSystemGestureInProgress;
|
||||
/** Whether the IME is visible. */
|
||||
private boolean mIsImeVisible;
|
||||
private boolean mIsImeShowing;
|
||||
private boolean mIsImeSwitcherShowing;
|
||||
|
||||
private final Alarm mTimeoutAlarm = new Alarm();
|
||||
private boolean mEnableBlockingTimeoutDuringTests = false;
|
||||
|
||||
private Animator mTaskbarBackgroundAlphaAnimator;
|
||||
private final long mTaskbarBackgroundDuration;
|
||||
private boolean mUserIsNotGoingHome = false;
|
||||
|
||||
private final boolean mInAppStateAffectsDesktopTasksVisibilityInTaskbar;
|
||||
private long mTaskbarBackgroundDuration;
|
||||
private boolean mIsGoingHome;
|
||||
|
||||
// Evaluate whether the handle should be stashed
|
||||
private final LongPredicate mIsStashedPredicate = flags -> {
|
||||
@@ -299,18 +271,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
mSystemUiProxy = SystemUiProxy.INSTANCE.get(activity);
|
||||
mAccessibilityManager = mActivity.getSystemService(AccessibilityManager.class);
|
||||
|
||||
// Taskbar, via `TaskbarDesktopModeController`, depends on `TaskbarStashController` state to
|
||||
// determine whether desktop tasks should be shown because taskbar is pinned on the home
|
||||
// screen for freeform windowing displays. In this case, list of items shown in the taskbar
|
||||
// needs to be updated when in-app state changes.
|
||||
// TODO(b/390665752): Feature to "lock" pinned taskbar to home screen will be superseded by
|
||||
// pinning, in other launcher states, at which point this variable can be removed.
|
||||
mInAppStateAffectsDesktopTasksVisibilityInTaskbar =
|
||||
!mActivity.showDesktopTaskbarForFreeformDisplay()
|
||||
&& mActivity.showLockedTaskbarOnHome();
|
||||
|
||||
mTaskbarBackgroundDuration = activity.getResources().getInteger(
|
||||
R.integer.taskbar_background_duration);
|
||||
mTaskbarBackgroundDuration =
|
||||
activity.getResources().getInteger(R.integer.taskbar_background_duration);
|
||||
if (mActivity.isPhoneMode()) {
|
||||
mUnstashedHeight = mActivity.getResources().getDimensionPixelSize(
|
||||
R.dimen.taskbar_phone_size);
|
||||
@@ -322,6 +284,18 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show Taskbar upon receiving broadcast
|
||||
*/
|
||||
public void showTaskbarFromBroadcast() {
|
||||
// If user is in middle of taskbar education handle go to next step of education
|
||||
if (mControllers.taskbarEduTooltipController.isBeforeTooltipFeaturesStep()) {
|
||||
mControllers.taskbarEduTooltipController.hide();
|
||||
mControllers.taskbarEduTooltipController.maybeShowFeaturesEdu();
|
||||
}
|
||||
updateAndAnimateTransientTaskbar(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the controller
|
||||
*/
|
||||
@@ -349,7 +323,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
StashedHandleViewController.ALPHA_INDEX_STASHED);
|
||||
mTaskbarStashedHandleHintScale = stashedHandleController.getStashedHandleHintScale();
|
||||
|
||||
boolean isTransientTaskbar = mActivity.isTransientTaskbar();
|
||||
boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity);
|
||||
boolean isInSetup = !mActivity.isUserSetupComplete() || setupUIVisible;
|
||||
boolean isStashedInAppAuto =
|
||||
isTransientTaskbar && !mTaskbarSharedState.getTaskbarWasPinned();
|
||||
@@ -364,16 +338,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
// For now, assume we're in an app, since LauncherTaskbarUIController won't be able to tell
|
||||
// us that we're paused until a bit later. This avoids flickering upon recreating taskbar.
|
||||
updateStateForFlag(FLAG_IN_APP, true);
|
||||
updateStateForFlag(FLAG_STASHED_BUBBLE_BAR_ON_PHONE, mActivity.isBubbleBarOnPhone());
|
||||
|
||||
applyState(/* duration = */ 0);
|
||||
|
||||
// Hide the background while stashed so it doesn't show on fast swipes home
|
||||
boolean shouldHideTaskbarBackground = mActivity.isPhoneMode() ||
|
||||
(enableScalingRevealHomeAnimation() && isTransientTaskbar && isStashed());
|
||||
|
||||
mTaskbarBackgroundAlphaForStash.setValue(shouldHideTaskbarBackground ? 0 : 1);
|
||||
|
||||
if (mTaskbarSharedState.getTaskbarWasPinned()
|
||||
|| !mTaskbarSharedState.taskbarWasStashedAuto) {
|
||||
tryStartTaskbarTimeout();
|
||||
@@ -412,10 +377,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
* Returns how long the stash/unstash animation should play.
|
||||
*/
|
||||
public long getStashDuration() {
|
||||
if (mActivity.isPinnedTaskbar()) {
|
||||
return PINNED_TASKBAR_TRANSITION_DURATION;
|
||||
}
|
||||
return mActivity.isTransientTaskbar() ? TRANSIENT_TASKBAR_STASH_DURATION
|
||||
return DisplayController.isTransientTaskbar(mActivity)
|
||||
? TRANSIENT_TASKBAR_STASH_DURATION
|
||||
: TASKBAR_STASH_DURATION;
|
||||
}
|
||||
|
||||
@@ -426,28 +389,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
return mIsStashed;
|
||||
}
|
||||
|
||||
public boolean isDeviceLocked() {
|
||||
return hasAnyFlag(FLAG_STASHED_DEVICE_LOCKED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the hotseat stashed.
|
||||
* b/373429249 - we might change this behavior if we remove the scrim, that's why we're keeping
|
||||
* this method
|
||||
*/
|
||||
public void stashHotseat(boolean stash) {
|
||||
mControllers.uiController.stashHotseat(stash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Instantly un-stashes the hotseat.
|
||||
* * b/373429249 - we might change this behavior if we remove the scrim, that's why we're
|
||||
* keeping this method
|
||||
*/
|
||||
public void unStashHotseatInstantly() {
|
||||
mControllers.uiController.unStashHotseatInstantly();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the taskbar should be stashed in apps (e.g. user long pressed to stash).
|
||||
*/
|
||||
@@ -482,21 +423,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
return hasAnyFlag(FLAGS_IN_APP);
|
||||
}
|
||||
|
||||
/** Returns whether the taskbar is currently in overview screen. */
|
||||
public boolean isInOverview() {
|
||||
return hasAnyFlag(FLAG_IN_OVERVIEW);
|
||||
}
|
||||
|
||||
/** Returns whether the taskbar is currently on launcher home screen. */
|
||||
public boolean isOnHome() {
|
||||
return !isInOverview() && !isInApp();
|
||||
}
|
||||
|
||||
/** Returns whether taskbar is hidden for bubbles. */
|
||||
public boolean isHiddenForBubbles() {
|
||||
return hasAnyFlag(FLAG_STASHED_FOR_BUBBLES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the height that taskbar will be touchable.
|
||||
*/
|
||||
@@ -513,8 +439,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
* @see android.view.WindowInsets.Type#systemBars()
|
||||
*/
|
||||
public int getContentHeightToReportToApps() {
|
||||
boolean isTransient = mActivity.isTransientTaskbar();
|
||||
if (mActivity.isUserSetupComplete() && (mActivity.isPhoneGestureNavMode() || isTransient)) {
|
||||
if (mActivity.isUserSetupComplete() && (mActivity.isPhoneGestureNavMode()
|
||||
|| DisplayController.isTransientTaskbar(mActivity))) {
|
||||
return getStashedHeight();
|
||||
}
|
||||
|
||||
@@ -559,17 +485,9 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
/**
|
||||
* Stash or unstashes the transient taskbar, using the default TASKBAR_STASH_DURATION.
|
||||
* If bubble bar exists, it will match taskbars stashing behavior.
|
||||
* Will not delay taskbar background by default.
|
||||
*/
|
||||
public void updateAndAnimateTransientTaskbar(boolean stash) {
|
||||
updateAndAnimateTransientTaskbar(stash, SHOULD_BUBBLES_FOLLOW_DEFAULT_VALUE, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Stash or unstashes the transient taskbar, using the default TASKBAR_STASH_DURATION.
|
||||
*/
|
||||
public void updateAndAnimateTransientTaskbar(boolean stash, boolean shouldBubblesFollow) {
|
||||
updateAndAnimateTransientTaskbar(stash, shouldBubblesFollow, false);
|
||||
updateAndAnimateTransientTaskbar(stash, /* shouldBubblesFollow= */ true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -577,47 +495,28 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
*
|
||||
* @param stash whether transient taskbar should be stashed.
|
||||
* @param shouldBubblesFollow whether bubbles should match taskbars behavior.
|
||||
* @param delayTaskbarBackground whether we will delay the taskbar background animation
|
||||
*/
|
||||
public void updateAndAnimateTransientTaskbar(boolean stash, boolean shouldBubblesFollow,
|
||||
boolean delayTaskbarBackground) {
|
||||
if (!mActivity.isTransientTaskbar() || mActivity.isBubbleBarOnPhone()) {
|
||||
public void updateAndAnimateTransientTaskbar(boolean stash, boolean shouldBubblesFollow) {
|
||||
if (!DisplayController.isTransientTaskbar(mActivity)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (stash
|
||||
&& !mControllers.taskbarAutohideSuspendController
|
||||
.isSuspendedForTransientTaskbarInLauncher()
|
||||
&& mControllers.taskbarAutohideSuspendController
|
||||
.isTransientTaskbarStashingSuspended()) {
|
||||
if (
|
||||
stash
|
||||
&& !mControllers.taskbarAutohideSuspendController
|
||||
.isSuspendedForTransientTaskbarInLauncher()
|
||||
&& mControllers.taskbarAutohideSuspendController
|
||||
.isTransientTaskbarStashingSuspended()) {
|
||||
// Avoid stashing if autohide is currently suspended.
|
||||
return;
|
||||
}
|
||||
|
||||
boolean shouldApplyState = false;
|
||||
|
||||
if (delayTaskbarBackground) {
|
||||
mControllers.taskbarStashController.updateStateForFlag(FLAG_DELAY_TASKBAR_BG_TAG, true);
|
||||
shouldApplyState = true;
|
||||
}
|
||||
|
||||
if (hasAnyFlag(FLAG_STASHED_IN_APP_AUTO) != stash) {
|
||||
mTaskbarSharedState.taskbarWasStashedAuto = stash;
|
||||
updateStateForFlag(FLAG_STASHED_IN_APP_AUTO, stash);
|
||||
shouldApplyState = true;
|
||||
}
|
||||
|
||||
if (shouldApplyState) {
|
||||
applyState();
|
||||
}
|
||||
|
||||
// Effectively a no-opp to remove the tag.
|
||||
if (delayTaskbarBackground) {
|
||||
mControllers.taskbarStashController.updateStateForFlag(FLAG_DELAY_TASKBAR_BG_TAG,
|
||||
false);
|
||||
mControllers.taskbarStashController.applyState(0);
|
||||
}
|
||||
|
||||
mControllers.bubbleControllers.ifPresent(controllers -> {
|
||||
if (shouldBubblesFollow) {
|
||||
final boolean willStash = mIsStashedPredicate.test(mState);
|
||||
@@ -649,7 +548,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
|
||||
/** Toggles the Taskbar's stash state. */
|
||||
public void toggleTaskbarStash() {
|
||||
if (!mActivity.isTransientTaskbar() || !hasAnyFlag(FLAGS_IN_APP)) return;
|
||||
if (!DisplayController.isTransientTaskbar(mActivity) || !hasAnyFlag(FLAGS_IN_APP)) return;
|
||||
updateAndAnimateTransientTaskbar(!hasAnyFlag(FLAG_STASHED_IN_APP_AUTO));
|
||||
}
|
||||
|
||||
@@ -671,7 +570,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
/* isStashed= */ mActivity.isPhoneMode(),
|
||||
placeholderDuration,
|
||||
TRANSITION_UNSTASH_SUW_MANUAL,
|
||||
/* skipTaskbarBackgroundDelay */ false,
|
||||
/* jankTag= */ "SUW_MANUAL");
|
||||
animation.addListener(AnimatorListeners.forEndCallback(
|
||||
() -> mControllers.taskbarViewController.setDeferUpdatesForSUW(false)));
|
||||
@@ -681,14 +579,13 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
/**
|
||||
* Create a stash animation and save to {@link #mAnimator}.
|
||||
*
|
||||
* @param isStashed whether it's a stash animation or an unstash animation
|
||||
* @param duration duration of the animation
|
||||
* @param animationType what transition type to play.
|
||||
* @param shouldDelayBackground whether we should delay the taskbar bg animation
|
||||
* @param jankTag tag to be used in jank monitor trace.
|
||||
* @param isStashed whether it's a stash animation or an unstash animation
|
||||
* @param duration duration of the animation
|
||||
* @param animationType what transition type to play.
|
||||
* @param jankTag tag to be used in jank monitor trace.
|
||||
*/
|
||||
private void createAnimToIsStashed(boolean isStashed, long duration,
|
||||
@StashAnimation int animationType, boolean shouldDelayBackground, String jankTag) {
|
||||
@StashAnimation int animationType, String jankTag) {
|
||||
if (animationType == TRANSITION_UNSTASH_SUW_MANUAL && isStashed) {
|
||||
// The STASH_ANIMATION_SUW_MANUAL must only be used during an unstash animation.
|
||||
Log.e(TAG, "Illegal arguments:Using TRANSITION_UNSTASH_SUW_MANUAL to stash taskbar");
|
||||
@@ -700,7 +597,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
mAnimator = new AnimatorSet();
|
||||
addJankMonitorListener(
|
||||
mAnimator, /* expanding= */ !isStashed, /* tag= */ jankTag);
|
||||
final float stashTranslation = mActivity.isPhoneMode() || mActivity.isTransientTaskbar()
|
||||
boolean isTransientTaskbar = DisplayController.isTransientTaskbar(mActivity);
|
||||
final float stashTranslation = mActivity.isPhoneMode() || isTransientTaskbar
|
||||
? 0
|
||||
: (mUnstashedHeight - mStashedHeight);
|
||||
|
||||
@@ -724,9 +622,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
return;
|
||||
}
|
||||
|
||||
if (mActivity.isTransientTaskbar()) {
|
||||
createTransientAnimToIsStashed(mAnimator, isStashed, duration,
|
||||
shouldDelayBackground, animationType);
|
||||
if (isTransientTaskbar) {
|
||||
createTransientAnimToIsStashed(mAnimator, isStashed, duration, animationType);
|
||||
} else {
|
||||
createAnimToIsStashed(mAnimator, isStashed, duration, stashTranslation, animationType);
|
||||
}
|
||||
@@ -832,7 +729,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
|
||||
private void createTransientAnimToIsStashed(AnimatorSet as, boolean isStashed, long duration,
|
||||
boolean shouldDelayBackground, @StashAnimation int animationType) {
|
||||
@StashAnimation int animationType) {
|
||||
// Target values of the properties this is going to set
|
||||
final float backgroundOffsetTarget = isStashed ? 1 : 0;
|
||||
final float iconAlphaTarget = isStashed ? 0 : 1;
|
||||
@@ -846,14 +743,14 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
if (animationType == TRANSITION_HANDLE_FADE) {
|
||||
// When fading, the handle fades in/out at the beginning of the transition with
|
||||
// TASKBAR_STASH_ALPHA_DURATION.
|
||||
backgroundAndHandleAlphaDuration = TRANSIENT_TASKBAR_STASH_ALPHA_DURATION;
|
||||
backgroundAndHandleAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
|
||||
// The iconAlphaDuration must be set to duration for the skippable interpolators
|
||||
// below to work.
|
||||
iconAlphaDuration = duration;
|
||||
} else {
|
||||
iconAlphaStartDelay = TASKBAR_STASH_ALPHA_START_DELAY;
|
||||
iconAlphaDuration = TRANSIENT_TASKBAR_STASH_ALPHA_DURATION;
|
||||
backgroundAndHandleAlphaDuration = TRANSIENT_TASKBAR_STASH_ALPHA_DURATION;
|
||||
iconAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
|
||||
backgroundAndHandleAlphaDuration = TASKBAR_STASH_ALPHA_DURATION;
|
||||
|
||||
if (isStashed) {
|
||||
if (animationType == TRANSITION_HOME_TO_APP) {
|
||||
@@ -870,10 +767,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
backgroundAndHandleAlphaStartDelay,
|
||||
backgroundAndHandleAlphaDuration, LINEAR);
|
||||
|
||||
|
||||
if (enableScalingRevealHomeAnimation()
|
||||
&& !isStashed
|
||||
&& shouldDelayBackground) {
|
||||
if (enableScalingRevealHomeAnimation() && !isStashed) {
|
||||
play(as, getTaskbarBackgroundAnimatorWhenNotGoingHome(duration),
|
||||
0, 0, LINEAR);
|
||||
as.addListener(AnimatorListeners.forEndCallback(
|
||||
@@ -934,13 +828,17 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
private boolean mTaskbarBgAlphaAnimationStarted = false;
|
||||
@Override
|
||||
public void onAnimationUpdate(ValueAnimator valueAnimator) {
|
||||
if (mIsGoingHome) {
|
||||
mTaskbarBgAlphaAnimationStarted = true;
|
||||
}
|
||||
if (mTaskbarBgAlphaAnimationStarted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (valueAnimator.getAnimatedFraction() >= ANIMATED_FRACTION_THRESHOLD) {
|
||||
if (mUserIsNotGoingHome) {
|
||||
if (!mIsGoingHome) {
|
||||
playTaskbarBackgroundAlphaAnimation();
|
||||
setUserIsGoingHome(false);
|
||||
mTaskbarBgAlphaAnimationStarted = true;
|
||||
}
|
||||
}
|
||||
@@ -952,8 +850,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
/**
|
||||
* Sets whether the user is going home based on the current gesture.
|
||||
*/
|
||||
public void setUserIsNotGoingHome(boolean userIsNotGoingHome) {
|
||||
mUserIsNotGoingHome = userIsNotGoingHome;
|
||||
public void setUserIsGoingHome(boolean isGoingHome) {
|
||||
mIsGoingHome = isGoingHome;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -998,7 +896,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
int action = expanding ? InteractionJankMonitor.CUJ_TASKBAR_EXPAND :
|
||||
InteractionJankMonitor.CUJ_TASKBAR_COLLAPSE;
|
||||
animator.addListener(new AnimationSuccessListener() {
|
||||
animator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationStart(@NonNull Animator animation) {
|
||||
final Configuration.Builder builder =
|
||||
@@ -1010,16 +908,9 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationSuccess(@NonNull Animator animator) {
|
||||
public void onAnimationEnd(@NonNull Animator animation) {
|
||||
InteractionJankMonitor.getInstance().end(action);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(@NonNull Animator animation) {
|
||||
super.onAnimationCancel(animation);
|
||||
|
||||
InteractionJankMonitor.getInstance().cancel(action);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1049,29 +940,13 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
|
||||
public void applyState() {
|
||||
applyState(/* postApplyAction = */ null);
|
||||
}
|
||||
|
||||
/** Applies state and performs action after state is applied. */
|
||||
public void applyState(@Nullable Runnable postApplyAction) {
|
||||
applyState(hasAnyFlag(FLAG_IN_SETUP) ? 0 : TASKBAR_STASH_DURATION, postApplyAction);
|
||||
applyState(hasAnyFlag(FLAG_IN_SETUP) ? 0 : TASKBAR_STASH_DURATION);
|
||||
}
|
||||
|
||||
public void applyState(long duration) {
|
||||
applyState(duration, /* postApplyAction = */ null);
|
||||
}
|
||||
|
||||
private void applyState(long duration, @Nullable Runnable postApplyAction) {
|
||||
Animator animator = createApplyStateAnimator(duration);
|
||||
if (animator != null) {
|
||||
if (postApplyAction != null) {
|
||||
// performs action on animation end
|
||||
animator.addListener(AnimatorListeners.forEndCallback(postApplyAction));
|
||||
}
|
||||
animator.start();
|
||||
} else if (postApplyAction != null) {
|
||||
// animator was not created, just execute the action
|
||||
postApplyAction.run();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1089,9 +964,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
*/
|
||||
@Nullable
|
||||
public Animator createApplyStateAnimator(long duration) {
|
||||
if (mActivity.isPhoneMode()) {
|
||||
return null;
|
||||
}
|
||||
return mStatePropertyHolder.createSetStateAnimator(mState, duration);
|
||||
}
|
||||
|
||||
@@ -1124,9 +996,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
/**
|
||||
* When hiding the IME, delay the unstash animation to align with the end of the transition.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
long getTaskbarStashStartDelayForIme() {
|
||||
if (mIsImeVisible) {
|
||||
private long getTaskbarStashStartDelayForIme() {
|
||||
if (mIsImeShowing) {
|
||||
// Only delay when IME is exiting, not entering.
|
||||
return 0;
|
||||
}
|
||||
@@ -1142,36 +1013,28 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
long startDelay = 0;
|
||||
|
||||
updateStateForFlag(FLAG_STASHED_IN_APP_SYSUI, hasAnyFlag(systemUiStateFlags,
|
||||
SYSUI_STATE_DIALOG_SHOWING | (ENABLE_TASKBAR_BEHIND_SHADE.isTrue()
|
||||
? 0
|
||||
: SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE)
|
||||
));
|
||||
SYSUI_STATE_NOTIFICATION_PANEL_VISIBLE));
|
||||
|
||||
boolean stashForBubbles = hasAnyFlag(FLAG_IN_OVERVIEW)
|
||||
&& hasAnyFlag(systemUiStateFlags, SYSUI_STATE_BUBBLES_EXPANDED)
|
||||
&& mActivity.isTransientTaskbar();
|
||||
&& DisplayController.isTransientTaskbar(mActivity);
|
||||
updateStateForFlag(FLAG_STASHED_SYSUI,
|
||||
hasAnyFlag(systemUiStateFlags, SYSUI_STATE_SCREEN_PINNING) || stashForBubbles);
|
||||
updateStateForFlag(FLAG_STASHED_DEVICE_LOCKED,
|
||||
SystemUiFlagUtils.isLocked(systemUiStateFlags));
|
||||
|
||||
mIsImeVisible = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_VISIBLE);
|
||||
mIsImeShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SHOWING);
|
||||
mIsImeSwitcherShowing = hasAnyFlag(systemUiStateFlags, SYSUI_STATE_IME_SWITCHER_SHOWING);
|
||||
if (updateStateForFlag(FLAG_STASHED_IME, shouldStashForIme())) {
|
||||
animDuration = TASKBAR_STASH_DURATION_FOR_IME;
|
||||
startDelay = getTaskbarStashStartDelayForIme();
|
||||
}
|
||||
|
||||
if (isTaskbarHidden(systemUiStateFlags) && !hasAnyFlag(FLAG_TASKBAR_HIDDEN)) {
|
||||
updateStateForFlag(FLAG_TASKBAR_HIDDEN, isTaskbarHidden(systemUiStateFlags));
|
||||
applyState(0, SKIP_TOTAL_DREAM_ANIM_DURATION);
|
||||
} else {
|
||||
updateStateForFlag(FLAG_TASKBAR_HIDDEN, isTaskbarHidden(systemUiStateFlags));
|
||||
applyState(skipAnim ? 0 : animDuration, skipAnim ? 0 : startDelay);
|
||||
}
|
||||
applyState(skipAnim ? 0 : animDuration, skipAnim ? 0 : startDelay);
|
||||
}
|
||||
|
||||
/**
|
||||
* We stash when the IME is visible.
|
||||
* We stash when IME or IME switcher is showing.
|
||||
*
|
||||
* <p>Do not stash if in small screen, with 3 button nav, and in landscape (or seascape).
|
||||
* <p>Do not stash if taskbar is transient.
|
||||
@@ -1179,7 +1042,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
* <p>Do not stash if a system gesture is started.
|
||||
*/
|
||||
private boolean shouldStashForIme() {
|
||||
if (mActivity.isTransientTaskbar()) {
|
||||
if (DisplayController.isTransientTaskbar(mActivity)) {
|
||||
return false;
|
||||
}
|
||||
// Do not stash if in small screen, with 3 button nav, and in landscape.
|
||||
@@ -1189,16 +1052,16 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
}
|
||||
|
||||
// Do not stash if pinned taskbar, hardware keyboard is attached and no IME is docked
|
||||
if (mActivity.isHardwareKeyboard() && mActivity.isPinnedTaskbar()
|
||||
if (mActivity.isHardwareKeyboard() && DisplayController.isPinnedTaskbar(mActivity)
|
||||
&& !mActivity.isImeDocked()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Do not stash if hardware keyboard is attached, in 3 button nav and desktop windowing mode
|
||||
if (mActivity.isHardwareKeyboard()
|
||||
&& mActivity.isThreeButtonNav()
|
||||
&& mControllers.taskbarDesktopModeController
|
||||
.isInDesktopModeAndNotInOverview(mActivity.getDisplayId())) {
|
||||
DesktopVisibilityController visibilityController =
|
||||
LauncherActivityInterface.INSTANCE.getDesktopVisibilityController();
|
||||
if (visibilityController != null && mActivity.isHardwareKeyboard()
|
||||
&& mActivity.isThreeButtonNav() && visibilityController.areDesktopTasksVisible()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1207,7 +1070,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
return false;
|
||||
}
|
||||
|
||||
return mIsImeVisible;
|
||||
return mIsImeShowing || mIsImeSwitcherShowing;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1253,14 +1116,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_TRANSIENT_TASKBAR,
|
||||
!hasAnyFlag(FLAG_STASHED_IN_APP_AUTO));
|
||||
}
|
||||
if (hasAnyFlag(changedFlags, FLAG_IN_OVERVIEW | FLAG_IN_APP)) {
|
||||
mControllers.runAfterInit(() -> mControllers.taskbarInsetsController
|
||||
.onTaskbarOrBubblebarWindowHeightOrInsetsChanged());
|
||||
if (mInAppStateAffectsDesktopTasksVisibilityInTaskbar) {
|
||||
mControllers.runAfterInit(
|
||||
() -> mControllers.taskbarViewController.commitRunningAppsToUI());
|
||||
}
|
||||
}
|
||||
mActivity.applyForciblyShownFlagWhileTransientTaskbarUnstashed(!isStashedInApp());
|
||||
}
|
||||
|
||||
@@ -1275,8 +1130,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
*/
|
||||
public void setUpTaskbarSystemAction(boolean visible) {
|
||||
UI_HELPER_EXECUTOR.execute(() -> {
|
||||
if (!visible || !mActivity.isTransientTaskbar()
|
||||
|| mActivity.isPhoneMode()) {
|
||||
if (!visible || !DisplayController.isTransientTaskbar(mActivity)) {
|
||||
mAccessibilityManager.unregisterSystemAction(SYSTEM_ACTION_ID_TASKBAR);
|
||||
mIsTaskbarSystemActionRegistered = false;
|
||||
return;
|
||||
@@ -1300,12 +1154,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
* Clean up on destroy from TaskbarControllers
|
||||
*/
|
||||
public void onDestroy() {
|
||||
// If the controller is destroyed before the animation finishes, we cancel the animation
|
||||
// so that we don't finish the CUJ.
|
||||
if (mAnimator != null) {
|
||||
mAnimator.cancel();
|
||||
mAnimator = null;
|
||||
}
|
||||
UI_HELPER_EXECUTOR.execute(
|
||||
() -> mAccessibilityManager.unregisterSystemAction(SYSTEM_ACTION_ID_TASKBAR));
|
||||
}
|
||||
@@ -1326,7 +1174,7 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
* If false, attempts to re/start the timeout
|
||||
*/
|
||||
public void updateTaskbarTimeout(boolean isAutohideSuspended) {
|
||||
if (!mActivity.isTransientTaskbar()) {
|
||||
if (!DisplayController.isTransientTaskbar(mActivity)) {
|
||||
return;
|
||||
}
|
||||
if (isAutohideSuspended) {
|
||||
@@ -1339,8 +1187,10 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
/**
|
||||
* Attempts to start timer to auto hide the taskbar based on time.
|
||||
*/
|
||||
private void tryStartTaskbarTimeout() {
|
||||
if (!mActivity.isTransientTaskbar() || mIsStashed || mEnableBlockingTimeoutDuringTests) {
|
||||
public void tryStartTaskbarTimeout() {
|
||||
if (!DisplayController.isTransientTaskbar(mActivity)
|
||||
|| mIsStashed
|
||||
|| mEnableBlockingTimeoutDuringTests) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1365,11 +1215,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
updateAndAnimateTransientTaskbarForTimeout();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
Alarm getTimeoutAlarm() {
|
||||
return mTimeoutAlarm;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dumpLogs(String prefix, PrintWriter pw) {
|
||||
pw.println(prefix + "TaskbarStashController:");
|
||||
@@ -1380,7 +1225,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
pw.println(prefix + "\tappliedState=" + getStateString(mStatePropertyHolder.mPrevFlags));
|
||||
pw.println(prefix + "\tmState=" + getStateString(mState));
|
||||
pw.println(prefix + "\tmIsSystemGestureInProgress=" + mIsSystemGestureInProgress);
|
||||
pw.println(prefix + "\tmIsImeVisible=" + mIsImeVisible);
|
||||
pw.println(prefix + "\tmIsImeShowing=" + mIsImeShowing);
|
||||
pw.println(prefix + "\tmIsImeSwitcherShowing=" + mIsImeSwitcherShowing);
|
||||
}
|
||||
|
||||
private static String getStateString(long flags) {
|
||||
@@ -1422,13 +1268,6 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
*/
|
||||
@Nullable
|
||||
public Animator createSetStateAnimator(long flags, long duration) {
|
||||
// We do this when we want to synchronize the app launch and taskbar stash animations.
|
||||
if (syncAppLaunchWithTaskbarStash()
|
||||
&& hasAnyFlag(FLAG_IGNORE_IN_APP)
|
||||
&& hasAnyFlag(flags, FLAG_IN_APP)) {
|
||||
flags = flags & ~FLAG_IN_APP;
|
||||
}
|
||||
|
||||
boolean isStashed = mStashCondition.test(flags);
|
||||
|
||||
if (DEBUG) {
|
||||
@@ -1478,9 +1317,8 @@ public class TaskbarStashController implements TaskbarControllers.LoggableTaskba
|
||||
mIsStashed = isStashed;
|
||||
mLastStartedTransitionType = animationType;
|
||||
|
||||
boolean shouldDelayBackground = hasAnyFlag(FLAG_DELAY_TASKBAR_BG_TAG);
|
||||
// This sets mAnimator.
|
||||
createAnimToIsStashed(mIsStashed, duration, animationType, shouldDelayBackground,
|
||||
createAnimToIsStashed(mIsStashed, duration, animationType,
|
||||
computeTaskbarJankMonitorTag(changedFlags));
|
||||
return mAnimator;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.android.launcher3.testing.shared.ResourceUtils
|
||||
import com.android.launcher3.touch.SingleAxisSwipeDetector
|
||||
import com.android.launcher3.touch.SingleAxisSwipeDetector.DIRECTION_NEGATIVE
|
||||
import com.android.launcher3.touch.SingleAxisSwipeDetector.VERTICAL
|
||||
import com.android.launcher3.util.DisplayController
|
||||
import com.android.launcher3.util.TouchController
|
||||
import com.android.quickstep.inputconsumers.TaskbarUnstashInputConsumer
|
||||
|
||||
@@ -38,7 +39,7 @@ import com.android.quickstep.inputconsumers.TaskbarUnstashInputConsumer
|
||||
class TaskbarStashViaTouchController(val controllers: TaskbarControllers) : TouchController {
|
||||
|
||||
private val activity: TaskbarActivityContext = controllers.taskbarActivityContext
|
||||
private val enabled = activity.isTransientTaskbar
|
||||
private val enabled = DisplayController.isTransientTaskbar(activity)
|
||||
private val swipeDownDetector: SingleAxisSwipeDetector
|
||||
private val translationCallback = controllers.taskbarTranslationController.transitionCallback
|
||||
/** Interpolator to apply resistance as user swipes down to the bottom of the screen. */
|
||||
@@ -66,7 +67,7 @@ class TaskbarStashViaTouchController(val controllers: TaskbarControllers) : Touc
|
||||
val gestureHeight: Int =
|
||||
ResourceUtils.getNavbarSize(
|
||||
ResourceUtils.NAVBAR_BOTTOM_GESTURE_SIZE,
|
||||
activity.resources,
|
||||
activity.resources
|
||||
)
|
||||
gestureHeightYThreshold = (activity.deviceProfile.heightPx - gestureHeight).toFloat()
|
||||
}
|
||||
@@ -88,7 +89,7 @@ class TaskbarStashViaTouchController(val controllers: TaskbarControllers) : Touc
|
||||
maxTouchDisplacement,
|
||||
0f,
|
||||
maxVisualDisplacement,
|
||||
displacementInterpolator,
|
||||
displacementInterpolator
|
||||
)
|
||||
)
|
||||
return false
|
||||
|
||||
@@ -25,6 +25,7 @@ import androidx.core.content.res.ResourcesCompat;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
|
||||
/**
|
||||
* Utility class that contains the different taskbar thresholds logic.
|
||||
@@ -38,6 +39,10 @@ public class TaskbarThresholdUtils {
|
||||
|
||||
private static int getThreshold(Resources r, DeviceProfile dp, int thresholdDimen,
|
||||
int multiplierDimen) {
|
||||
if (!FeatureFlags.ENABLE_DYNAMIC_TASKBAR_THRESHOLDS.get()) {
|
||||
return r.getDimensionPixelSize(thresholdDimen);
|
||||
}
|
||||
|
||||
float landscapeScreenHeight = dp.isLandscape ? dp.heightPx : dp.widthPx;
|
||||
float screenPart = (landscapeScreenHeight * SCREEN_UNITS);
|
||||
float defaultDp = dpiFromPx(screenPart, DisplayMetrics.DENSITY_DEVICE_STABLE);
|
||||
|
||||
@@ -29,6 +29,7 @@ import androidx.dynamicanimation.animation.SpringForce;
|
||||
import com.android.app.animation.Interpolators;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.anim.SpringAnimationBuilder;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
@@ -62,7 +63,7 @@ public class TaskbarTranslationController implements TaskbarControllers.Loggable
|
||||
|
||||
public TaskbarTranslationController(TaskbarActivityContext context) {
|
||||
mContext = context;
|
||||
mIsTransientTaskbar = mContext.isTransientTaskbar();
|
||||
mIsTransientTaskbar = DisplayController.isTransientTaskbar(mContext);
|
||||
mCallback = new TransitionCallback();
|
||||
}
|
||||
|
||||
@@ -94,8 +95,7 @@ public class TaskbarTranslationController implements TaskbarControllers.Loggable
|
||||
mControllers.taskbarDragLayerController.setTranslationYForSwipe(transY);
|
||||
mControllers.bubbleControllers.ifPresent(controllers -> {
|
||||
controllers.bubbleBarViewController.setTranslationYForSwipe(transY);
|
||||
controllers.bubbleStashedHandleViewController.ifPresent(
|
||||
controller -> controller.setTranslationYForSwipe(transY));
|
||||
controllers.bubbleStashedHandleViewController.setTranslationYForSwipe(transY);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ import static android.app.ActivityTaskManager.INVALID_TASK_ID;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT;
|
||||
import static com.android.launcher3.LauncherSettings.Favorites.CONTAINER_HOTSEAT_PREDICTION;
|
||||
import static com.android.launcher3.taskbar.TaskbarStashController.FLAG_IN_APP;
|
||||
import static com.android.quickstep.OverviewCommandHelper.TYPE_HIDE;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.content.Intent;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.view.MotionEvent;
|
||||
@@ -37,17 +37,16 @@ import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.model.data.ItemInfoWithIcon;
|
||||
import com.android.launcher3.popup.SystemShortcut;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarController;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions;
|
||||
import com.android.quickstep.GestureState;
|
||||
import com.android.quickstep.RecentsAnimationCallbacks;
|
||||
import com.android.quickstep.util.SplitTask;
|
||||
import com.android.quickstep.OverviewCommandHelper;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
import com.android.quickstep.util.TISBindHelper;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.TaskContainer;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
import com.android.quickstep.views.TaskView.TaskContainer;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
|
||||
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Collections;
|
||||
@@ -56,14 +55,12 @@ import java.util.stream.Stream;
|
||||
/**
|
||||
* Base class for providing different taskbar UI
|
||||
*/
|
||||
public class TaskbarUIController implements BubbleBarController.BubbleBarLocationListener {
|
||||
public class TaskbarUIController {
|
||||
public static final TaskbarUIController DEFAULT = new TaskbarUIController();
|
||||
|
||||
// Initialized in init.
|
||||
protected TaskbarControllers mControllers;
|
||||
|
||||
protected boolean mSkipLauncherVisibilityChange;
|
||||
|
||||
@CallSuper
|
||||
protected void init(TaskbarControllers taskbarControllers) {
|
||||
mControllers = taskbarControllers;
|
||||
@@ -79,27 +76,41 @@ public class TaskbarUIController implements BubbleBarController.BubbleBarLocatio
|
||||
}
|
||||
|
||||
/**
|
||||
* This should only be called by TaskbarStashController so that a TaskbarUIController can
|
||||
* This should only be called by TaskbarStashController so that a
|
||||
* TaskbarUIController can
|
||||
* disable stashing. All other controllers should use
|
||||
* {@link TaskbarStashController#supportsVisualStashing()} as the source of truth.
|
||||
* {@link TaskbarStashController#supportsVisualStashing()} as the source of
|
||||
* truth.
|
||||
*/
|
||||
public boolean supportsVisualStashing() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected void onStashedInAppChanged() { }
|
||||
protected void onStashedInAppChanged() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when taskbar icon layout bounds change.
|
||||
*/
|
||||
protected void onIconLayoutBoundsChanged() { }
|
||||
protected void onIconLayoutBoundsChanged() {
|
||||
}
|
||||
|
||||
protected String getTaskbarUIControllerName() {
|
||||
return "TaskbarUIController";
|
||||
}
|
||||
|
||||
/** Called when an icon is launched. */
|
||||
public void onTaskbarIconLaunched(ItemInfo item) { }
|
||||
@CallSuper
|
||||
public void onTaskbarIconLaunched(ItemInfo item) {
|
||||
// When launching from Taskbar, e.g. from Overview, set FLAG_IN_APP immediately
|
||||
// instead of
|
||||
// waiting for onPause, to reduce potential visual noise during the app open
|
||||
// transition.
|
||||
if (mControllers.taskbarStashController == null)
|
||||
return;
|
||||
mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_APP, true);
|
||||
mControllers.taskbarStashController.applyState();
|
||||
}
|
||||
|
||||
public View getRootView() {
|
||||
return mControllers.taskbarActivityContext.getDragLayer();
|
||||
@@ -107,7 +118,9 @@ public class TaskbarUIController implements BubbleBarController.BubbleBarLocatio
|
||||
|
||||
/**
|
||||
* Called when swiping from the bottom nav region in fully gestural mode.
|
||||
* @param inProgress True if the animation started, false if we just settled on an end target.
|
||||
*
|
||||
* @param inProgress True if the animation started, false if we just settled on
|
||||
* an end target.
|
||||
*/
|
||||
public void setSystemGestureInProgress(boolean inProgress) {
|
||||
mControllers.taskbarStashController.setSystemGestureInProgress(inProgress);
|
||||
@@ -117,15 +130,15 @@ public class TaskbarUIController implements BubbleBarController.BubbleBarLocatio
|
||||
* Manually closes the overlay window.
|
||||
*/
|
||||
public void hideOverlayWindow() {
|
||||
mControllers.keyboardQuickSwitchController.closeQuickSwitchView();
|
||||
boolean isTransientTaskbar = mControllers.taskbarActivityContext.isTransientTaskbar();
|
||||
if (!isTransientTaskbar || mControllers.taskbarAllAppsController.isOpen()) {
|
||||
if (!DisplayController.isTransientTaskbar(mControllers.taskbarActivityContext)
|
||||
|| mControllers.taskbarAllAppsController.isOpen()) {
|
||||
mControllers.taskbarOverlayController.hideWindow();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* User expands PiP to full-screen (or split-screen) mode, try to hide the Taskbar.
|
||||
* User expands PiP to full-screen (or split-screen) mode, try to hide the
|
||||
* Taskbar.
|
||||
*/
|
||||
public void onExpandPip() {
|
||||
if (mControllers != null) {
|
||||
@@ -164,18 +177,21 @@ public class TaskbarUIController implements BubbleBarController.BubbleBarLocatio
|
||||
|
||||
/**
|
||||
* @param ev MotionEvent in screen coordinates.
|
||||
* @return Whether any Taskbar item could handle the given MotionEvent if given the chance.
|
||||
*
|
||||
* @return Whether any Taskbar item could handle the given MotionEvent if given
|
||||
* the chance.
|
||||
*/
|
||||
public boolean isEventOverAnyTaskbarItem(MotionEvent ev) {
|
||||
return mControllers.taskbarViewController.isEventOverAnyItem(ev)
|
||||
|| mControllers.navbarButtonsViewController.isEventOverAnyItem(ev);
|
||||
}
|
||||
|
||||
/** Checks if the given {@link MotionEvent} is over the bubble bar views. */
|
||||
public boolean isEventOverBubbleBarViews(MotionEvent ev) {
|
||||
/**
|
||||
* Checks if the given {@link MotionEvent} is over the bubble bar stash handle.
|
||||
*/
|
||||
public boolean isEventOverBubbleBarStashHandle(MotionEvent ev) {
|
||||
return mControllers.bubbleControllers.map(
|
||||
bubbleControllers ->
|
||||
bubbleControllers.bubbleStashController.isEventOverBubbleBarViews(ev))
|
||||
bubbleControllers -> bubbleControllers.bubbleStashController.isEventOverStashHandle(ev))
|
||||
.orElse(false);
|
||||
}
|
||||
|
||||
@@ -187,39 +203,23 @@ public class TaskbarUIController implements BubbleBarController.BubbleBarLocatio
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if hotseat icons are on top of view hierarchy when aligned in the current state.
|
||||
* Returns true if hotseat icons are on top of view hierarchy when aligned in
|
||||
* the current state.
|
||||
*/
|
||||
public boolean isHotseatIconOnTopWhenAligned() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isAnimatingToHotseat() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips to the end of the animation to Hotseat - should only be used if
|
||||
* {@link #isAnimatingToHotseat()} returns true.
|
||||
*/
|
||||
public void endAnimationToHotseat() {}
|
||||
|
||||
/** Returns {@code true} if Taskbar is currently within overview. */
|
||||
protected boolean isInOverviewUi() {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Toggles all apps UI. Default implementation opens Taskbar All Apps, but may be overridden to
|
||||
* open different Alls Apps variant depending on the context.
|
||||
* @param focusSearch indicates whether All Apps should be opened with search input focused.
|
||||
* Returns {@code true} if Home All Apps available instead of Taskbar All Apps.
|
||||
*/
|
||||
protected void toggleAllApps(boolean focusSearch) {
|
||||
if (focusSearch) {
|
||||
mControllers.taskbarAllAppsController.toggleSearch();
|
||||
} else {
|
||||
mControllers.taskbarAllAppsController.toggle();
|
||||
}
|
||||
protected boolean canToggleHomeAllApps() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@@ -232,8 +232,10 @@ public class TaskbarUIController implements BubbleBarController.BubbleBarLocatio
|
||||
|
||||
/**
|
||||
* Returns RecentsView. Overwritten in LauncherTaskbarUIController and
|
||||
* FallbackTaskbarUIController with Launcher-specific implementations. Returns null for other
|
||||
* UI controllers (like DesktopTaskbarUIController) that don't have a RecentsView.
|
||||
* FallbackTaskbarUIController with Launcher-specific implementations. Returns
|
||||
* null for other
|
||||
* UI controllers (like DesktopTaskbarUIController) that don't have a
|
||||
* RecentsView.
|
||||
*/
|
||||
public @Nullable RecentsView getRecentsView() {
|
||||
return null;
|
||||
@@ -246,36 +248,30 @@ public class TaskbarUIController implements BubbleBarController.BubbleBarLocatio
|
||||
}
|
||||
|
||||
recentsView.getSplitSelectController().findLastActiveTasksAndRunCallback(
|
||||
Collections.singletonList(splitSelectSource.getItemInfo().getComponentKey()),
|
||||
Collections.singletonList(splitSelectSource.itemInfo.getComponentKey()),
|
||||
false /* findExactPairMatch */,
|
||||
foundTasks -> {
|
||||
@Nullable Task foundTask = foundTasks[0];
|
||||
@Nullable
|
||||
Task foundTask = foundTasks[0];
|
||||
splitSelectSource.alreadyRunningTaskId = foundTask == null
|
||||
? INVALID_TASK_ID
|
||||
: foundTask.key.id;
|
||||
splitSelectSource.animateCurrentTaskDismissal = foundTask != null;
|
||||
recentsView.initiateSplitSelect(splitSelectSource);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the clicked Taskbar icon to launch a second app for splitscreen.
|
||||
*/
|
||||
public void triggerSecondAppForSplit(ItemInfoWithIcon info, Intent intent, View startingView) {
|
||||
// When launching from Taskbar, e.g. from Overview, set FLAG_IN_APP immediately
|
||||
// to reduce potential visual noise during the app open transition.
|
||||
if (mControllers.taskbarStashController != null) {
|
||||
mControllers.taskbarStashController.updateStateForFlag(FLAG_IN_APP, true);
|
||||
mControllers.taskbarStashController.applyState();
|
||||
}
|
||||
|
||||
RecentsView recents = getRecentsView();
|
||||
recents.getSplitSelectController().findLastActiveTasksAndRunCallback(
|
||||
Collections.singletonList(info.getComponentKey()),
|
||||
false /* findExactPairMatch */,
|
||||
foundTasks -> {
|
||||
@Nullable Task foundTask = foundTasks[0];
|
||||
@Nullable
|
||||
Task foundTask = foundTasks[0];
|
||||
if (foundTask != null) {
|
||||
TaskView foundTaskView = recents.getTaskViewByTaskId(foundTask.key.id);
|
||||
// TODO (b/266482558): This additional null check is needed because there
|
||||
@@ -286,14 +282,13 @@ public class TaskbarUIController implements BubbleBarController.BubbleBarLocatio
|
||||
if (foundTaskView != null) {
|
||||
// There is already a running app of this type, use that as second app.
|
||||
// Get index of task (0 or 1), in case it's a GroupedTaskView
|
||||
TaskContainer taskContainer =
|
||||
foundTaskView.getTaskContainerById(foundTask.key.id);
|
||||
TaskContainer taskContainer = foundTaskView.getTaskContainerById(foundTask.key.id);
|
||||
recents.confirmSplitSelect(
|
||||
foundTaskView,
|
||||
foundTask,
|
||||
taskContainer.getIconView().getDrawable(),
|
||||
taskContainer.getSnapshotView(),
|
||||
taskContainer.getThumbnail(),
|
||||
taskContainer.getThumbnailViewDeprecated(),
|
||||
taskContainer.getThumbnailViewDeprecated().getThumbnail(),
|
||||
null /* intent */,
|
||||
null /* user */,
|
||||
info);
|
||||
@@ -311,14 +306,14 @@ public class TaskbarUIController implements BubbleBarController.BubbleBarLocatio
|
||||
intent,
|
||||
info.user,
|
||||
info);
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the Keyboard Quick Switch View.
|
||||
*
|
||||
* This will set the focus to the first task from the right (from the left in RTL)
|
||||
* This will set the focus to the first task from the right (from the left in
|
||||
* RTL)
|
||||
*/
|
||||
public void openQuickSwitchView() {
|
||||
mControllers.keyboardQuickSwitchController.openQuickSwitchView();
|
||||
@@ -327,10 +322,13 @@ public class TaskbarUIController implements BubbleBarController.BubbleBarLocatio
|
||||
/**
|
||||
* Launches the focused task and closes the Keyboard Quick Switch View.
|
||||
*
|
||||
* If the overlay or view are closed, or the overview task is focused, then Overview is
|
||||
* launched. If the overview task is launched, then the first hidden task is focused.
|
||||
* If the overlay or view are closed, or the overview task is focused, then
|
||||
* Overview is
|
||||
* launched. If the overview task is launched, then the first hidden task is
|
||||
* focused.
|
||||
*
|
||||
* @return the index of what task should be focused in ; -1 iff Overview shouldn't be launched
|
||||
* @return the index of what task should be focused in ; -1 iff Overview
|
||||
* shouldn't be launched
|
||||
*/
|
||||
public int launchFocusedTask() {
|
||||
int focusedTaskIndex = mControllers.keyboardQuickSwitchController.launchFocusedTask();
|
||||
@@ -342,10 +340,12 @@ public class TaskbarUIController implements BubbleBarController.BubbleBarLocatio
|
||||
* Launches the given task in split-screen.
|
||||
*/
|
||||
public void launchSplitTasks(
|
||||
@NonNull SplitTask splitTask, @Nullable RemoteTransition remoteTransition) { }
|
||||
@NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the matching view (if any) in the taskbar.
|
||||
*
|
||||
* @param view The view to match.
|
||||
*/
|
||||
public @Nullable View findMatchingView(View view) {
|
||||
@@ -357,7 +357,8 @@ public class TaskbarUIController implements BubbleBarController.BubbleBarLocatio
|
||||
return null;
|
||||
}
|
||||
|
||||
// Taskbar has the same items as the hotseat and we can use screenId to find the match.
|
||||
// Taskbar has the same items as the hotseat and we can use screenId to find the
|
||||
// match.
|
||||
int screenId = info.screenId;
|
||||
View[] views = mControllers.taskbarViewController.getIconViews();
|
||||
for (int i = views.length - 1; i >= 0; --i) {
|
||||
@@ -371,7 +372,9 @@ public class TaskbarUIController implements BubbleBarController.BubbleBarLocatio
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for when launcher state transition completes after user swipes to home.
|
||||
* Callback for when launcher state transition completes after user swipes to
|
||||
* home.
|
||||
*
|
||||
* @param finalState The final state of the transition.
|
||||
*/
|
||||
public void onStateTransitionCompletedAfterSwipeToHome(LauncherState finalState) {
|
||||
@@ -381,7 +384,8 @@ public class TaskbarUIController implements BubbleBarController.BubbleBarLocatio
|
||||
/**
|
||||
* Refreshes the resumed state of this ui controller.
|
||||
*/
|
||||
public void refreshResumedState() {}
|
||||
public void refreshResumedState() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a stream of split screen menu options appropriate to the device.
|
||||
@@ -394,19 +398,35 @@ public class TaskbarUIController implements BubbleBarController.BubbleBarLocatio
|
||||
}
|
||||
|
||||
/** Adjusts the hotseat for the bubble bar. */
|
||||
public void adjustHotseatForBubbleBar(boolean isBubbleBarVisible) {}
|
||||
public void adjustHotseatForBubbleBar(boolean isBubbleBarVisible) {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected TISBindHelper getTISBindHelper() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the focused task in the Keyboard Quick Switch view through the OverviewCommandHelper
|
||||
* Launches the focused task in the Keyboard Quick Switch view through the
|
||||
* OverviewCommandHelper
|
||||
* <p>
|
||||
* Use this helper method when the focused task may be the overview task.
|
||||
*/
|
||||
public void launchKeyboardFocusedTask() {
|
||||
mControllers.navButtonController.hideOverview();
|
||||
TISBindHelper tisBindHelper = getTISBindHelper();
|
||||
if (tisBindHelper == null) {
|
||||
return;
|
||||
}
|
||||
OverviewCommandHelper overviewCommandHelper = tisBindHelper.getOverviewCommandHelper();
|
||||
if (overviewCommandHelper == null) {
|
||||
return;
|
||||
}
|
||||
overviewCommandHelper.addCommand(TYPE_HIDE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the taskbar based on the visibility of the launcher.
|
||||
*
|
||||
* @param isVisible True if launcher is visible, false otherwise.
|
||||
*/
|
||||
public void onLauncherVisibilityChanged(boolean isVisible) {
|
||||
@@ -415,7 +435,8 @@ public class TaskbarUIController implements BubbleBarController.BubbleBarLocatio
|
||||
}
|
||||
|
||||
/**
|
||||
* Request for UI controller to ignore animations for the next callback for the end of recents
|
||||
* Request for UI controller to ignore animations for the next callback for the
|
||||
* end of recents
|
||||
* animation
|
||||
*/
|
||||
public void setSkipNextRecentsAnimEnd() {
|
||||
@@ -425,51 +446,7 @@ public class TaskbarUIController implements BubbleBarController.BubbleBarLocatio
|
||||
/**
|
||||
* Sets whether the user is going home based on the current gesture.
|
||||
*/
|
||||
public void setUserIsNotGoingHome(boolean isNotGoingHome) {
|
||||
mControllers.taskbarStashController.setUserIsNotGoingHome(isNotGoingHome);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether to prevent taskbar from reacting to launcher visibility during the recents
|
||||
* transition animation.
|
||||
*/
|
||||
public void setSkipLauncherVisibilityChange(boolean skip) {
|
||||
mSkipLauncherVisibilityChange = skip;
|
||||
}
|
||||
|
||||
/** Sets whether the hotseat is stashed */
|
||||
public void stashHotseat(boolean stash) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBubbleBarLocationAnimated(BubbleBarLocation location) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBubbleBarLocationUpdated(BubbleBarLocation location) {
|
||||
}
|
||||
|
||||
/** Un-stash the hotseat instantly */
|
||||
public void unStashHotseatInstantly() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when we want to unstash taskbar when user performs swipes up gesture.
|
||||
*/
|
||||
public void onSwipeToUnstashTaskbar() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called at the end of a gesture (see {@link GestureState.GestureEndTarget}).
|
||||
* @param endTarget Where the gesture animation is going to.
|
||||
* @param callbacks callbacks to track the recents animation lifecycle. The state change is
|
||||
* automatically reset once the recents animation finishes
|
||||
* @return An optional Animator to play in parallel with the default gesture end animation.
|
||||
*/
|
||||
public @Nullable Animator getParallelAnimationToGestureEndTarget(
|
||||
GestureState.GestureEndTarget endTarget,
|
||||
long duration,
|
||||
RecentsAnimationCallbacks callbacks) {
|
||||
return null;
|
||||
public void setUserIsGoingHome(boolean isGoingHome) {
|
||||
mControllers.taskbarStashController.setUserIsGoingHome(isGoingHome);
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -16,28 +16,15 @@
|
||||
|
||||
package com.android.launcher3.taskbar;
|
||||
|
||||
import static android.window.DesktopModeFlags.ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION;
|
||||
|
||||
import static com.android.launcher3.config.FeatureFlags.enableTaskbarPinning;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_LONG_PRESS;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP;
|
||||
import static com.android.launcher3.taskbar.TaskbarAutohideSuspendController.FLAG_AUTOHIDE_SUSPEND_TASKBAR_OVERFLOW;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.view.GestureDetector;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.InputDevice;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.internal.jank.Cuj;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController;
|
||||
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
|
||||
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
|
||||
|
||||
/**
|
||||
* Callbacks for {@link TaskbarView} to interact with its controller.
|
||||
@@ -47,14 +34,12 @@ public class TaskbarViewCallbacks {
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private final TaskbarControllers mControllers;
|
||||
private final TaskbarView mTaskbarView;
|
||||
private final GestureDetector mGestureDetector;
|
||||
|
||||
public TaskbarViewCallbacks(TaskbarActivityContext activity, TaskbarControllers controllers,
|
||||
TaskbarView taskbarView) {
|
||||
mActivity = activity;
|
||||
mControllers = controllers;
|
||||
mTaskbarView = taskbarView;
|
||||
mGestureDetector = new GestureDetector(activity, new TaskbarViewGestureListener());
|
||||
}
|
||||
|
||||
public View.OnClickListener getIconOnClickListener() {
|
||||
@@ -62,40 +47,25 @@ public class TaskbarViewCallbacks {
|
||||
}
|
||||
|
||||
/** Trigger All Apps button click action. */
|
||||
public void triggerAllAppsButtonClick(View v) {
|
||||
protected void triggerAllAppsButtonClick(View v) {
|
||||
InteractionJankMonitorWrapper.begin(v, Cuj.CUJ_LAUNCHER_OPEN_ALL_APPS,
|
||||
/* tag= */ "TASKBAR_BUTTON");
|
||||
mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_ALLAPPS_BUTTON_TAP);
|
||||
if (mActivity.showLockedTaskbarOnHome()
|
||||
|| mActivity.showDesktopTaskbarForFreeformDisplay()) {
|
||||
// If the taskbar can be shown on the home screen, use mAllAppsToggler to toggle all
|
||||
// apps, which will toggle the launcher activity all apps when on home screen.
|
||||
// TODO(b/395913143): Reconsider this if a gap in taskbar all apps functionality that
|
||||
// prevents users to drag items to workspace is addressed.
|
||||
mControllers.uiController.toggleAllApps(false);
|
||||
} else {
|
||||
mControllers.taskbarAllAppsController.toggle();
|
||||
}
|
||||
mControllers.taskbarAllAppsController.toggle();
|
||||
}
|
||||
|
||||
/** Trigger All Apps button long click action. */
|
||||
public void triggerAllAppsButtonLongClick() {
|
||||
protected void triggerAllAppsButtonLongClick() {
|
||||
mActivity.getStatsLogManager().logger().log(LAUNCHER_TASKBAR_ALLAPPS_BUTTON_LONG_PRESS);
|
||||
}
|
||||
|
||||
/** @return true if haptic feedback should occur when long pressing the all apps button. */
|
||||
public boolean isAllAppsButtonHapticFeedbackEnabled(Context context) {
|
||||
public boolean isAllAppsButtonHapticFeedbackEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
public View.OnTouchListener getTaskbarTouchListener() {
|
||||
return (view, event) -> mGestureDetector.onTouchEvent(event);
|
||||
}
|
||||
|
||||
public View.OnLongClickListener getTaskbarDividerLongClickListener() {
|
||||
return v -> {
|
||||
mControllers.taskbarPinningController.showPinningView(v, getDividerCenterX());
|
||||
mControllers.taskbarPinningController.showPinningView(v);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
@@ -103,9 +73,8 @@ public class TaskbarViewCallbacks {
|
||||
public View.OnTouchListener getTaskbarDividerRightClickListener() {
|
||||
return (v, event) -> {
|
||||
if (event.isFromSource(InputDevice.SOURCE_MOUSE)
|
||||
&& event.getAction() == MotionEvent.ACTION_DOWN
|
||||
&& event.getButtonState() == MotionEvent.BUTTON_SECONDARY) {
|
||||
mControllers.taskbarPinningController.showPinningView(v, getDividerCenterX());
|
||||
mControllers.taskbarPinningController.showPinningView(v);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -121,13 +90,6 @@ public class TaskbarViewCallbacks {
|
||||
return new TaskbarHoverToolTipController(mActivity, mTaskbarView, icon);
|
||||
}
|
||||
|
||||
/** Callback invoked before Taskbar icons are laid out. */
|
||||
void onPreLayoutChildren() {
|
||||
if (enableTaskbarPinning() && ENABLE_TASKBAR_RECENTS_LAYOUT_TRANSITION.isTrue()) {
|
||||
mControllers.taskbarViewController.updateTaskbarIconTranslationXForPinning();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies launcher to update icon alignment.
|
||||
*/
|
||||
@@ -142,116 +104,4 @@ public class TaskbarViewCallbacks {
|
||||
mControllers.taskbarScrimViewController.onTaskbarVisibilityChanged(
|
||||
mTaskbarView.getVisibility());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current location of bubble bar, if it is visible.
|
||||
* Returns {@code null} if bubble bar is not shown.
|
||||
*/
|
||||
@Nullable
|
||||
public BubbleBarLocation getBubbleBarLocationIfVisible() {
|
||||
BubbleBarViewController bubbleBarViewController =
|
||||
mControllers.bubbleControllers.map(c -> c.bubbleBarViewController).orElse(null);
|
||||
if (bubbleBarViewController != null && bubbleBarViewController.isBubbleBarVisible()) {
|
||||
return bubbleBarViewController.getBubbleBarLocation();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the max bubble bar collapsed width for the current bubble bar visibility state. Used to
|
||||
* reserve space for the bubble bar when transitioning taskbar view into overflow.
|
||||
*/
|
||||
public float getBubbleBarMaxCollapsedWidthIfVisible() {
|
||||
return mControllers.bubbleControllers
|
||||
.filter(c -> !c.bubbleBarViewController.isHiddenForNoBubbles())
|
||||
.map(c -> c.bubbleBarViewController.getCollapsedWidthWithMaxVisibleBubbles())
|
||||
.orElse(0f);
|
||||
}
|
||||
|
||||
/** Returns true if bubble bar controllers are present. */
|
||||
public boolean isBubbleBarEnabled() {
|
||||
return mControllers.bubbleControllers.isPresent();
|
||||
}
|
||||
|
||||
/** Returns on click listener for the taskbar overflow view. */
|
||||
public View.OnClickListener getOverflowOnClickListener() {
|
||||
return new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
toggleKeyboardQuickSwitchView();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Returns on long click listener for the taskbar overflow view. */
|
||||
public View.OnLongClickListener getOverflowOnLongClickListener() {
|
||||
return new View.OnLongClickListener() {
|
||||
@Override
|
||||
public boolean onLongClick(View v) {
|
||||
toggleKeyboardQuickSwitchView();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private void toggleKeyboardQuickSwitchView() {
|
||||
if (mTaskbarView.getTaskbarOverflowView() != null) {
|
||||
mTaskbarView.getTaskbarOverflowView().setIsActive(
|
||||
!mTaskbarView.getTaskbarOverflowView().getIsActive());
|
||||
mControllers.taskbarAutohideSuspendController
|
||||
.updateFlag(FLAG_AUTOHIDE_SUSPEND_TASKBAR_OVERFLOW,
|
||||
mTaskbarView.getTaskbarOverflowView().getIsActive());
|
||||
}
|
||||
mControllers.keyboardQuickSwitchController.toggleQuickSwitchViewForTaskbar(
|
||||
mControllers.taskbarViewController.getTaskIdsForPinnedApps(),
|
||||
this::onKeyboardQuickSwitchViewClosed);
|
||||
}
|
||||
|
||||
private void onKeyboardQuickSwitchViewClosed() {
|
||||
if (mTaskbarView.getTaskbarOverflowView() != null) {
|
||||
mTaskbarView.getTaskbarOverflowView().setIsActive(false);
|
||||
}
|
||||
mControllers.taskbarAutohideSuspendController.updateFlag(
|
||||
FLAG_AUTOHIDE_SUSPEND_TASKBAR_OVERFLOW, false);
|
||||
}
|
||||
|
||||
private float getDividerCenterX() {
|
||||
View divider = mTaskbarView.getTaskbarDividerViewContainer();
|
||||
if (divider == null) {
|
||||
return 0.0f;
|
||||
}
|
||||
return divider.getX() + (float) divider.getWidth() / 2;
|
||||
}
|
||||
|
||||
private class TaskbarViewGestureListener extends GestureDetector.SimpleOnGestureListener {
|
||||
@Override
|
||||
public boolean onDown(@NonNull MotionEvent event) {
|
||||
if (event.isFromSource(InputDevice.SOURCE_MOUSE)
|
||||
&& event.getButtonState() == MotionEvent.BUTTON_SECONDARY) {
|
||||
maybeShowPinningView(event);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onSingleTapUp(@NonNull MotionEvent event) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLongPress(@NonNull MotionEvent event) {
|
||||
if (maybeShowPinningView(event)) {
|
||||
mTaskbarView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if the taskbar pinning popup view was shown for {@code event}. */
|
||||
private boolean maybeShowPinningView(@NonNull MotionEvent event) {
|
||||
if (!mActivity.isPinnedTaskbar() || mTaskbarView.isEventOverAnyItem(event)) {
|
||||
return false;
|
||||
}
|
||||
mControllers.taskbarPinningController.showPinningView(mTaskbarView, event.getRawX());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,14 +16,10 @@
|
||||
|
||||
package com.android.launcher3.taskbar
|
||||
|
||||
import android.app.contextualsearch.ContextualSearchManager.ENTRYPOINT_LONG_PRESS_META
|
||||
import android.content.Context
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.logging.StatsLogManager
|
||||
import com.android.launcher3.util.ResourceBasedOverride
|
||||
import com.android.launcher3.util.ResourceBasedOverride.Overrides
|
||||
import com.android.quickstep.TopTaskTracker
|
||||
import com.android.quickstep.util.ContextualSearchInvoker
|
||||
|
||||
/** Creates [TaskbarViewCallbacks] instances. */
|
||||
open class TaskbarViewCallbacksFactory : ResourceBasedOverride {
|
||||
@@ -32,35 +28,7 @@ open class TaskbarViewCallbacksFactory : ResourceBasedOverride {
|
||||
activity: TaskbarActivityContext,
|
||||
controllers: TaskbarControllers,
|
||||
taskbarView: TaskbarView,
|
||||
): TaskbarViewCallbacks {
|
||||
return object : TaskbarViewCallbacks(activity, controllers, taskbarView) {
|
||||
override fun triggerAllAppsButtonLongClick() {
|
||||
super.triggerAllAppsButtonLongClick()
|
||||
|
||||
val contextualSearchInvoked =
|
||||
ContextualSearchInvoker(activity).show(ENTRYPOINT_LONG_PRESS_META)
|
||||
if (contextualSearchInvoked) {
|
||||
val runningPackage =
|
||||
TopTaskTracker.INSTANCE[activity].getCachedTopTask(
|
||||
/* filterOnlyVisibleRecents */ true,
|
||||
activity.display.displayId,
|
||||
)
|
||||
.getPackageName()
|
||||
activity.statsLogManager
|
||||
.logger()
|
||||
.withPackageName(runningPackage)
|
||||
.log(StatsLogManager.LauncherEvent.LAUNCHER_LAUNCH_OMNI_SUCCESSFUL_META)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isAllAppsButtonHapticFeedbackEnabled(context: Context): Boolean {
|
||||
return longPressAllAppsToStartContextualSearch(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open fun longPressAllAppsToStartContextualSearch(context: Context): Boolean =
|
||||
ContextualSearchInvoker(context).runContextualSearchInvocationChecksAndLogFailures()
|
||||
): TaskbarViewCallbacks = TaskbarViewCallbacks(activity, controllers, taskbarView)
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -22,6 +22,7 @@ import android.view.ViewTreeObserver
|
||||
import android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION
|
||||
import android.view.WindowManager
|
||||
import android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
|
||||
import com.android.launcher3.util.DisplayController
|
||||
import com.android.launcher3.views.BaseDragLayer
|
||||
import com.android.systemui.animation.ViewRootSync
|
||||
import java.io.PrintWriter
|
||||
@@ -40,7 +41,7 @@ private const val TEMP_BACKGROUND_WINDOW_TITLE = "VoiceInteractionTaskbarBackgro
|
||||
class VoiceInteractionWindowController(val context: TaskbarActivityContext) :
|
||||
TaskbarControllers.LoggableTaskbarController, TaskbarControllers.BackgroundRendererController {
|
||||
|
||||
private val isSeparateBackgroundEnabled = !context.isTransientTaskbar && !context.isPhoneMode
|
||||
private val isSeparateBackgroundEnabled = !DisplayController.isTransientTaskbar(context)
|
||||
private val taskbarBackgroundRenderer = TaskbarBackgroundRenderer(context)
|
||||
private val nonTouchableInsetsComputer =
|
||||
ViewTreeObserver.OnComputeInternalInsetsListener {
|
||||
@@ -95,7 +96,7 @@ class VoiceInteractionWindowController(val context: TaskbarActivityContext) :
|
||||
separateWindowLayoutParams =
|
||||
context.createDefaultWindowLayoutParams(
|
||||
TYPE_APPLICATION_OVERLAY,
|
||||
TEMP_BACKGROUND_WINDOW_TITLE,
|
||||
TEMP_BACKGROUND_WINDOW_TITLE
|
||||
)
|
||||
separateWindowLayoutParams?.isSystemApplicationOverlay = true
|
||||
}
|
||||
@@ -108,7 +109,7 @@ class VoiceInteractionWindowController(val context: TaskbarActivityContext) :
|
||||
}
|
||||
|
||||
fun setIsVoiceInteractionWindowVisible(visible: Boolean, skipAnim: Boolean) {
|
||||
if (isVoiceInteractionWindowVisible == visible || context.isPhoneMode) {
|
||||
if (isVoiceInteractionWindowVisible == visible) {
|
||||
return
|
||||
}
|
||||
isVoiceInteractionWindowVisible = visible
|
||||
@@ -161,7 +162,7 @@ class VoiceInteractionWindowController(val context: TaskbarActivityContext) :
|
||||
// First add the temporary window, then hide the overlapping taskbar background.
|
||||
context.addWindowView(
|
||||
separateWindowForTaskbarBackground,
|
||||
separateWindowLayoutParams,
|
||||
separateWindowLayoutParams
|
||||
);
|
||||
{ controllers.taskbarDragLayerController.setIsBackgroundDrawnElsewhere(true) }
|
||||
} else {
|
||||
@@ -177,7 +178,7 @@ class VoiceInteractionWindowController(val context: TaskbarActivityContext) :
|
||||
ViewRootSync.synchronizeNextDraw(
|
||||
separateWindowForTaskbarBackground!!,
|
||||
context.dragLayer,
|
||||
onWindowsSynchronized,
|
||||
onWindowsSynchronized
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,13 @@ package com.android.launcher3.taskbar.allapps;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
|
||||
|
||||
import java.util.Optional;
|
||||
@@ -43,6 +46,23 @@ public class TaskbarAllAppsContainerView extends
|
||||
mOnInvalidateHeaderListener = onInvalidateHeaderListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View inflateSearchBar() {
|
||||
if (isSearchSupported()) {
|
||||
return super.inflateSearchBar();
|
||||
}
|
||||
|
||||
// Remove top padding of header, since we do not have any search
|
||||
mHeader.setPadding(mHeader.getPaddingLeft(), 0,
|
||||
mHeader.getPaddingRight(), mHeader.getPaddingBottom());
|
||||
|
||||
TaskbarAllAppsFallbackSearchContainer searchView = new TaskbarAllAppsFallbackSearchContainer(getContext(),
|
||||
null);
|
||||
searchView.setId(R.id.search_container_all_apps);
|
||||
searchView.setVisibility(GONE);
|
||||
return searchView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invalidateHeader() {
|
||||
super.invalidateHeader();
|
||||
@@ -50,6 +70,11 @@ public class TaskbarAllAppsContainerView extends
|
||||
OnInvalidateHeaderListener::onInvalidateHeader);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isSearchSupported() {
|
||||
return FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInAllApps() {
|
||||
// All apps is always open
|
||||
|
||||
@@ -35,15 +35,21 @@ import com.android.launcher3.util.PackageUserKey;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Handles the all apps overlay window initialization, updates, and its data.
|
||||
* <p>
|
||||
* All apps is in an application overlay window instead of taskbar's navigation bar panel window,
|
||||
* because a navigation bar panel is higher than UI components that all apps should be below such as
|
||||
* All apps is in an application overlay window instead of taskbar's navigation
|
||||
* bar panel window,
|
||||
* because a navigation bar panel is higher than UI components that all apps
|
||||
* should be below such as
|
||||
* the notification tray.
|
||||
* <p>
|
||||
* The all apps window is created and destroyed upon opening and closing all apps, respectively.
|
||||
* Application data may be bound while the window does not exist, so this controller will store
|
||||
* The all apps window is created and destroyed upon opening and closing all
|
||||
* apps, respectively.
|
||||
* Application data may be bound while the window does not exist, so this
|
||||
* controller will store
|
||||
* the models for the next all apps session.
|
||||
*/
|
||||
public final class TaskbarAllAppsController {
|
||||
@@ -69,7 +75,8 @@ public final class TaskbarAllAppsController {
|
||||
mControllers = controllers;
|
||||
|
||||
/*
|
||||
* Recreate All Apps if it was open in the previous Taskbar instance (e.g. the configuration
|
||||
* Recreate All Apps if it was open in the previous Taskbar instance (e.g. the
|
||||
* configuration
|
||||
* changed).
|
||||
*/
|
||||
if (allAppsVisible) {
|
||||
@@ -119,12 +126,25 @@ public final class TaskbarAllAppsController {
|
||||
mZeroStateSearchSuggestions = zeroStateSearchSuggestions;
|
||||
}
|
||||
|
||||
/** Toggles visibility of {@link TaskbarAllAppsContainerView} in the overlay window. */
|
||||
/** Updates the current notification dots. */
|
||||
public void updateNotificationDots(Predicate<PackageUserKey> updatedDots) {
|
||||
if (mAppsView != null) {
|
||||
mAppsView.getAppsStore().updateNotificationDots(updatedDots);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles visibility of {@link TaskbarAllAppsContainerView} in the overlay
|
||||
* window.
|
||||
*/
|
||||
public void toggle() {
|
||||
toggle(false);
|
||||
}
|
||||
|
||||
/** Toggles visibility of {@link TaskbarAllAppsContainerView} with the keyboard for search. */
|
||||
/**
|
||||
* Toggles visibility of {@link TaskbarAllAppsContainerView} with the keyboard
|
||||
* for search.
|
||||
*/
|
||||
public void toggleSearch() {
|
||||
toggle(true);
|
||||
}
|
||||
@@ -133,7 +153,6 @@ public final class TaskbarAllAppsController {
|
||||
if (isOpen()) {
|
||||
mSlideInView.close(true);
|
||||
} else {
|
||||
mControllers.taskbarPopupController.maybeCloseMultiInstanceMenu();
|
||||
show(true, showKeyboard);
|
||||
}
|
||||
}
|
||||
@@ -151,6 +170,11 @@ public final class TaskbarAllAppsController {
|
||||
if (mAppsView != null) {
|
||||
return;
|
||||
}
|
||||
// mControllers and getSharedState should never be null here. Do not handle
|
||||
// null-pointer
|
||||
// to catch invalid states.
|
||||
mControllers.getSharedState().allAppsVisible = true;
|
||||
|
||||
mOverlayContext = mControllers.taskbarOverlayController.requestWindow();
|
||||
|
||||
// Initialize search session for All Apps.
|
||||
@@ -164,8 +188,10 @@ public final class TaskbarAllAppsController {
|
||||
|
||||
mSlideInView = (TaskbarAllAppsSlideInView) mOverlayContext.getLayoutInflater().inflate(
|
||||
R.layout.taskbar_all_apps_sheet, mOverlayContext.getDragLayer(), false);
|
||||
// Ensures All Apps gets touch events in case it is not the top floating view. Floating
|
||||
// views above it may not be able to intercept the touch, so All Apps should try to.
|
||||
// Ensures All Apps gets touch events in case it is not the top floating view.
|
||||
// Floating
|
||||
// views above it may not be able to intercept the touch, so All Apps should try
|
||||
// to.
|
||||
mOverlayContext.getDragLayer().addTouchController(mSlideInView);
|
||||
mSlideInView.addOnCloseListener(this::cleanUpOverlay);
|
||||
TaskbarAllAppsViewController viewController = new TaskbarAllAppsViewController(
|
||||
@@ -182,15 +208,18 @@ public final class TaskbarAllAppsController {
|
||||
.findFixedRowByType(PredictionRowView.class)
|
||||
.setPredictedApps(mPredictedApps);
|
||||
// 1 alternative that would be more work:
|
||||
// Create a shared drag layer between taskbar and taskbarAllApps so that when dragging
|
||||
// starts and taskbarAllApps can close, but the drag layer that the view is being dragged in
|
||||
// Create a shared drag layer between taskbar and taskbarAllApps so that when
|
||||
// dragging
|
||||
// starts and taskbarAllApps can close, but the drag layer that the view is
|
||||
// being dragged in
|
||||
// doesn't also close
|
||||
mOverlayContext.getDragController().setDisallowGlobalDrag(mDisallowGlobalDrag);
|
||||
mOverlayContext.getDragController().setDisallowLongClick(mDisallowLongClick);
|
||||
}
|
||||
|
||||
private void cleanUpOverlay() {
|
||||
// Floating search bar is added to the drag layer in ActivityAllAppsContainerView onAttach;
|
||||
// Floating search bar is added to the drag layer in
|
||||
// ActivityAllAppsContainerView onAttach;
|
||||
// removed here as this is a special case that we remove the all apps panel.
|
||||
if (mAppsView != null && mOverlayContext != null
|
||||
&& mAppsView.getSearchUiDelegate().isSearchBarFloating()) {
|
||||
@@ -210,20 +239,17 @@ public final class TaskbarAllAppsController {
|
||||
mAppsView = null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public TaskbarAllAppsContainerView getAppsView() {
|
||||
return mAppsView;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public int getTaskbarAllAppsTopPadding() {
|
||||
// Allow null-pointer since this should only be null if the apps view is not showing.
|
||||
// Allow null-pointer since this should only be null if the apps view is not
|
||||
// showing.
|
||||
return mAppsView.getActiveRecyclerView().getClipBounds().top;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public int getTaskbarAllAppsScroll() {
|
||||
// Allow null-pointer since this should only be null if the apps view is not showing.
|
||||
// Allow null-pointer since this should only be null if the apps view is not
|
||||
// showing.
|
||||
return mAppsView.getActiveRecyclerView().computeVerticalScrollOffset();
|
||||
}
|
||||
|
||||
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (C) 2022 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.android.launcher3.taskbar.allapps;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.ExtendedEditText;
|
||||
import com.android.launcher3.allapps.ActivityAllAppsContainerView;
|
||||
import com.android.launcher3.allapps.SearchUiManager;
|
||||
|
||||
/** Empty search container for Taskbar All Apps used as a fallback if search is not supported. */
|
||||
public class TaskbarAllAppsFallbackSearchContainer extends View implements SearchUiManager {
|
||||
public TaskbarAllAppsFallbackSearchContainer(Context context, AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public TaskbarAllAppsFallbackSearchContainer(
|
||||
Context context, AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeSearch(ActivityAllAppsContainerView<?> containerView) {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resetSearch() {
|
||||
// Do nothing.
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ExtendedEditText getEditText() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+45
-53
@@ -15,9 +15,8 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar.allapps;
|
||||
|
||||
import static com.android.app.animation.Interpolators.DECELERATED_EASE;
|
||||
import static com.android.app.animation.Interpolators.EMPHASIZED;
|
||||
import static com.android.app.animation.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.Flags.enablePredictiveBackGesture;
|
||||
import static com.android.launcher3.touch.AllAppsSwipeController.ALL_APPS_FADE_MANUAL;
|
||||
import static com.android.launcher3.touch.AllAppsSwipeController.SCRIM_FADE_MANUAL;
|
||||
|
||||
@@ -35,16 +34,14 @@ import android.window.OnBackInvokedDispatcher;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import app.lawnchair.theme.color.tokens.ColorTokens;
|
||||
import app.lawnchair.util.LawnchairUtilsKt;
|
||||
import com.android.app.animation.Interpolators;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Flags;
|
||||
import com.android.launcher3.Insettable;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.anim.AnimatorListeners;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsViewController.TaskbarAllAppsCallbacks;
|
||||
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
|
||||
import com.android.launcher3.util.Themes;
|
||||
@@ -54,11 +51,9 @@ import com.android.launcher3.views.AbstractSlideInView;
|
||||
public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverlayContext>
|
||||
implements Insettable, DeviceProfile.OnDeviceProfileChangeListener {
|
||||
private final Handler mHandler;
|
||||
private final int mMaxBlurRadius;
|
||||
|
||||
private TaskbarAllAppsContainerView mAppsView;
|
||||
private float mShiftRange;
|
||||
private int mBlurRadius;
|
||||
private @Nullable Runnable mShowOnFullyAttachedToWindowRunnable;
|
||||
|
||||
// Initialized in init.
|
||||
@@ -72,8 +67,6 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
|
||||
int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
mHandler = new Handler(Looper.myLooper());
|
||||
mMaxBlurRadius = getResources().getDimensionPixelSize(
|
||||
R.dimen.max_depth_blur_radius_enhanced);
|
||||
}
|
||||
|
||||
void init(TaskbarAllAppsCallbacks callbacks) {
|
||||
@@ -109,7 +102,6 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
|
||||
if (!animate) {
|
||||
mAllAppsCallbacks.onAllAppsTransitionEnd(true);
|
||||
setTranslationShift(TRANSLATION_SHIFT_OPENED);
|
||||
mBlurRadius = mMaxBlurRadius;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -129,21 +121,11 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
|
||||
final boolean isOpening = mToTranslationShift == TRANSLATION_SHIFT_OPENED;
|
||||
|
||||
if (mActivityContext.getDeviceProfile().isPhone) {
|
||||
final Interpolator allAppsFadeInterpolator =
|
||||
isOpening ? ALL_APPS_FADE_MANUAL : Interpolators.reverse(ALL_APPS_FADE_MANUAL);
|
||||
final Interpolator allAppsFadeInterpolator = isOpening ? ALL_APPS_FADE_MANUAL
|
||||
: Interpolators.reverse(ALL_APPS_FADE_MANUAL);
|
||||
animation.setViewAlpha(mAppsView, 1 - mToTranslationShift, allAppsFadeInterpolator);
|
||||
}
|
||||
|
||||
if (Flags.allAppsBlur()) {
|
||||
Interpolator blurInterpolator = isOpening ? LINEAR : DECELERATED_EASE;
|
||||
animation.addOnFrameListener(a -> {
|
||||
float blurProgress =
|
||||
isOpening ? a.getAnimatedFraction() : 1 - a.getAnimatedFraction();
|
||||
mBlurRadius =
|
||||
(int) (mMaxBlurRadius * blurInterpolator.getInterpolation(blurProgress));
|
||||
});
|
||||
}
|
||||
|
||||
mAllAppsCallbacks.onAllAppsAnimationPending(animation, isOpening);
|
||||
}
|
||||
|
||||
@@ -200,7 +182,9 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
|
||||
mContent = mAppsView;
|
||||
|
||||
// Setup header protection for search bar, if enabled.
|
||||
mAppsView.setOnInvalidateHeaderListener(this::invalidate);
|
||||
if (FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
|
||||
mAppsView.setOnInvalidateHeaderListener(this::invalidate);
|
||||
}
|
||||
|
||||
DeviceProfile dp = mActivityContext.getDeviceProfile();
|
||||
setShiftRange(dp.allAppsShiftRange);
|
||||
@@ -210,13 +194,16 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
mActivityContext.addOnDeviceProfileChangeListener(this);
|
||||
mAppsView.getAppsRecyclerViewContainer().setOutlineProvider(mViewOutlineProvider);
|
||||
mAppsView.getAppsRecyclerViewContainer().setClipToOutline(true);
|
||||
if (!Utilities.ATLEAST_U) return;
|
||||
OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
|
||||
if (dispatcher != null) {
|
||||
dispatcher.registerOnBackInvokedCallback(
|
||||
OnBackInvokedDispatcher.PRIORITY_DEFAULT, this);
|
||||
if (enablePredictiveBackGesture()) {
|
||||
mAppsView.getAppsRecyclerViewContainer().setOutlineProvider(mViewOutlineProvider);
|
||||
mAppsView.getAppsRecyclerViewContainer().setClipToOutline(true);
|
||||
OnBackInvokedDispatcher dispatcher;
|
||||
if (!Utilities.ATLEAST_U) return;
|
||||
dispatcher = findOnBackInvokedDispatcher();
|
||||
if (dispatcher != null) {
|
||||
dispatcher.registerOnBackInvokedCallback(
|
||||
OnBackInvokedDispatcher.PRIORITY_DEFAULT, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,25 +211,32 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
mActivityContext.removeOnDeviceProfileChangeListener(this);
|
||||
mAppsView.getAppsRecyclerViewContainer().setOutlineProvider(null);
|
||||
mAppsView.getAppsRecyclerViewContainer().setClipToOutline(false);
|
||||
if (!Utilities.ATLEAST_U) return;
|
||||
OnBackInvokedDispatcher dispatcher = findOnBackInvokedDispatcher();
|
||||
if (dispatcher != null) {
|
||||
dispatcher.unregisterOnBackInvokedCallback(this);
|
||||
if (enablePredictiveBackGesture()) {
|
||||
mAppsView.getAppsRecyclerViewContainer().setOutlineProvider(null);
|
||||
mAppsView.getAppsRecyclerViewContainer().setClipToOutline(false);
|
||||
OnBackInvokedDispatcher dispatcher;
|
||||
if (!Utilities.ATLEAST_U) return;
|
||||
dispatcher = findOnBackInvokedDispatcher();
|
||||
if (dispatcher != null && Utilities.ATLEAST_T) {
|
||||
dispatcher.unregisterOnBackInvokedCallback(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void dispatchDraw(Canvas canvas) {
|
||||
// We should call drawOnScrimWithBottomOffset() rather than drawOnScrimWithScale(). Because
|
||||
// for taskbar all apps, the scrim view is a child view of AbstractSlideInView. Thus scaling
|
||||
// down in AbstractSlideInView#onScaleProgressChanged() with SCALE_PROPERTY has already
|
||||
// done the job - there is no need to re-apply scale effect here. But it also means we need
|
||||
// to pass extra bottom offset to background scrim to fill the bottom gap during predictive
|
||||
// We should call drawOnScrimWithBottomOffset() rather than
|
||||
// drawOnScrimWithScale(). Because
|
||||
// for taskbar all apps, the scrim view is a child view of AbstractSlideInView.
|
||||
// Thus scaling
|
||||
// down in AbstractSlideInView#onScaleProgressChanged() with SCALE_PROPERTY has
|
||||
// already
|
||||
// done the job - there is no need to re-apply scale effect here. But it also
|
||||
// means we need
|
||||
// to pass extra bottom offset to background scrim to fill the bottom gap during
|
||||
// predictive
|
||||
// back swipe.
|
||||
mAppsView.drawOnScrimWithBottomOffset(canvas, getBottomOffsetPx());
|
||||
mActivityContext.getOverlayController().setBackgroundBlurRadius(mBlurRadius);
|
||||
super.dispatchDraw(canvas);
|
||||
}
|
||||
|
||||
@@ -254,13 +248,9 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
|
||||
|
||||
@Override
|
||||
protected int getScrimColor(Context context) {
|
||||
if (!mActivityContext.getDeviceProfile().shouldShowAllAppsOnSheet()) {
|
||||
return LawnchairUtilsKt.getAllAppsScrimColor(context);
|
||||
}
|
||||
if (Flags.allAppsBlur()) {
|
||||
return LawnchairUtilsKt.getAllAppsScrimColorOverBlur(context);
|
||||
}
|
||||
return ColorTokens.WidgetsPickerScrim.resolveColor(context);
|
||||
return mActivityContext.getDeviceProfile().isPhone
|
||||
? Themes.getAttrColor(context, R.attr.allAppsScrimColor)
|
||||
: context.getColor(R.color.widgets_picker_scrim);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -282,7 +272,6 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
|
||||
public void onDeviceProfileChanged(DeviceProfile dp) {
|
||||
setShiftRange(dp.allAppsShiftRange);
|
||||
setTranslationShift(TRANSLATION_SHIFT_OPENED);
|
||||
mBlurRadius = mMaxBlurRadius;
|
||||
}
|
||||
|
||||
private void setShiftRange(float shiftRange) {
|
||||
@@ -300,8 +289,10 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
|
||||
}
|
||||
|
||||
/**
|
||||
* In taskbar all apps search mode, we should scale down content inside all apps, rather
|
||||
* than the whole all apps bottom sheet, to indicate we will navigate back within the all apps.
|
||||
* In taskbar all apps search mode, we should scale down content inside all
|
||||
* apps, rather
|
||||
* than the whole all apps bottom sheet, to indicate we will navigate back
|
||||
* within the all apps.
|
||||
*/
|
||||
@Override
|
||||
public boolean shouldAnimateContentViewInBackSwipe() {
|
||||
@@ -318,7 +309,8 @@ public class TaskbarAllAppsSlideInView extends AbstractSlideInView<TaskbarOverla
|
||||
@Override
|
||||
public void onBackInvoked() {
|
||||
if (mAllAppsCallbacks.handleSearchBackInvoked()) {
|
||||
// We need to scale back taskbar all apps if we navigate back within search inside all
|
||||
// We need to scale back taskbar all apps if we navigate back within search
|
||||
// inside all
|
||||
// apps
|
||||
post(this::animateSwipeToDismissProgressToStart);
|
||||
} else {
|
||||
|
||||
+3
-2
@@ -31,6 +31,7 @@ import com.android.launcher3.taskbar.TaskbarSharedState;
|
||||
import com.android.launcher3.taskbar.TaskbarStashController;
|
||||
import com.android.launcher3.taskbar.overlay.TaskbarOverlayContext;
|
||||
import com.android.launcher3.taskbar.overlay.TaskbarOverlayController;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
|
||||
|
||||
import java.util.Optional;
|
||||
@@ -89,7 +90,7 @@ final class TaskbarAllAppsViewController {
|
||||
}
|
||||
|
||||
private void setUpTaskbarStashing() {
|
||||
if (mContext.isTransientTaskbar()) {
|
||||
if (DisplayController.isTransientTaskbar(mContext)) {
|
||||
mTaskbarStashController.updateStateForFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS, true);
|
||||
mTaskbarStashController.applyState();
|
||||
}
|
||||
@@ -102,7 +103,7 @@ final class TaskbarAllAppsViewController {
|
||||
AbstractFloatingView.closeOpenContainer(
|
||||
mContext, AbstractFloatingView.TYPE_ACTION_POPUP);
|
||||
|
||||
if (mContext.isTransientTaskbar()) {
|
||||
if (DisplayController.isTransientTaskbar(mContext)) {
|
||||
mTaskbarStashController.updateStateForFlag(FLAG_STASHED_IN_TASKBAR_ALL_APPS, false);
|
||||
mTaskbarStashController.applyState();
|
||||
}
|
||||
|
||||
+8
-2
@@ -21,6 +21,7 @@ import android.view.View
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.allapps.AllAppsTransitionListener
|
||||
import com.android.launcher3.anim.PendingAnimation
|
||||
import com.android.launcher3.config.FeatureFlags
|
||||
import com.android.launcher3.dragndrop.DragOptions.PreDragCondition
|
||||
import com.android.launcher3.model.data.ItemInfo
|
||||
import com.android.launcher3.util.ResourceBasedOverride
|
||||
@@ -60,11 +61,16 @@ open class TaskbarSearchSessionController : ResourceBasedOverride, AllAppsTransi
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun newInstance(context: Context): TaskbarSearchSessionController =
|
||||
Overrides.getObject(
|
||||
fun newInstance(context: Context): TaskbarSearchSessionController {
|
||||
if (!FeatureFlags.ENABLE_ALL_APPS_SEARCH_IN_TASKBAR.get()) {
|
||||
return TaskbarSearchSessionController()
|
||||
}
|
||||
|
||||
return Overrides.getObject(
|
||||
TaskbarSearchSessionController::class.java,
|
||||
context,
|
||||
R.string.taskbar_search_session_controller_class,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,14 +43,10 @@ class BubbleBarBackground(context: Context, private var backgroundHeight: Float)
|
||||
private val arrowTipRadius: Float
|
||||
private val arrowVisibleHeight: Float
|
||||
|
||||
private val strokeAlpha: Int
|
||||
private val strokeColor: Int
|
||||
private val strokeColorDropTarget: Int
|
||||
private val shadowAlpha: Int
|
||||
private val shadowBlur: Float
|
||||
private val keyShadowDistance: Float
|
||||
private val shadowAlpha: Float
|
||||
private var shadowBlur = 0f
|
||||
private var keyShadowDistance = 0f
|
||||
private var arrowHeightFraction = 1f
|
||||
private var isShowingDropTarget: Boolean = false
|
||||
|
||||
var arrowPositionX: Float = 0f
|
||||
private set
|
||||
@@ -75,27 +71,6 @@ class BubbleBarBackground(context: Context, private var backgroundHeight: Float)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Scale of the background in the x direction. Pivot is at the left edge if [anchorLeft] is
|
||||
* `true` and at the right edge if it is `false`
|
||||
*/
|
||||
var scaleX: Float = 1f
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
field = value
|
||||
invalidateSelf()
|
||||
}
|
||||
}
|
||||
|
||||
/** Scale of the background in the y direction. Pivot is at the bottom edge. */
|
||||
var scaleY: Float = 1f
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
field = value
|
||||
invalidateSelf()
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
val res = context.resources
|
||||
// configure fill paint
|
||||
@@ -103,21 +78,19 @@ class BubbleBarBackground(context: Context, private var backgroundHeight: Float)
|
||||
fillPaint.flags = Paint.ANTI_ALIAS_FLAG
|
||||
fillPaint.style = Paint.Style.FILL
|
||||
// configure stroke paint
|
||||
strokeColor = context.getColor(R.color.taskbar_stroke)
|
||||
strokeColorDropTarget = context.getColor(com.android.internal.R.color.system_primary_fixed)
|
||||
strokePaint.color = strokeColor
|
||||
strokePaint.color = context.getColor(R.color.taskbar_stroke)
|
||||
strokePaint.flags = Paint.ANTI_ALIAS_FLAG
|
||||
strokePaint.style = Paint.Style.STROKE
|
||||
strokePaint.strokeWidth = res.getDimension(R.dimen.transient_taskbar_stroke_width)
|
||||
// apply theme alpha attributes
|
||||
if (Utilities.isDarkTheme(context)) {
|
||||
strokeAlpha = DARK_THEME_STROKE_ALPHA
|
||||
strokePaint.alpha = DARK_THEME_STROKE_ALPHA
|
||||
shadowAlpha = DARK_THEME_SHADOW_ALPHA
|
||||
} else {
|
||||
strokeAlpha = LIGHT_THEME_STROKE_ALPHA
|
||||
strokePaint.alpha = LIGHT_THEME_STROKE_ALPHA
|
||||
shadowAlpha = LIGHT_THEME_SHADOW_ALPHA
|
||||
}
|
||||
strokePaint.alpha = strokeAlpha
|
||||
|
||||
shadowBlur = res.getDimension(R.dimen.transient_taskbar_shadow_blur)
|
||||
keyShadowDistance = res.getDimension(R.dimen.transient_taskbar_key_shadow_distance)
|
||||
arrowWidth = res.getDimension(R.dimen.bubblebar_pointer_width)
|
||||
@@ -138,28 +111,25 @@ class BubbleBarBackground(context: Context, private var backgroundHeight: Float)
|
||||
override fun draw(canvas: Canvas) {
|
||||
canvas.save()
|
||||
|
||||
// TODO (b/277359345): Should animate the alpha similar to taskbar (see TaskbarDragLayer)
|
||||
// Draw shadows.
|
||||
val newShadowAlpha =
|
||||
mapToRange(fillPaint.alpha, 0, 255, 0, shadowAlpha, Interpolators.LINEAR)
|
||||
mapToRange(fillPaint.alpha.toFloat(), 0f, 255f, 0f, shadowAlpha, Interpolators.LINEAR)
|
||||
fillPaint.setShadowLayer(
|
||||
shadowBlur,
|
||||
0f,
|
||||
keyShadowDistance,
|
||||
setColorAlphaBound(Color.BLACK, newShadowAlpha),
|
||||
setColorAlphaBound(Color.BLACK, Math.round(newShadowAlpha))
|
||||
)
|
||||
// Create background path
|
||||
val backgroundPath = Path()
|
||||
val scaledBackgroundHeight = backgroundHeight * scaleY
|
||||
val scaledWidth = width * scaleX
|
||||
val topOffset = scaledBackgroundHeight - bounds.height().toFloat()
|
||||
val topOffset = backgroundHeight - bounds.height().toFloat()
|
||||
val radius = backgroundHeight / 2f
|
||||
val left = bounds.left + (if (anchorLeft) 0f else bounds.width().toFloat() - width)
|
||||
val right = bounds.left + (if (anchorLeft) width else bounds.width().toFloat())
|
||||
val top = bounds.top - topOffset + arrowVisibleHeight
|
||||
|
||||
val left = bounds.left + (if (anchorLeft) 0f else bounds.width().toFloat() - scaledWidth)
|
||||
val right = bounds.left + (if (anchorLeft) scaledWidth else bounds.width().toFloat())
|
||||
// Calculate top with scaled heights for background and arrow to align with stash handle
|
||||
val top = bounds.bottom - scaledBackgroundHeight + getScaledArrowVisibleHeight()
|
||||
val bottom = bounds.bottom.toFloat()
|
||||
|
||||
val bottom = bounds.top + bounds.height().toFloat()
|
||||
backgroundPath.addRoundRect(left, top, right, bottom, radius, radius, Path.Direction.CW)
|
||||
addArrowPathIfNeeded(backgroundPath, topOffset)
|
||||
|
||||
@@ -172,20 +142,19 @@ class BubbleBarBackground(context: Context, private var backgroundHeight: Float)
|
||||
private fun addArrowPathIfNeeded(sourcePath: Path, topOffset: Float) {
|
||||
if (!showingArrow || arrowHeightFraction <= 0) return
|
||||
val arrowPath = Path()
|
||||
val scaledHeight = getScaledArrowHeight()
|
||||
RoundedArrowDrawable.addDownPointingRoundedTriangleToPath(
|
||||
arrowWidth,
|
||||
scaledHeight,
|
||||
arrowHeight,
|
||||
arrowTipRadius,
|
||||
arrowPath,
|
||||
arrowPath
|
||||
)
|
||||
// flip it horizontally
|
||||
val pathTransform = Matrix()
|
||||
pathTransform.setRotate(180f, arrowWidth * 0.5f, scaledHeight * 0.5f)
|
||||
pathTransform.setRotate(180f, arrowWidth * 0.5f, arrowHeight * 0.5f)
|
||||
arrowPath.transform(pathTransform)
|
||||
// shift to arrow position
|
||||
val arrowStart = bounds.left + arrowPositionX - (arrowWidth / 2f)
|
||||
val arrowTop = (1 - arrowHeightFraction) * getScaledArrowVisibleHeight() - topOffset
|
||||
val arrowTop = (1 - arrowHeightFraction) * arrowVisibleHeight - topOffset
|
||||
arrowPath.offset(arrowStart, arrowTop)
|
||||
// union with rectangle
|
||||
sourcePath.op(arrowPath, Path.Op.UNION)
|
||||
@@ -201,7 +170,6 @@ class BubbleBarBackground(context: Context, private var backgroundHeight: Float)
|
||||
|
||||
override fun setAlpha(alpha: Int) {
|
||||
fillPaint.alpha = alpha
|
||||
strokePaint.alpha = mapToRange(alpha, 0, 255, 0, strokeAlpha, Interpolators.LINEAR)
|
||||
invalidateSelf()
|
||||
}
|
||||
|
||||
@@ -215,7 +183,6 @@ class BubbleBarBackground(context: Context, private var backgroundHeight: Float)
|
||||
|
||||
fun setBackgroundHeight(newHeight: Float) {
|
||||
backgroundHeight = newHeight
|
||||
invalidateSelf()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -232,34 +199,10 @@ class BubbleBarBackground(context: Context, private var backgroundHeight: Float)
|
||||
invalidateSelf()
|
||||
}
|
||||
|
||||
private fun getScaledArrowHeight(): Float {
|
||||
return arrowHeight * scaleY
|
||||
}
|
||||
|
||||
private fun getScaledArrowVisibleHeight(): Float {
|
||||
return max(0f, getScaledArrowHeight() - (arrowHeight - arrowVisibleHeight))
|
||||
}
|
||||
|
||||
/** Set whether the background should show the drop target */
|
||||
fun showDropTarget(isDropTarget: Boolean) {
|
||||
if (isShowingDropTarget == isDropTarget) {
|
||||
return
|
||||
}
|
||||
isShowingDropTarget = isDropTarget
|
||||
val strokeColor = if (isDropTarget) strokeColorDropTarget else strokeColor
|
||||
val alpha = if (isDropTarget) DRAG_STROKE_ALPHA else strokeAlpha
|
||||
strokePaint.color = strokeColor
|
||||
strokePaint.alpha = alpha
|
||||
invalidateSelf()
|
||||
}
|
||||
|
||||
fun isShowingDropTarget() = isShowingDropTarget
|
||||
|
||||
companion object {
|
||||
private const val DARK_THEME_STROKE_ALPHA = 51
|
||||
private const val LIGHT_THEME_STROKE_ALPHA = 41
|
||||
private const val DRAG_STROKE_ALPHA = 255
|
||||
private const val DARK_THEME_SHADOW_ALPHA = 51
|
||||
private const val LIGHT_THEME_SHADOW_ALPHA = 25
|
||||
private const val DARK_THEME_SHADOW_ALPHA = 51f
|
||||
private const val LIGHT_THEME_SHADOW_ALPHA = 25f
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,17 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar.bubbles;
|
||||
|
||||
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_PERSONS_DATA;
|
||||
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED;
|
||||
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
|
||||
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER;
|
||||
import static android.os.Process.THREAD_PRIORITY_BACKGROUND;
|
||||
|
||||
import static com.android.launcher3.icons.FastBitmapDrawable.WHITE_SCRIM_ALPHA;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
|
||||
import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING;
|
||||
@@ -29,42 +35,65 @@ import static java.util.stream.Collectors.toList;
|
||||
|
||||
import android.annotation.BinderThread;
|
||||
import android.annotation.Nullable;
|
||||
import android.app.Notification;
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.LauncherApps;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.drawable.AdaptiveIconDrawable;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.InsetDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.os.Process;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Log;
|
||||
import android.util.PathParser;
|
||||
import android.view.LayoutInflater;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.content.res.AppCompatResources;
|
||||
|
||||
import com.android.launcher3.taskbar.TaskbarSharedState;
|
||||
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
|
||||
import com.android.internal.graphics.ColorUtils;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.icons.BitmapInfo;
|
||||
import com.android.launcher3.icons.BubbleIconFactory;
|
||||
import com.android.launcher3.shortcuts.ShortcutRequest;
|
||||
import com.android.launcher3.util.Executors.SimpleThreadFactory;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags;
|
||||
import com.android.wm.shell.Flags;
|
||||
import com.android.wm.shell.bubbles.IBubblesListener;
|
||||
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
|
||||
import com.android.wm.shell.shared.bubbles.BubbleBarUpdate;
|
||||
import com.android.wm.shell.shared.bubbles.BubbleInfo;
|
||||
import com.android.wm.shell.shared.bubbles.RemovedBubble;
|
||||
import com.android.wm.shell.common.bubbles.BubbleBarLocation;
|
||||
import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
|
||||
import com.android.wm.shell.common.bubbles.BubbleInfo;
|
||||
import com.android.wm.shell.common.bubbles.RemovedBubble;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* This registers a listener with SysUIProxy to get information about changes to the bubble
|
||||
* stack state from WMShell (SysUI). The controller is also responsible for loading the necessary
|
||||
* This registers a listener with SysUIProxy to get information about changes to
|
||||
* the bubble
|
||||
* stack state from WMShell (SysUI). The controller is also responsible for
|
||||
* loading the necessary
|
||||
* information to render each of the bubbles & dispatches changes to
|
||||
* {@link BubbleBarViewController} which will then update {@link BubbleBarView} as needed.
|
||||
* {@link BubbleBarViewController} which will then update {@link BubbleBarView}
|
||||
* as needed.
|
||||
*
|
||||
* <p>For details around the behavior of the bubble bar, see {@link BubbleBarView}.
|
||||
* <p>
|
||||
* For details around the behavior of the bubble bar, see {@link BubbleBarView}.
|
||||
*/
|
||||
public class BubbleBarController extends IBubblesListener.Stub {
|
||||
|
||||
@@ -72,7 +101,8 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
private static final boolean DEBUG = false;
|
||||
|
||||
/**
|
||||
* Determines whether bubbles can be shown in the bubble bar. This value updates when the
|
||||
* Determines whether bubbles can be shown in the bubble bar. This value updates
|
||||
* when the
|
||||
* taskbar is recreated.
|
||||
*
|
||||
* @see #onTaskbarRecreated()
|
||||
@@ -85,7 +115,10 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
return sBubbleBarEnabled;
|
||||
}
|
||||
|
||||
/** Re-reads the value of the flag from SystemProperties when taskbar is recreated. */
|
||||
/**
|
||||
* Re-reads the value of the flag from SystemProperties when taskbar is
|
||||
* recreated.
|
||||
*/
|
||||
public static void onTaskbarRecreated() {
|
||||
sBubbleBarEnabled = Flags.enableBubbleBar()
|
||||
|| SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false);
|
||||
@@ -94,9 +127,10 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
private static final long MASK_HIDE_BUBBLE_BAR = SYSUI_STATE_BOUNCER_SHOWING
|
||||
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
|
||||
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED
|
||||
| SYSUI_STATE_IME_VISIBLE
|
||||
| SYSUI_STATE_IME_SHOWING
|
||||
| SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED
|
||||
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
|
||||
| SYSUI_STATE_QUICK_SETTINGS_EXPANDED
|
||||
| SYSUI_STATE_IME_SWITCHER_SHOWING;
|
||||
|
||||
private static final long MASK_HIDE_HANDLE_VIEW = SYSUI_STATE_BOUNCER_SHOWING
|
||||
| SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING
|
||||
@@ -109,29 +143,28 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
private final Context mContext;
|
||||
private final BubbleBarView mBarView;
|
||||
private final ArrayMap<String, BubbleBarBubble> mBubbles = new ArrayMap<>();
|
||||
private final ArrayMap<String, BubbleBarBubble> mSuppressedBubbles = new ArrayMap<>();
|
||||
|
||||
private static final Executor BUBBLE_STATE_EXECUTOR = Executors.newSingleThreadExecutor(
|
||||
new SimpleThreadFactory("BubbleStateUpdates-", THREAD_PRIORITY_BACKGROUND));
|
||||
private final LauncherApps mLauncherApps;
|
||||
private final BubbleIconFactory mIconFactory;
|
||||
private final SystemUiProxy mSystemUiProxy;
|
||||
|
||||
private BubbleBarItem mSelectedBubble;
|
||||
private BubbleBarOverflow mOverflowBubble;
|
||||
|
||||
private TaskbarSharedState mSharedState;
|
||||
private ImeVisibilityChecker mImeVisibilityChecker;
|
||||
private BubbleBarViewController mBubbleBarViewController;
|
||||
private BubbleStashController mBubbleStashController;
|
||||
private Optional<BubbleStashedHandleViewController> mBubbleStashedHandleViewController;
|
||||
private BubbleStashedHandleViewController mBubbleStashedHandleViewController;
|
||||
private BubblePinController mBubblePinController;
|
||||
private BubbleCreator mBubbleCreator;
|
||||
private BubbleBarLocationListener mBubbleBarLocationListener;
|
||||
|
||||
// Cache last sent top coordinate to avoid sending duplicate updates to shell
|
||||
private int mLastSentBubbleBarTop;
|
||||
|
||||
private boolean mIsImeVisible = false;
|
||||
|
||||
/**
|
||||
* Similar to {@link BubbleBarUpdate} but rather than {@link BubbleInfo}s it uses
|
||||
* Similar to {@link BubbleBarUpdate} but rather than {@link BubbleInfo}s it
|
||||
* uses
|
||||
* {@link BubbleBarBubble}s so that it can be used to update the views.
|
||||
*/
|
||||
private static class BubbleBarViewUpdate {
|
||||
@@ -146,8 +179,6 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
List<RemovedBubble> removedBubbles;
|
||||
List<String> bubbleKeysInOrder;
|
||||
Point expandedViewDropTargetSize;
|
||||
boolean showOverflow;
|
||||
boolean showOverflowChanged;
|
||||
|
||||
// These need to be loaded in the background
|
||||
BubbleBarBubble addedBubble;
|
||||
@@ -166,8 +197,6 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
removedBubbles = update.removedBubbles;
|
||||
bubbleKeysInOrder = update.bubbleKeysInOrder;
|
||||
expandedViewDropTargetSize = update.expandedViewDropTargetSize;
|
||||
showOverflow = update.showOverflow;
|
||||
showOverflowChanged = update.showOverflowChanged;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -176,74 +205,81 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
mBarView = bubbleView; // Need the view for inflating bubble views.
|
||||
|
||||
mSystemUiProxy = SystemUiProxy.INSTANCE.get(context);
|
||||
|
||||
if (sBubbleBarEnabled) {
|
||||
mSystemUiProxy.setBubblesListener(this);
|
||||
}
|
||||
mLauncherApps = context.getSystemService(LauncherApps.class);
|
||||
mIconFactory = new BubbleIconFactory(context,
|
||||
context.getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size),
|
||||
context.getResources().getDimensionPixelSize(R.dimen.bubblebar_badge_size),
|
||||
context.getResources().getColor(R.color.important_conversation),
|
||||
context.getResources().getDimensionPixelSize(
|
||||
com.android.internal.R.dimen.importance_ring_stroke_width));
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
mSystemUiProxy.setBubblesListener(null);
|
||||
// Saves bubble bar state
|
||||
BubbleInfo[] bubbleInfoItems = new BubbleInfo[mBubbles.size()];
|
||||
mBubbles.values().forEach(bubbleBarBubble -> {
|
||||
int index = mBubbleBarViewController.bubbleViewIndex(bubbleBarBubble.getView());
|
||||
if (index < 0 || index >= bubbleInfoItems.length) {
|
||||
Log.e(TAG, "Found improper index: " + index + " for " + bubbleBarBubble);
|
||||
} else {
|
||||
bubbleInfoItems[index] = bubbleBarBubble.getInfo();
|
||||
}
|
||||
});
|
||||
mSharedState.bubbleInfoItems = Arrays.asList(bubbleInfoItems);
|
||||
mSharedState.suppressedBubbleInfoItems = new ArrayList<>(mSuppressedBubbles.size());
|
||||
for (int i = 0; i < mSuppressedBubbles.size(); i++) {
|
||||
mSharedState.suppressedBubbleInfoItems.add(mSuppressedBubbles.valueAt(i).getInfo());
|
||||
}
|
||||
}
|
||||
|
||||
/** Initializes controllers. */
|
||||
public void init(BubbleControllers bubbleControllers,
|
||||
BubbleBarLocationListener bubbleBarLocationListener,
|
||||
TaskbarSharedState sharedState) {
|
||||
mSharedState = sharedState;
|
||||
ImeVisibilityChecker imeVisibilityChecker) {
|
||||
mImeVisibilityChecker = imeVisibilityChecker;
|
||||
mBubbleBarViewController = bubbleControllers.bubbleBarViewController;
|
||||
mBubbleStashController = bubbleControllers.bubbleStashController;
|
||||
mBubbleStashedHandleViewController = bubbleControllers.bubbleStashedHandleViewController;
|
||||
mBubblePinController = bubbleControllers.bubblePinController;
|
||||
mBubbleCreator = bubbleControllers.bubbleCreator;
|
||||
mBubbleBarLocationListener = bubbleBarLocationListener;
|
||||
|
||||
bubbleControllers.runAfterInit(() -> {
|
||||
restoreSavedState(sharedState);
|
||||
mBubbleBarViewController.setHiddenForBubbles(
|
||||
!sBubbleBarEnabled || mBubbles.isEmpty());
|
||||
mBubbleStashedHandleViewController.ifPresent(
|
||||
controller -> controller.setHiddenForBubbles(
|
||||
!sBubbleBarEnabled || mBubbles.isEmpty()));
|
||||
mBubbleStashedHandleViewController.setHiddenForBubbles(
|
||||
!sBubbleBarEnabled || mBubbles.isEmpty());
|
||||
mBubbleBarViewController.setUpdateSelectedBubbleAfterCollapse(
|
||||
key -> setSelectedBubbleInternal(mBubbles.get(key)));
|
||||
mBubbleBarViewController.setBoundsChangeListener(this::onBubbleBarBoundsChanged);
|
||||
mBubbleBarLocationListener.onBubbleBarLocationUpdated(
|
||||
mBubbleBarViewController.getBubbleBarLocation());
|
||||
if (sBubbleBarEnabled) {
|
||||
mSystemUiProxy.setBubblesListener(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the bubble bar, handle bar, and stash controllers based on sysui state flags.
|
||||
* Creates and adds the overflow bubble to the bubble bar if it hasn't been
|
||||
* created yet.
|
||||
*
|
||||
* <p>
|
||||
* This should be called on the {@link #BUBBLE_STATE_EXECUTOR} executor to avoid
|
||||
* inflating
|
||||
* the overflow multiple times.
|
||||
*/
|
||||
private void createAndAddOverflowIfNeeded() {
|
||||
if (mOverflowBubble == null) {
|
||||
BubbleBarOverflow overflow = createOverflow(mContext);
|
||||
MAIN_EXECUTOR.execute(() -> {
|
||||
// we're on the main executor now, so check that the overflow hasn't been
|
||||
// created
|
||||
// again to avoid races.
|
||||
if (mOverflowBubble == null) {
|
||||
mBubbleBarViewController.addBubble(
|
||||
overflow, /* isExpanding= */ false, /* suppressAnimation= */ true);
|
||||
mOverflowBubble = overflow;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the bubble bar, handle bar, and stash controllers based on sysui
|
||||
* state flags.
|
||||
*/
|
||||
public void updateStateForSysuiFlags(@SystemUiStateFlags long flags) {
|
||||
boolean hideBubbleBar = (flags & MASK_HIDE_BUBBLE_BAR) != 0;
|
||||
mBubbleBarViewController.setHiddenForSysui(hideBubbleBar);
|
||||
|
||||
boolean hideHandleView = (flags & MASK_HIDE_HANDLE_VIEW) != 0;
|
||||
mBubbleStashedHandleViewController.ifPresent(
|
||||
controller -> controller.setHiddenForSysui(hideHandleView));
|
||||
mBubbleStashedHandleViewController.setHiddenForSysui(hideHandleView);
|
||||
|
||||
boolean sysuiLocked = (flags & MASK_SYSUI_LOCKED) != 0;
|
||||
mBubbleStashController.setSysuiLocked(sysuiLocked);
|
||||
mIsImeVisible = (flags & SYSUI_STATE_IME_VISIBLE) != 0;
|
||||
if (mIsImeVisible) {
|
||||
mBubbleBarViewController.onImeVisible();
|
||||
}
|
||||
mBubbleStashController.onSysuiLockedStateChange(sysuiLocked);
|
||||
}
|
||||
|
||||
//
|
||||
@@ -261,24 +297,20 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
|| !update.currentBubbleList.isEmpty()) {
|
||||
// We have bubbles to load
|
||||
BUBBLE_STATE_EXECUTOR.execute(() -> {
|
||||
createAndAddOverflowIfNeeded();
|
||||
if (update.addedBubble != null) {
|
||||
viewUpdate.addedBubble = mBubbleCreator.populateBubble(mContext,
|
||||
update.addedBubble,
|
||||
mBarView,
|
||||
viewUpdate.addedBubble = populateBubble(mContext, update.addedBubble, mBarView,
|
||||
null /* existingBubble */);
|
||||
}
|
||||
if (update.updatedBubble != null) {
|
||||
BubbleBarBubble existingBubble = mBubbles.get(update.updatedBubble.getKey());
|
||||
viewUpdate.updatedBubble =
|
||||
mBubbleCreator.populateBubble(mContext, update.updatedBubble,
|
||||
mBarView,
|
||||
existingBubble);
|
||||
viewUpdate.updatedBubble = populateBubble(mContext, update.updatedBubble, mBarView,
|
||||
existingBubble);
|
||||
}
|
||||
if (update.currentBubbleList != null && !update.currentBubbleList.isEmpty()) {
|
||||
List<BubbleBarBubble> currentBubbles = new ArrayList<>();
|
||||
for (int i = 0; i < update.currentBubbleList.size(); i++) {
|
||||
BubbleBarBubble b = mBubbleCreator.populateBubble(mContext,
|
||||
update.currentBubbleList.get(i), mBarView,
|
||||
BubbleBarBubble b = populateBubble(mContext, update.currentBubbleList.get(i), mBarView,
|
||||
null /* existingBubble */);
|
||||
currentBubbles.add(b);
|
||||
}
|
||||
@@ -293,61 +325,92 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
private void restoreSavedState(TaskbarSharedState sharedState) {
|
||||
if (sharedState.bubbleBarLocation != null) {
|
||||
updateBubbleBarLocationInternal(sharedState.bubbleBarLocation);
|
||||
}
|
||||
restoreSavedBubbles(sharedState.bubbleInfoItems);
|
||||
restoreSuppressed(sharedState.suppressedBubbleInfoItems);
|
||||
}
|
||||
|
||||
private void restoreSavedBubbles(List<BubbleInfo> bubbleInfos) {
|
||||
if (bubbleInfos == null || bubbleInfos.isEmpty()) return;
|
||||
// Iterate in reverse because new bubbles are added in front and the list is in order.
|
||||
for (int i = bubbleInfos.size() - 1; i >= 0; i--) {
|
||||
BubbleBarBubble bubble = mBubbleCreator.populateBubble(mContext,
|
||||
bubbleInfos.get(i), mBarView, /* existingBubble = */ null);
|
||||
if (bubble == null) {
|
||||
Log.e(TAG, "Could not instantiate BubbleBarBubble for " + bubbleInfos.get(i));
|
||||
continue;
|
||||
}
|
||||
addBubbleInternally(bubble, /* isExpanding= */ false, /* suppressAnimation= */ true);
|
||||
}
|
||||
}
|
||||
|
||||
private void restoreSuppressed(List<BubbleInfo> bubbleInfos) {
|
||||
if (bubbleInfos == null || bubbleInfos.isEmpty()) return;
|
||||
for (BubbleInfo bubbleInfo : bubbleInfos.reversed()) {
|
||||
BubbleBarBubble bb = mBubbleCreator.populateBubble(mContext, bubbleInfo,
|
||||
mBarView, /* existingBubble= */
|
||||
null);
|
||||
if (bb != null) {
|
||||
mSuppressedBubbles.put(bb.getKey(), bb);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applyViewChanges(BubbleBarViewUpdate update) {
|
||||
final boolean isCollapsed = (update.expandedChanged && !update.expanded)
|
||||
|| (!update.expandedChanged && !mBubbleBarViewController.isExpanded());
|
||||
final boolean isExpanding = update.expandedChanged && update.expanded;
|
||||
// don't animate bubbles if this is the initial state because we may be unfolding or
|
||||
// enabling gesture nav. also suppress animation if the bubble bar is hidden for sysui e.g.
|
||||
// don't animate bubbles if this is the initial state because we may be
|
||||
// unfolding or
|
||||
// enabling gesture nav. also suppress animation if the bubble bar is hidden for
|
||||
// sysui e.g.
|
||||
// the shade is open, or we're locked.
|
||||
final boolean suppressAnimation =
|
||||
update.initialState || mBubbleBarViewController.isHiddenForSysui() || mIsImeVisible;
|
||||
final boolean suppressAnimation = update.initialState || mBubbleBarViewController.isHiddenForSysui()
|
||||
|| mImeVisibilityChecker.isImeVisible();
|
||||
|
||||
if (update.initialState && mSharedState.hasSavedBubbles()) {
|
||||
// clear restored state
|
||||
mBubbleBarViewController.removeAllBubbles();
|
||||
mBubbles.clear();
|
||||
mBubbleBarViewController.showOverflow(update.showOverflow);
|
||||
BubbleBarBubble bubbleToSelect = null;
|
||||
if (!update.removedBubbles.isEmpty()) {
|
||||
for (int i = 0; i < update.removedBubbles.size(); i++) {
|
||||
RemovedBubble removedBubble = update.removedBubbles.get(i);
|
||||
BubbleBarBubble bubble = mBubbles.remove(removedBubble.getKey());
|
||||
if (bubble != null) {
|
||||
mBubbleBarViewController.removeBubble(bubble);
|
||||
} else {
|
||||
Log.w(TAG, "trying to remove bubble that doesn't exist: "
|
||||
+ removedBubble.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (update.addedBubble != null) {
|
||||
mBubbles.put(update.addedBubble.getKey(), update.addedBubble);
|
||||
mBubbleBarViewController.addBubble(update.addedBubble, isExpanding, suppressAnimation);
|
||||
if (isCollapsed) {
|
||||
// If we're collapsed, the most recently added bubble will be selected.
|
||||
bubbleToSelect = update.addedBubble;
|
||||
}
|
||||
|
||||
}
|
||||
if (update.currentBubbles != null && !update.currentBubbles.isEmpty()) {
|
||||
// Iterate in reverse because new bubbles are added in front and the list is in
|
||||
// order.
|
||||
for (int i = update.currentBubbles.size() - 1; i >= 0; i--) {
|
||||
BubbleBarBubble bubble = update.currentBubbles.get(i);
|
||||
if (bubble != null) {
|
||||
mBubbles.put(bubble.getKey(), bubble);
|
||||
mBubbleBarViewController.addBubble(bubble, isExpanding, suppressAnimation);
|
||||
if (isCollapsed) {
|
||||
// If we're collapsed, the most recently added bubble will be selected.
|
||||
bubbleToSelect = bubble;
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "trying to add bubble but null after loading! "
|
||||
+ update.addedBubble.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Adds and removals have happened, update visibility before any other visual
|
||||
// changes.
|
||||
mBubbleBarViewController.setHiddenForBubbles(mBubbles.isEmpty());
|
||||
mBubbleStashedHandleViewController.setHiddenForBubbles(mBubbles.isEmpty());
|
||||
|
||||
if (mBubbles.isEmpty()) {
|
||||
// all bubbles were removed. clear the selected bubble
|
||||
mSelectedBubble = null;
|
||||
}
|
||||
|
||||
if (update.updatedBubble != null) {
|
||||
// Updates mean the dot state may have changed; any other changes were updated
|
||||
// in
|
||||
// the populateBubble step.
|
||||
BubbleBarBubble bb = mBubbles.get(update.updatedBubble.getKey());
|
||||
// If we're not stashed, we're visible so animate
|
||||
bb.getView().updateDotVisibility(!mBubbleStashController.isStashed() /* animate */);
|
||||
mBubbleBarViewController.animateBubbleNotification(bb, /* isExpanding= */ false);
|
||||
}
|
||||
if (update.bubbleKeysInOrder != null && !update.bubbleKeysInOrder.isEmpty()) {
|
||||
// Create the new list
|
||||
List<BubbleBarBubble> newOrder = update.bubbleKeysInOrder.stream()
|
||||
.map(mBubbles::get).filter(Objects::nonNull).collect(toList());
|
||||
if (!newOrder.isEmpty()) {
|
||||
mBubbleBarViewController.reorderBubbles(newOrder);
|
||||
}
|
||||
}
|
||||
if (update.suppressedBubbleKey != null) {
|
||||
// TODO: (b/273316505) handle suppression
|
||||
}
|
||||
if (update.unsuppressedBubbleKey != null) {
|
||||
// TODO: (b/273316505) handle suppression
|
||||
}
|
||||
BubbleBarBubble bubbleToSelect = null;
|
||||
if (update.selectedBubbleKey != null) {
|
||||
if (mSelectedBubble == null
|
||||
|| !update.selectedBubbleKey.equals(mSelectedBubble.getKey())) {
|
||||
@@ -360,139 +423,6 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Flags.enableOptionalBubbleOverflow()
|
||||
&& update.showOverflowChanged && !update.showOverflow && update.addedBubble != null
|
||||
&& update.removedBubbles.isEmpty()
|
||||
&& !mBubbles.isEmpty()) {
|
||||
// A bubble was added from the overflow (& now it's empty / not showing)
|
||||
mBubbleBarViewController.removeOverflowAndAddBubble(update.addedBubble, bubbleToSelect);
|
||||
} else if (update.addedBubble != null && update.removedBubbles.size() == 1) {
|
||||
// we're adding and removing a bubble at the same time. handle this as a single update.
|
||||
RemovedBubble removedBubble = update.removedBubbles.get(0);
|
||||
BubbleBarBubble bubbleToRemove = mBubbles.remove(removedBubble.getKey());
|
||||
boolean showOverflow = update.showOverflowChanged && update.showOverflow;
|
||||
if (bubbleToRemove != null) {
|
||||
mBubbleBarViewController.addBubbleAndRemoveBubble(update.addedBubble,
|
||||
bubbleToRemove, bubbleToSelect, isExpanding, suppressAnimation,
|
||||
showOverflow);
|
||||
} else {
|
||||
mBubbleBarViewController.addBubble(update.addedBubble, isExpanding,
|
||||
suppressAnimation, bubbleToSelect);
|
||||
Log.w(TAG, "trying to remove bubble that doesn't exist: " + removedBubble.getKey());
|
||||
}
|
||||
} else {
|
||||
boolean overflowNeedsToBeAdded = Flags.enableOptionalBubbleOverflow()
|
||||
&& update.showOverflowChanged && update.showOverflow;
|
||||
if (!update.removedBubbles.isEmpty()) {
|
||||
for (int i = 0; i < update.removedBubbles.size(); i++) {
|
||||
RemovedBubble removedBubble = update.removedBubbles.get(i);
|
||||
BubbleBarBubble bubble = mBubbles.remove(removedBubble.getKey());
|
||||
if (bubble != null && overflowNeedsToBeAdded) {
|
||||
// First removal, show the overflow
|
||||
overflowNeedsToBeAdded = false;
|
||||
mBubbleBarViewController.addOverflowAndRemoveBubble(bubble, bubbleToSelect);
|
||||
} else if (bubble != null) {
|
||||
mBubbleBarViewController.removeBubble(bubble);
|
||||
} else {
|
||||
Log.w(TAG, "trying to remove bubble that doesn't exist: "
|
||||
+ removedBubble.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (update.addedBubble != null) {
|
||||
mBubbleBarViewController.addBubble(update.addedBubble, isExpanding,
|
||||
suppressAnimation, bubbleToSelect);
|
||||
}
|
||||
if (Flags.enableOptionalBubbleOverflow()
|
||||
&& update.showOverflowChanged
|
||||
&& update.showOverflow != mBubbleBarViewController.isOverflowAdded()) {
|
||||
mBubbleBarViewController.showOverflow(update.showOverflow);
|
||||
}
|
||||
}
|
||||
|
||||
// if a bubble was updated upstream, but removed before the update was received, add it back
|
||||
if (update.updatedBubble != null && !mBubbles.containsKey(update.updatedBubble.getKey())) {
|
||||
addBubbleInternally(update.updatedBubble, isExpanding, suppressAnimation);
|
||||
}
|
||||
|
||||
if (update.addedBubble != null && isCollapsed && bubbleToSelect == null) {
|
||||
// If we're collapsed, the most recently added bubble will be selected.
|
||||
bubbleToSelect = update.addedBubble;
|
||||
}
|
||||
|
||||
if (update.currentBubbles != null && !update.currentBubbles.isEmpty()) {
|
||||
// Iterate in reverse because new bubbles are added in front and the list is in order.
|
||||
for (int i = update.currentBubbles.size() - 1; i >= 0; i--) {
|
||||
BubbleBarBubble bubble = update.currentBubbles.get(i);
|
||||
if (bubble != null) {
|
||||
addBubbleInternally(bubble, isExpanding, suppressAnimation);
|
||||
if (isCollapsed && bubbleToSelect == null) {
|
||||
// If we're collapsed, the most recently added bubble will be selected.
|
||||
bubbleToSelect = bubble;
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "trying to add bubble but null after loading! "
|
||||
+ update.addedBubble.getKey());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Flags.enableOptionalBubbleOverflow() && update.initialState && update.showOverflow) {
|
||||
mBubbleBarViewController.showOverflow(true);
|
||||
}
|
||||
|
||||
if (update.suppressedBubbleKey != null) {
|
||||
BubbleBarBubble bb = mBubbles.remove(update.suppressedBubbleKey);
|
||||
if (bb != null) {
|
||||
mSuppressedBubbles.put(update.suppressedBubbleKey, bb);
|
||||
mBubbleBarViewController.removeBubble(bb);
|
||||
}
|
||||
}
|
||||
if (update.unsuppressedBubbleKey != null) {
|
||||
BubbleBarBubble bb = mSuppressedBubbles.remove(update.unsuppressedBubbleKey);
|
||||
if (bb != null) {
|
||||
// Unsuppressing an existing bubble should not cause the bar to expand or animate
|
||||
addBubbleInternally(bb, /* isExpanding= */ false, /* suppressAnimation= */ true);
|
||||
if (mBubbleBarViewController.isHiddenForNoBubbles()) {
|
||||
mBubbleBarViewController.setHiddenForBubbles(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the visibility if this is the initial state, if there are no bubbles, or if the
|
||||
// animation is suppressed.
|
||||
// If this is the initial bubble, the bubble bar will become visible as part of the
|
||||
// animation.
|
||||
if (update.initialState || mBubbles.isEmpty() || suppressAnimation) {
|
||||
mBubbleBarViewController.setHiddenForBubbles(mBubbles.isEmpty());
|
||||
}
|
||||
mBubbleStashedHandleViewController.ifPresent(
|
||||
controller -> controller.setHiddenForBubbles(mBubbles.isEmpty()));
|
||||
|
||||
if (mBubbles.isEmpty()) {
|
||||
// all bubbles were removed. clear the selected bubble
|
||||
mSelectedBubble = null;
|
||||
}
|
||||
|
||||
if (update.updatedBubble != null) {
|
||||
// Updates mean the dot state may have changed; any other changes were updated in
|
||||
// the populateBubble step.
|
||||
BubbleBarBubble bb = mBubbles.get(update.updatedBubble.getKey());
|
||||
if (suppressAnimation) {
|
||||
// since we're not animating this update, we should update the dot visibility here.
|
||||
bb.getView().updateDotVisibility(/* animate= */ false);
|
||||
} else {
|
||||
mBubbleBarViewController.animateBubbleNotification(
|
||||
bb, /* isExpanding= */ false, /* isUpdate= */ true);
|
||||
}
|
||||
}
|
||||
if (update.bubbleKeysInOrder != null && !update.bubbleKeysInOrder.isEmpty()) {
|
||||
// Create the new list
|
||||
List<BubbleBarBubble> newOrder = update.bubbleKeysInOrder.stream()
|
||||
.map(mBubbles::get).filter(Objects::nonNull).toList();
|
||||
if (!newOrder.isEmpty()) {
|
||||
mBubbleBarViewController.reorderBubbles(newOrder);
|
||||
}
|
||||
}
|
||||
if (bubbleToSelect != null) {
|
||||
setSelectedBubbleInternal(bubbleToSelect);
|
||||
}
|
||||
@@ -507,7 +437,6 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
}
|
||||
}
|
||||
if (update.bubbleBarLocation != null) {
|
||||
mSharedState.bubbleBarLocation = update.bubbleBarLocation;
|
||||
if (update.bubbleBarLocation != mBubbleBarViewController.getBubbleBarLocation()) {
|
||||
updateBubbleBarLocationInternal(update.bubbleBarLocation);
|
||||
}
|
||||
@@ -517,16 +446,19 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the given bubble from the backing list of bubbles after it was dismissed by the user.
|
||||
*/
|
||||
public void onBubbleDismissed(BubbleView bubble) {
|
||||
mBubbles.remove(bubble.getBubble().getKey());
|
||||
}
|
||||
|
||||
/** Tells WMShell to show the currently selected bubble. */
|
||||
public void showSelectedBubble() {
|
||||
if (getSelectedBubbleKey() != null) {
|
||||
if (mSelectedBubble instanceof BubbleBarBubble) {
|
||||
// Because we've visited this bubble, we should suppress the notification.
|
||||
// This is updated on WMShell side when we show the bubble, but that update
|
||||
// isn't
|
||||
// passed to launcher, instead we apply it directly here.
|
||||
BubbleInfo info = ((BubbleBarBubble) mSelectedBubble).getInfo();
|
||||
info.setFlags(
|
||||
info.getFlags() | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
|
||||
mSelectedBubble.getView().updateDotVisibility(true /* animate */);
|
||||
}
|
||||
mLastSentBubbleBarTop = mBarView.getRestingTopPositionOnScreen();
|
||||
mSystemUiProxy.showBubble(getSelectedBubbleKey(), mLastSentBubbleBarTop);
|
||||
} else {
|
||||
@@ -534,21 +466,27 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
/** Updates the currently selected bubble for launcher views and tells WMShell to show it. */
|
||||
/**
|
||||
* 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());
|
||||
if (DEBUG)
|
||||
Log.w(TAG, "showingSelectedBubble: " + b.getKey());
|
||||
setSelectedBubbleInternal(b);
|
||||
showSelectedBubble();
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the bubble that should be selected. This notifies the views, it does not notify
|
||||
* Sets the bubble that should be selected. This notifies the views, it does not
|
||||
* notify
|
||||
* WMShell that the selection has changed, that should go through either
|
||||
* {@link #showSelectedBubble()} or {@link #showAndSelectBubble(BubbleBarItem)}.
|
||||
*/
|
||||
private void setSelectedBubbleInternal(BubbleBarItem b) {
|
||||
if (!Objects.equals(b, mSelectedBubble)) {
|
||||
if (DEBUG) Log.w(TAG, "selectingBubble: " + b.getKey());
|
||||
if (DEBUG)
|
||||
Log.w(TAG, "selectingBubble: " + b.getKey());
|
||||
mSelectedBubble = b;
|
||||
mBubbleBarViewController.updateSelectedBubble(mSelectedBubble);
|
||||
}
|
||||
@@ -570,57 +508,153 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
* <p>
|
||||
* Updates the value locally in Launcher and in WMShell.
|
||||
*/
|
||||
public void updateBubbleBarLocation(BubbleBarLocation location,
|
||||
@BubbleBarLocation.UpdateSource int source) {
|
||||
public void updateBubbleBarLocation(BubbleBarLocation location) {
|
||||
updateBubbleBarLocationInternal(location);
|
||||
mSystemUiProxy.setBubbleBarLocation(location, source);
|
||||
mSystemUiProxy.setBubbleBarLocation(location);
|
||||
}
|
||||
|
||||
private void updateBubbleBarLocationInternal(BubbleBarLocation location) {
|
||||
mBubbleBarViewController.setBubbleBarLocation(location);
|
||||
mBubbleStashController.setBubbleBarLocation(location);
|
||||
mBubbleBarLocationListener.onBubbleBarLocationUpdated(location);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
|
||||
MAIN_EXECUTOR.execute(
|
||||
() -> {
|
||||
mBubbleBarViewController.animateBubbleBarLocation(bubbleBarLocation);
|
||||
mBubbleBarLocationListener.onBubbleBarLocationAnimated(bubbleBarLocation);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation bubbleBarLocation) {
|
||||
MAIN_EXECUTOR.execute(() -> {
|
||||
mBubbleBarViewController.onDragItemOverBubbleBarDragZone(bubbleBarLocation);
|
||||
if (mBubbleBarViewController.isLocationUpdatedForDropTarget()) {
|
||||
mBubbleBarLocationListener.onBubbleBarLocationAnimated(bubbleBarLocation);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemDraggedOutsideBubbleBarDropZone() {
|
||||
MAIN_EXECUTOR.execute(() -> {
|
||||
if (mBubbleBarViewController.isLocationUpdatedForDropTarget()) {
|
||||
BubbleBarLocation original = mBubbleBarViewController.getBubbleBarLocation();
|
||||
mBubbleBarLocationListener.onBubbleBarLocationAnimated(original);
|
||||
}
|
||||
mBubbleBarViewController.onItemDraggedOutsideBubbleBarDropZone();
|
||||
});
|
||||
}
|
||||
|
||||
/** Notifies WMShell to show the expanded view. */
|
||||
void showExpandedView() {
|
||||
mSystemUiProxy.showExpandedView();
|
||||
() -> mBubbleBarViewController.animateBubbleBarLocation(bubbleBarLocation));
|
||||
}
|
||||
|
||||
//
|
||||
// Loading data for the bubbles
|
||||
//
|
||||
|
||||
@Nullable
|
||||
private BubbleBarBubble populateBubble(Context context, BubbleInfo b, BubbleBarView bbv,
|
||||
@Nullable BubbleBarBubble existingBubble) {
|
||||
String appName;
|
||||
Bitmap badgeBitmap;
|
||||
Bitmap bubbleBitmap;
|
||||
Path dotPath;
|
||||
int dotColor;
|
||||
|
||||
boolean isImportantConvo = b.isImportantConversation();
|
||||
|
||||
ShortcutRequest.QueryResult result = new ShortcutRequest(context,
|
||||
new UserHandle(b.getUserId()))
|
||||
.forPackage(b.getPackageName(), b.getShortcutId())
|
||||
.query(FLAG_MATCH_DYNAMIC
|
||||
| FLAG_MATCH_PINNED_BY_ANY_LAUNCHER
|
||||
| FLAG_MATCH_CACHED
|
||||
| FLAG_GET_PERSONS_DATA);
|
||||
|
||||
ShortcutInfo shortcutInfo = result.size() > 0 ? result.get(0) : null;
|
||||
if (shortcutInfo == null) {
|
||||
Log.w(TAG, "No shortcutInfo found for bubble: " + b.getKey()
|
||||
+ " with shortcutId: " + b.getShortcutId());
|
||||
}
|
||||
|
||||
ApplicationInfo appInfo;
|
||||
try {
|
||||
appInfo = mLauncherApps.getApplicationInfo(
|
||||
b.getPackageName(),
|
||||
0,
|
||||
new UserHandle(b.getUserId()));
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// If we can't find package... don't think we should show the bubble.
|
||||
Log.w(TAG, "Unable to find packageName: " + b.getPackageName());
|
||||
return null;
|
||||
}
|
||||
if (appInfo == null) {
|
||||
Log.w(TAG, "Unable to find appInfo: " + b.getPackageName());
|
||||
return null;
|
||||
}
|
||||
PackageManager pm = context.getPackageManager();
|
||||
appName = String.valueOf(appInfo.loadLabel(pm));
|
||||
Drawable appIcon = appInfo.loadUnbadgedIcon(pm);
|
||||
Drawable badgedIcon = pm.getUserBadgedIcon(appIcon, new UserHandle(b.getUserId()));
|
||||
|
||||
// Badged bubble image
|
||||
Drawable bubbleDrawable = mIconFactory.getBubbleDrawable(context, shortcutInfo,
|
||||
b.getIcon());
|
||||
if (bubbleDrawable == null) {
|
||||
// Default to app icon
|
||||
bubbleDrawable = appIcon;
|
||||
}
|
||||
|
||||
BitmapInfo badgeBitmapInfo = mIconFactory.getBadgeBitmap(badgedIcon, isImportantConvo);
|
||||
badgeBitmap = badgeBitmapInfo.icon;
|
||||
|
||||
float[] bubbleBitmapScale = new float[1];
|
||||
bubbleBitmap = mIconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale);
|
||||
|
||||
// Dot color & placement
|
||||
Path iconPath = PathParser.createPathFromPathData(
|
||||
context.getResources().getString(
|
||||
com.android.internal.R.string.config_icon_mask));
|
||||
Matrix matrix = new Matrix();
|
||||
float scale = bubbleBitmapScale[0];
|
||||
float radius = BubbleView.DEFAULT_PATH_SIZE / 2f;
|
||||
matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
|
||||
radius /* pivot y */);
|
||||
iconPath.transform(matrix);
|
||||
dotPath = iconPath;
|
||||
dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
|
||||
Color.WHITE, WHITE_SCRIM_ALPHA / 255f);
|
||||
|
||||
if (existingBubble == null) {
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
BubbleView bubbleView = (BubbleView) inflater.inflate(
|
||||
R.layout.bubblebar_item_view, bbv, false /* attachToRoot */);
|
||||
|
||||
BubbleBarBubble bubble = new BubbleBarBubble(b, bubbleView,
|
||||
badgeBitmap, bubbleBitmap, dotColor, dotPath, appName);
|
||||
bubbleView.setBubble(bubble);
|
||||
return bubble;
|
||||
} else {
|
||||
// If we already have a bubble (so it already has an inflated view), update it.
|
||||
existingBubble.setInfo(b);
|
||||
existingBubble.setBadge(badgeBitmap);
|
||||
existingBubble.setIcon(bubbleBitmap);
|
||||
existingBubble.setDotColor(dotColor);
|
||||
existingBubble.setDotPath(dotPath);
|
||||
existingBubble.setAppName(appName);
|
||||
return existingBubble;
|
||||
}
|
||||
}
|
||||
|
||||
private BubbleBarOverflow createOverflow(Context context) {
|
||||
Bitmap bitmap = createOverflowBitmap(context);
|
||||
LayoutInflater inflater = LayoutInflater.from(context);
|
||||
BubbleView bubbleView = (BubbleView) inflater.inflate(
|
||||
R.layout.bubblebar_item_view, mBarView, false /* attachToRoot */);
|
||||
BubbleBarOverflow overflow = new BubbleBarOverflow(bubbleView);
|
||||
bubbleView.setOverflow(overflow, bitmap);
|
||||
return overflow;
|
||||
}
|
||||
|
||||
private Bitmap createOverflowBitmap(Context context) {
|
||||
Drawable iconDrawable = AppCompatResources.getDrawable(mContext,
|
||||
R.drawable.bubble_ic_overflow_button);
|
||||
|
||||
final TypedArray ta = mContext.obtainStyledAttributes(
|
||||
new int[] {
|
||||
com.android.internal.R.attr.materialColorOnPrimaryFixed,
|
||||
com.android.internal.R.attr.materialColorPrimaryFixed
|
||||
});
|
||||
int overflowIconColor = ta.getColor(0, Color.WHITE);
|
||||
int overflowBackgroundColor = ta.getColor(1, Color.BLACK);
|
||||
ta.recycle();
|
||||
|
||||
iconDrawable.setTint(overflowIconColor);
|
||||
|
||||
int inset = context.getResources().getDimensionPixelSize(R.dimen.bubblebar_overflow_inset);
|
||||
Drawable foreground = new InsetDrawable(iconDrawable, inset);
|
||||
Drawable drawable = new AdaptiveIconDrawable(new ColorDrawable(overflowBackgroundColor),
|
||||
foreground);
|
||||
|
||||
return mIconFactory.createBadgedIconBitmap(drawable).icon;
|
||||
}
|
||||
|
||||
private void onBubbleBarBoundsChanged() {
|
||||
int newTop = mBarView.getRestingTopPositionOnScreen();
|
||||
if (newTop != mLastSentBubbleBarTop) {
|
||||
@@ -629,20 +663,9 @@ public class BubbleBarController extends IBubblesListener.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
private void addBubbleInternally(BubbleBarBubble bubble, boolean isExpanding,
|
||||
boolean suppressAnimation) {
|
||||
mBubbles.put(bubble.getKey(), bubble);
|
||||
mBubbleBarViewController.addBubble(bubble, isExpanding,
|
||||
suppressAnimation, /* bubbleToSelect = */ null);
|
||||
}
|
||||
|
||||
/** Listener of {@link BubbleBarLocation} updates. */
|
||||
public interface BubbleBarLocationListener {
|
||||
|
||||
/** Called when {@link BubbleBarLocation} is animated, but change is not yet final. */
|
||||
void onBubbleBarLocationAnimated(BubbleBarLocation location);
|
||||
|
||||
/** Called when {@link BubbleBarLocation} is updated permanently. */
|
||||
void onBubbleBarLocationUpdated(BubbleBarLocation location);
|
||||
/** Interface for checking whether the IME is visible. */
|
||||
public interface ImeVisibilityChecker {
|
||||
/** Whether the IME is visible. */
|
||||
boolean isImeVisible();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,11 +17,10 @@ package com.android.launcher3.taskbar.bubbles
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Path
|
||||
import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutMessage
|
||||
import com.android.wm.shell.shared.bubbles.BubbleInfo
|
||||
import com.android.wm.shell.common.bubbles.BubbleInfo
|
||||
|
||||
/** An entity in the bubble bar. */
|
||||
sealed class BubbleBarItem(open val key: String, open var view: BubbleView)
|
||||
sealed class BubbleBarItem(open var key: String, open var view: BubbleView)
|
||||
|
||||
/** Contains state info about a bubble in the bubble bar as well as presentation information. */
|
||||
data class BubbleBarBubble(
|
||||
@@ -31,13 +30,8 @@ data class BubbleBarBubble(
|
||||
var icon: Bitmap,
|
||||
var dotColor: Int,
|
||||
var dotPath: Path,
|
||||
var appName: String,
|
||||
var flyoutMessage: BubbleBarFlyoutMessage?,
|
||||
var appName: String
|
||||
) : BubbleBarItem(info.key, view)
|
||||
|
||||
/** Represents the overflow bubble in the bubble bar. */
|
||||
data class BubbleBarOverflow(override var view: BubbleView) : BubbleBarItem(KEY, view) {
|
||||
companion object {
|
||||
const val KEY = "Overflow"
|
||||
}
|
||||
}
|
||||
data class BubbleBarOverflow(override var view: BubbleView) : BubbleBarItem("Overflow", view)
|
||||
|
||||
@@ -27,10 +27,8 @@ import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarController.BubbleBarLocationListener
|
||||
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
|
||||
import com.android.wm.shell.shared.bubbles.BaseBubblePinController
|
||||
import com.android.wm.shell.shared.bubbles.BubbleBarLocation
|
||||
import com.android.wm.shell.common.bubbles.BaseBubblePinController
|
||||
import com.android.wm.shell.common.bubbles.BubbleBarLocation
|
||||
|
||||
/**
|
||||
* Controller to manage pinning bubble bar to left or right when dragging starts from the bubble bar
|
||||
@@ -43,17 +41,12 @@ class BubbleBarPinController(
|
||||
|
||||
private lateinit var bubbleBarViewController: BubbleBarViewController
|
||||
private lateinit var bubbleStashController: BubbleStashController
|
||||
private lateinit var bubbleBarLocationListener: BubbleBarLocationListener
|
||||
private var exclRectWidth: Float = 0f
|
||||
private var exclRectHeight: Float = 0f
|
||||
|
||||
private var dropTargetView: View? = null
|
||||
|
||||
fun init(
|
||||
bubbleControllers: BubbleControllers,
|
||||
bubbleBarLocationListener: BubbleBarLocationListener
|
||||
) {
|
||||
this.bubbleBarLocationListener = bubbleBarLocationListener
|
||||
fun init(bubbleControllers: BubbleControllers) {
|
||||
bubbleBarViewController = bubbleControllers.bubbleBarViewController
|
||||
bubbleStashController = bubbleControllers.bubbleStashController
|
||||
exclRectWidth = context.resources.getDimension(R.dimen.bubblebar_dismiss_zone_width)
|
||||
@@ -92,7 +85,6 @@ class BubbleBarPinController(
|
||||
|
||||
val bounds = bubbleBarViewController.bubbleBarBounds
|
||||
val horizontalMargin = bubbleBarViewController.horizontalMargin
|
||||
bubbleBarLocationListener.onBubbleBarLocationAnimated(location)
|
||||
dropTargetView?.updateLayoutParams<FrameLayout.LayoutParams> {
|
||||
width = bounds.width()
|
||||
height = bounds.height()
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+138
-1034
File diff suppressed because it is too large
Load Diff
@@ -15,35 +15,22 @@
|
||||
*/
|
||||
package com.android.launcher3.taskbar.bubbles;
|
||||
|
||||
import static com.android.launcher3.taskbar.TaskbarViewController.ALPHA_INDEX_BUBBLE_BAR;
|
||||
|
||||
import android.graphics.Rect;
|
||||
import android.view.View;
|
||||
|
||||
import com.android.launcher3.taskbar.TaskbarControllers;
|
||||
import com.android.launcher3.taskbar.TaskbarSharedState;
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarViewController.TaskbarViewPropertiesProvider;
|
||||
import com.android.launcher3.taskbar.bubbles.stashing.BubbleBarLocationOnDemandListener;
|
||||
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
|
||||
import com.android.launcher3.util.MultiPropertyFactory;
|
||||
import com.android.launcher3.util.RunnableList;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.util.Optional;
|
||||
|
||||
/** Hosts various bubble controllers to facilitate passing between one another. */
|
||||
/**
|
||||
* Hosts various bubble controllers to facilitate passing between one another.
|
||||
*/
|
||||
public class BubbleControllers {
|
||||
|
||||
public final BubbleBarController bubbleBarController;
|
||||
public final BubbleBarViewController bubbleBarViewController;
|
||||
public final BubbleStashController bubbleStashController;
|
||||
public final Optional<BubbleStashedHandleViewController> bubbleStashedHandleViewController;
|
||||
public final BubbleStashedHandleViewController bubbleStashedHandleViewController;
|
||||
public final BubbleDragController bubbleDragController;
|
||||
public final BubbleDismissController bubbleDismissController;
|
||||
public final BubbleBarPinController bubbleBarPinController;
|
||||
public final BubblePinController bubblePinController;
|
||||
public final Optional<BubbleBarSwipeController> bubbleBarSwipeController;
|
||||
public final BubbleCreator bubbleCreator;
|
||||
|
||||
private final RunnableList mPostInitRunnables = new RunnableList();
|
||||
|
||||
@@ -56,13 +43,11 @@ public class BubbleControllers {
|
||||
BubbleBarController bubbleBarController,
|
||||
BubbleBarViewController bubbleBarViewController,
|
||||
BubbleStashController bubbleStashController,
|
||||
Optional<BubbleStashedHandleViewController> bubbleStashedHandleViewController,
|
||||
BubbleStashedHandleViewController bubbleStashedHandleViewController,
|
||||
BubbleDragController bubbleDragController,
|
||||
BubbleDismissController bubbleDismissController,
|
||||
BubbleBarPinController bubbleBarPinController,
|
||||
BubblePinController bubblePinController,
|
||||
Optional<BubbleBarSwipeController> bubbleBarSwipeController,
|
||||
BubbleCreator bubbleCreator) {
|
||||
BubblePinController bubblePinController) {
|
||||
this.bubbleBarController = bubbleBarController;
|
||||
this.bubbleBarViewController = bubbleBarViewController;
|
||||
this.bubbleStashController = bubbleStashController;
|
||||
@@ -71,8 +56,6 @@ public class BubbleControllers {
|
||||
this.bubbleDismissController = bubbleDismissController;
|
||||
this.bubbleBarPinController = bubbleBarPinController;
|
||||
this.bubblePinController = bubblePinController;
|
||||
this.bubbleBarSwipeController = bubbleBarSwipeController;
|
||||
this.bubbleCreator = bubbleCreator;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -82,44 +65,16 @@ public class BubbleControllers {
|
||||
* were created
|
||||
* in constructors for now, as some controllers may still be waiting for init().
|
||||
*/
|
||||
public void init(TaskbarSharedState taskbarSharedState, TaskbarControllers taskbarControllers) {
|
||||
BubbleBarLocationCompositeListener bubbleBarLocationListeners =
|
||||
new BubbleBarLocationCompositeListener(
|
||||
taskbarControllers.navbarButtonsViewController,
|
||||
taskbarControllers.taskbarViewController,
|
||||
new BubbleBarLocationOnDemandListener(() -> taskbarControllers.uiController)
|
||||
);
|
||||
public void init(TaskbarControllers taskbarControllers) {
|
||||
bubbleBarController.init(this,
|
||||
bubbleBarLocationListeners,
|
||||
taskbarSharedState);
|
||||
bubbleStashedHandleViewController.ifPresent(
|
||||
controller -> controller.init(/* bubbleControllers = */ this));
|
||||
bubbleStashController.init(
|
||||
taskbarControllers.taskbarInsetsController,
|
||||
bubbleBarViewController,
|
||||
bubbleStashedHandleViewController.orElse(null),
|
||||
taskbarControllers::runAfterInit
|
||||
);
|
||||
bubbleBarViewController.init(taskbarControllers, /* bubbleControllers = */ this,
|
||||
new TaskbarViewPropertiesProvider() {
|
||||
@Override
|
||||
public Rect getTaskbarViewBounds() {
|
||||
return taskbarControllers.taskbarViewController
|
||||
.getTransientTaskbarIconLayoutBoundsInParent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MultiPropertyFactory<View>.MultiProperty getIconsAlpha() {
|
||||
return taskbarControllers.taskbarViewController
|
||||
.getTaskbarIconAlpha()
|
||||
.get(ALPHA_INDEX_BUBBLE_BAR);
|
||||
}
|
||||
});
|
||||
taskbarControllers.navbarButtonsViewController::isImeVisible);
|
||||
bubbleBarViewController.init(taskbarControllers, this);
|
||||
bubbleStashedHandleViewController.init(taskbarControllers, this);
|
||||
bubbleStashController.init(taskbarControllers, this);
|
||||
bubbleDragController.init(/* bubbleControllers = */ this);
|
||||
bubbleDismissController.init(/* bubbleControllers = */ this);
|
||||
bubbleBarPinController.init(this, bubbleBarLocationListeners);
|
||||
bubbleBarPinController.init(this);
|
||||
bubblePinController.init(this);
|
||||
bubbleBarSwipeController.ifPresent(c -> c.init(this));
|
||||
|
||||
mPostInitRunnables.executeAllAndDestroy();
|
||||
}
|
||||
@@ -141,13 +96,7 @@ public class BubbleControllers {
|
||||
* Cleans up all controllers.
|
||||
*/
|
||||
public void onDestroy() {
|
||||
bubbleStashedHandleViewController.ifPresent(BubbleStashedHandleViewController::onDestroy);
|
||||
bubbleStashedHandleViewController.onDestroy();
|
||||
bubbleBarController.onDestroy();
|
||||
bubbleBarViewController.onDestroy();
|
||||
}
|
||||
|
||||
/** Dumps bubble controllers state. */
|
||||
public void dump(PrintWriter pw) {
|
||||
bubbleBarViewController.dump(pw);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +29,8 @@ import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext;
|
||||
import com.android.launcher3.taskbar.TaskbarDragLayer;
|
||||
import com.android.wm.shell.shared.bubbles.DismissView;
|
||||
import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
|
||||
import com.android.wm.shell.common.bubbles.DismissView;
|
||||
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
|
||||
|
||||
|
||||
/**
|
||||
@@ -144,10 +144,10 @@ public class BubbleDismissController {
|
||||
if (mMagnetizedObject.getUnderlyingObject() instanceof BubbleView) {
|
||||
BubbleView bubbleView = (BubbleView) mMagnetizedObject.getUnderlyingObject();
|
||||
if (bubbleView.getBubble() != null) {
|
||||
mBubbleBarViewController.notifySysUiBubbleDismissed(bubbleView.getBubble());
|
||||
mBubbleBarViewController.onDismissBubbleWhileDragging(bubbleView.getBubble());
|
||||
}
|
||||
} else if (mMagnetizedObject.getUnderlyingObject() instanceof BubbleBarView) {
|
||||
mBubbleBarViewController.onDismissAllBubbles();
|
||||
mBubbleBarViewController.onDismissAllBubblesWhileDragging();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,7 @@
|
||||
package com.android.launcher3.taskbar.bubbles
|
||||
|
||||
import com.android.launcher3.R
|
||||
import com.android.wm.shell.R as SharedR
|
||||
import com.android.wm.shell.shared.bubbles.DismissView
|
||||
import com.android.wm.shell.common.bubbles.DismissView
|
||||
|
||||
/**
|
||||
* Dismiss view is shared from WMShell. It requires setup with local resources.
|
||||
@@ -36,9 +35,9 @@ fun DismissView.setup() {
|
||||
iconSizeResId = R.dimen.bubblebar_dismiss_target_icon_size,
|
||||
bottomMarginResId = R.dimen.bubblebar_dismiss_target_bottom_margin,
|
||||
floatingGradientHeightResId = R.dimen.bubblebar_dismiss_floating_gradient_height,
|
||||
floatingGradientColorResId = android.R.color.system_neutral1_900,
|
||||
backgroundResId = SharedR.drawable.floating_dismiss_background,
|
||||
iconResId = SharedR.drawable.floating_dismiss_ic_close,
|
||||
floatingGradientColorResId = R.color.system_neutral1_900,
|
||||
backgroundResId = R.drawable.bg_bubble_dismiss_circle,
|
||||
iconResId = R.drawable.ic_bubble_dismiss_white
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -29,14 +29,14 @@ import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
import androidx.dynamicanimation.animation.FloatPropertyCompat;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.wm.shell.shared.bubbles.DismissCircleView;
|
||||
import com.android.wm.shell.shared.bubbles.DismissView;
|
||||
//import com.android.wm.shell.shared.animation.PhysicsAnimator;
|
||||
import com.android.wm.shell.common.bubbles.DismissCircleView;
|
||||
import com.android.wm.shell.common.bubbles.DismissView;
|
||||
|
||||
import app.lawnchair.animation.PhysicsAnimator;
|
||||
|
||||
/**
|
||||
* The animator performs the bubble animations while dragging and coordinates bubble and dismiss
|
||||
* The animator performs the bubble animations while dragging and coordinates
|
||||
* bubble and dismiss
|
||||
* view animations when it gets magnetized, released or dismissed.
|
||||
*/
|
||||
public class BubbleDragAnimator {
|
||||
@@ -46,11 +46,11 @@ public class BubbleDragAnimator {
|
||||
// 400f matches to MEDIUM_LOW spring stiffness
|
||||
private static final float TRANSLATION_SPRING_STIFFNESS = 400f;
|
||||
|
||||
private final PhysicsAnimator.SpringConfig mDefaultConfig =
|
||||
new PhysicsAnimator.SpringConfig(STIFFNESS_LOW, DAMPING_RATIO_LOW_BOUNCY);
|
||||
private final PhysicsAnimator.SpringConfig mTranslationConfig =
|
||||
new PhysicsAnimator.SpringConfig(TRANSLATION_SPRING_STIFFNESS,
|
||||
DAMPING_RATIO_LOW_BOUNCY);
|
||||
private final PhysicsAnimator.SpringConfig mDefaultConfig = new PhysicsAnimator.SpringConfig(STIFFNESS_LOW,
|
||||
DAMPING_RATIO_LOW_BOUNCY);
|
||||
private final PhysicsAnimator.SpringConfig mTranslationConfig = new PhysicsAnimator.SpringConfig(
|
||||
TRANSLATION_SPRING_STIFFNESS,
|
||||
DAMPING_RATIO_LOW_BOUNCY);
|
||||
@NonNull
|
||||
private final View mView;
|
||||
@NonNull
|
||||
@@ -114,7 +114,8 @@ public class BubbleDragAnimator {
|
||||
*
|
||||
* @param restingPosition the position to animate to
|
||||
* @param velocity the initial velocity to use for the spring animation
|
||||
* @param endActions gets called when the animation completes or gets cancelled
|
||||
* @param endActions gets called when the animation completes or gets
|
||||
* cancelled
|
||||
*/
|
||||
public void animateToRestingState(@NonNull PointF restingPosition, @NonNull PointF velocity,
|
||||
@Nullable Runnable endActions) {
|
||||
@@ -130,7 +131,7 @@ public class BubbleDragAnimator {
|
||||
boolean wasFling, boolean canceled, float finalValue, float finalVelocity,
|
||||
boolean allRelevantPropertyAnimationsEnded) -> {
|
||||
if (canceled || allRelevantPropertyAnimationsEnded) {
|
||||
resetAnimatedViews(restingPosition, /* dismissed= */ false);
|
||||
resetAnimatedViews(restingPosition);
|
||||
if (endActions != null) {
|
||||
endActions.run();
|
||||
}
|
||||
@@ -140,7 +141,8 @@ public class BubbleDragAnimator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the dragged view alongside the dismiss view when it gets captured in the dismiss
|
||||
* Animates the dragged view alongside the dismiss view when it gets captured in
|
||||
* the dismiss
|
||||
* target area.
|
||||
*/
|
||||
public void animateDismissCaptured() {
|
||||
@@ -161,7 +163,8 @@ public class BubbleDragAnimator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the dragged view alongside the dismiss view when it gets released from the dismiss
|
||||
* Animates the dragged view alongside the dismiss view when it gets released
|
||||
* from the dismiss
|
||||
* target area.
|
||||
*/
|
||||
public void animateDismissReleased() {
|
||||
@@ -182,10 +185,13 @@ public class BubbleDragAnimator {
|
||||
}
|
||||
|
||||
/**
|
||||
* Animates the dragged bubble dismiss when it's released in the dismiss target area.
|
||||
* Animates the dragged bubble dismiss when it's released in the dismiss target
|
||||
* area.
|
||||
*
|
||||
* @param initialPosition the initial position to move the bubble too after animation finishes
|
||||
* @param endActions gets called when the animation completes or gets cancelled
|
||||
* @param initialPosition the initial position to move the bubble too after
|
||||
* animation finishes
|
||||
* @param endActions gets called when the animation completes or gets
|
||||
* cancelled
|
||||
*/
|
||||
public void animateDismiss(@NonNull PointF initialPosition, @Nullable Runnable endActions) {
|
||||
float dismissHeight = mDismissView != null ? mDismissView.getHeight() : 0f;
|
||||
@@ -199,8 +205,9 @@ public class BubbleDragAnimator {
|
||||
boolean wasFling, boolean canceled, float finalValue, float finalVelocity,
|
||||
boolean allRelevantPropertyAnimationsEnded) -> {
|
||||
if (canceled || allRelevantPropertyAnimationsEnded) {
|
||||
resetAnimatedViews(initialPosition, /* dismissed= */ true);
|
||||
if (endActions != null) endActions.run();
|
||||
resetAnimatedViews(initialPosition);
|
||||
if (endActions != null)
|
||||
endActions.run();
|
||||
}
|
||||
})
|
||||
.start();
|
||||
@@ -210,14 +217,11 @@ public class BubbleDragAnimator {
|
||||
* Reset the animated views to the initial state
|
||||
*
|
||||
* @param initialPosition position of the bubble
|
||||
* @param dismissed whether the animated view was dismissed
|
||||
*/
|
||||
private void resetAnimatedViews(@NonNull PointF initialPosition, boolean dismissed) {
|
||||
private void resetAnimatedViews(@NonNull PointF initialPosition) {
|
||||
mView.setScaleX(1f);
|
||||
mView.setScaleY(1f);
|
||||
if (!dismissed) {
|
||||
mView.setAlpha(1f);
|
||||
}
|
||||
mView.setAlpha(1f);
|
||||
mView.setTranslationX(initialPosition.x);
|
||||
mView.setTranslationY(initialPosition.y);
|
||||
|
||||
|
||||
@@ -16,31 +16,19 @@
|
||||
package com.android.launcher3.taskbar.bubbles;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.graphics.Point;
|
||||
import android.graphics.PointF;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.VelocityTracker;
|
||||
import android.view.View;
|
||||
import android.view.ViewConfiguration;
|
||||
import android.view.WindowManager;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.dynamicanimation.animation.FloatPropertyCompat;
|
||||
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext;
|
||||
import com.android.wm.shell.shared.bubbles.BaseBubblePinController.LocationChangeListener;
|
||||
import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
|
||||
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
|
||||
import com.android.wm.shell.shared.bubbles.DeviceConfig;
|
||||
import com.android.wm.shell.shared.bubbles.DragZone;
|
||||
import com.android.wm.shell.shared.bubbles.DragZoneFactory;
|
||||
import com.android.wm.shell.shared.bubbles.DragZoneFactory.DesktopWindowModeChecker;
|
||||
import com.android.wm.shell.shared.bubbles.DragZoneFactory.SplitScreenModeChecker;
|
||||
import com.android.wm.shell.shared.bubbles.DraggedObject;
|
||||
import com.android.wm.shell.shared.bubbles.DropTargetManager;
|
||||
import com.android.wm.shell.shared.bubbles.DropTargetManager.DragZoneChangedListener;
|
||||
import com.android.wm.shell.common.bubbles.BaseBubblePinController.LocationChangeListener;
|
||||
import com.android.wm.shell.common.bubbles.BubbleBarLocation;
|
||||
|
||||
/**
|
||||
* Controls bubble bar drag interactions.
|
||||
@@ -88,36 +76,9 @@ public class BubbleDragController {
|
||||
private BubbleDismissController mBubbleDismissController;
|
||||
private BubbleBarPinController mBubbleBarPinController;
|
||||
private BubblePinController mBubblePinController;
|
||||
private final DropTargetManager mDropTargetManager;
|
||||
private final DragZoneFactory mDragZoneFactory;
|
||||
private final BubbleDragZoneChangedListener mBubbleDragZoneChangedListener;
|
||||
|
||||
private boolean mIsDragging;
|
||||
|
||||
public BubbleDragController(TaskbarActivityContext activity, FrameLayout dropTargetParent) {
|
||||
public BubbleDragController(TaskbarActivityContext activity) {
|
||||
mActivity = activity;
|
||||
WindowManager windowManager =
|
||||
mActivity.getApplicationContext().getSystemService(WindowManager.class);
|
||||
DeviceConfig deviceConfig =
|
||||
DeviceConfig.create(mActivity.getApplicationContext(), windowManager);
|
||||
SplitScreenModeChecker splitScreenModeChecker = new SplitScreenModeChecker() {
|
||||
@NonNull
|
||||
@Override
|
||||
public SplitScreenMode getSplitScreenMode() {
|
||||
return SplitScreenMode.NONE;
|
||||
}
|
||||
};
|
||||
DesktopWindowModeChecker desktopWindowModeChecker = new DesktopWindowModeChecker() {
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
mDragZoneFactory = new DragZoneFactory(mActivity.getApplicationContext(), deviceConfig,
|
||||
splitScreenModeChecker, desktopWindowModeChecker);
|
||||
mBubbleDragZoneChangedListener = new BubbleDragZoneChangedListener();
|
||||
mDropTargetManager = new DropTargetManager(mActivity.getApplicationContext(),
|
||||
dropTargetParent, mBubbleDragZoneChangedListener);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,90 +128,45 @@ public class BubbleDragController {
|
||||
}
|
||||
};
|
||||
|
||||
private BubbleBarLocation getBubbleBarLocationDuringDrag() {
|
||||
return BubbleAnythingFlagHelper.enableBubbleToFullscreen()
|
||||
? mBubbleDragZoneChangedListener.mBubbleBarLocation
|
||||
: mReleasedLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDragStart() {
|
||||
mBubblePinController.setListener(mLocationChangeListener);
|
||||
mBubbleBarViewController.onBubbleDragStart(bubbleView);
|
||||
if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
|
||||
DraggedObject.Bubble draggedBubble =
|
||||
new DraggedObject.Bubble(
|
||||
mBubbleBarViewController.getBubbleBarLocation());
|
||||
mDropTargetManager.onDragStarted(draggedBubble,
|
||||
mDragZoneFactory.createSortedDragZones(draggedBubble));
|
||||
} else {
|
||||
mBubblePinController.setListener(mLocationChangeListener);
|
||||
mBubblePinController.onDragStart(
|
||||
mBubbleBarViewController.getBubbleBarLocation().isOnLeft(
|
||||
bubbleView.isLayoutRtl()));
|
||||
}
|
||||
mBubblePinController.onDragStart(
|
||||
mBubbleBarViewController.getBubbleBarLocation().isOnLeft(
|
||||
bubbleView.isLayoutRtl()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDragUpdate(float x, float y, float newTx, float newTy) {
|
||||
bubbleView.setDragTranslationX(newTx);
|
||||
bubbleView.setTranslationY(newTy);
|
||||
if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
|
||||
mDropTargetManager.onDragUpdated((int) x, (int) y);
|
||||
} else {
|
||||
mBubblePinController.onDragUpdate(x, y);
|
||||
}
|
||||
mBubblePinController.onDragUpdate(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDragRelease() {
|
||||
if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
|
||||
mDropTargetManager.onDragEnded();
|
||||
if (!mBubbleDragZoneChangedListener.isDraggedToFullscreen()) {
|
||||
// TODO b/393173014: check for desktop window and split once they're
|
||||
// implemented. this notifies wm shell that the dragged bubble was
|
||||
// released so that we can show the expanded view. we only want to show it
|
||||
// after releasing in a Bubble zone. But Split and Desktop Window aren't
|
||||
// implemented yet, so we only check for full screen for now.
|
||||
mBubbleBarViewController.onBubbleDragRelease(
|
||||
getBubbleBarLocationDuringDrag());
|
||||
}
|
||||
} else {
|
||||
mBubblePinController.onDragEnd();
|
||||
mBubbleBarViewController.onBubbleDragRelease(getBubbleBarLocationDuringDrag());
|
||||
}
|
||||
mBubblePinController.onDragEnd();
|
||||
mBubbleBarViewController.onBubbleDragRelease(mReleasedLocation);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDragDismiss() {
|
||||
if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
|
||||
mDropTargetManager.onDragEnded();
|
||||
} else {
|
||||
mBubblePinController.onDragEnd();
|
||||
}
|
||||
mBubbleBarViewController.onBubbleDismissed(bubbleView);
|
||||
mBubblePinController.onDragEnd();
|
||||
mBubbleBarViewController.onBubbleDragEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDragEnd(float x, float y) {
|
||||
mBubbleBarController.updateBubbleBarLocation(getBubbleBarLocationDuringDrag(),
|
||||
BubbleBarLocation.UpdateSource.DRAG_BUBBLE);
|
||||
if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
|
||||
mDropTargetManager.onDragEnded();
|
||||
if (mBubbleDragZoneChangedListener.isDraggedToFullscreen()) {
|
||||
mBubbleBarViewController.moveDraggedBubbleToFullscreen(
|
||||
bubbleView, new Point((int) x, (int) y));
|
||||
}
|
||||
} else {
|
||||
mBubblePinController.setListener(null);
|
||||
}
|
||||
void onDragEnd() {
|
||||
mBubbleBarController.updateBubbleBarLocation(mReleasedLocation);
|
||||
mBubbleBarViewController.onBubbleDragEnd();
|
||||
mBubblePinController.setListener(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PointF getRestingPosition() {
|
||||
return mBubbleBarViewController.getDraggedBubbleReleaseTranslation(
|
||||
getInitialPosition(), getBubbleBarLocationDuringDrag());
|
||||
getInitialPosition(), mReleasedLocation);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -268,12 +184,6 @@ public class BubbleDragController {
|
||||
private final LocationChangeListener mLocationChangeListener =
|
||||
location -> mReleasedLocation = location;
|
||||
|
||||
private BubbleBarLocation getBubbleBarLocationDuringDrag() {
|
||||
return BubbleAnythingFlagHelper.enableBubbleToFullscreen()
|
||||
? mBubbleDragZoneChangedListener.mBubbleBarLocation
|
||||
: mReleasedLocation;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean onTouchDown(@NonNull View view, @NonNull MotionEvent event) {
|
||||
if (bubbleBarView.isExpanded()) return false;
|
||||
@@ -282,88 +192,53 @@ public class BubbleDragController {
|
||||
|
||||
@Override
|
||||
void onDragStart() {
|
||||
mBubbleBarPinController.setListener(mLocationChangeListener);
|
||||
initialRelativePivot.set(bubbleBarView.getRelativePivotX(),
|
||||
bubbleBarView.getRelativePivotY());
|
||||
// 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.setIsDragging(true);
|
||||
if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
|
||||
DraggedObject.BubbleBar draggedBubbleBar = new DraggedObject.BubbleBar(
|
||||
mBubbleBarViewController.getBubbleBarLocation());
|
||||
mDropTargetManager.onDragStarted(draggedBubbleBar,
|
||||
mDragZoneFactory.createSortedDragZones(draggedBubbleBar));
|
||||
} else {
|
||||
mBubbleBarPinController.setListener(mLocationChangeListener);
|
||||
mBubbleBarPinController.onDragStart(
|
||||
bubbleBarView.getBubbleBarLocation().isOnLeft(
|
||||
bubbleBarView.isLayoutRtl()));
|
||||
}
|
||||
mBubbleBarPinController.onDragStart(
|
||||
bubbleBarView.getBubbleBarLocation().isOnLeft(bubbleBarView.isLayoutRtl()));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDragUpdate(float x, float y, float newTx, float newTy) {
|
||||
bubbleBarView.setTranslationX(newTx);
|
||||
bubbleBarView.setTranslationY(newTy);
|
||||
if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
|
||||
mDropTargetManager.onDragUpdated((int) x, (int) y);
|
||||
} else {
|
||||
mBubbleBarPinController.onDragUpdate(x, y);
|
||||
}
|
||||
mBubbleBarPinController.onDragUpdate(x, y);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDragRelease() {
|
||||
if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
|
||||
mDropTargetManager.onDragEnded();
|
||||
} else {
|
||||
mBubbleBarPinController.onDragEnd();
|
||||
}
|
||||
mBubbleBarPinController.onDragEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDragDismiss() {
|
||||
if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
|
||||
mDropTargetManager.onDragEnded();
|
||||
} else {
|
||||
mBubbleBarPinController.onDragEnd();
|
||||
}
|
||||
mBubbleBarPinController.onDragEnd();
|
||||
}
|
||||
|
||||
@Override
|
||||
void onDragEnd(float x, float y) {
|
||||
void onDragEnd() {
|
||||
// Make sure to update location as the first thing. Pivot update causes a relayout
|
||||
mBubbleBarController.updateBubbleBarLocation(getBubbleBarLocationDuringDrag(),
|
||||
BubbleBarLocation.UpdateSource.DRAG_BAR);
|
||||
mBubbleBarController.updateBubbleBarLocation(mReleasedLocation);
|
||||
bubbleBarView.setIsDragging(false);
|
||||
// Restoring the initial pivot for the bubble bar view
|
||||
bubbleBarView.setRelativePivot(initialRelativePivot.x, initialRelativePivot.y);
|
||||
mBubbleBarViewController.onBubbleBarDragEnd();
|
||||
if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
|
||||
mDropTargetManager.onDragEnded();
|
||||
} else {
|
||||
mBubbleBarPinController.setListener(null);
|
||||
}
|
||||
mBubbleBarPinController.setListener(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected PointF getRestingPosition() {
|
||||
return mBubbleBarViewController.getBubbleBarDragReleaseTranslation(
|
||||
getInitialPosition(), getBubbleBarLocationDuringDrag());
|
||||
getInitialPosition(), mReleasedLocation);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Whether there is an item being dragged or not. */
|
||||
public boolean isDragging() {
|
||||
return mIsDragging;
|
||||
}
|
||||
|
||||
/** Sets whether something is being dragged or not. */
|
||||
public void setIsDragging(boolean isDragging) {
|
||||
mIsDragging = isDragging;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bubble touch listener for handling a single bubble view or bubble bar view while dragging.
|
||||
* The dragging starts after "shorter" long click (the long click duration might change):
|
||||
@@ -409,7 +284,7 @@ public class BubbleDragController {
|
||||
private final PointF mTouchDownLocation = new PointF();
|
||||
private final PointF mViewInitialPosition = new PointF();
|
||||
private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
|
||||
private final long mPressToDragTimeout = ViewConfiguration.getLongPressTimeout();
|
||||
private final long mPressToDragTimeout = ViewConfiguration.getLongPressTimeout() / 2;
|
||||
private State mState = State.IDLE;
|
||||
private int mTouchSlop = -1;
|
||||
private BubbleDragAnimator mAnimator;
|
||||
@@ -430,7 +305,7 @@ public class BubbleDragController {
|
||||
/**
|
||||
* Called when the dragging interaction has ended and all the animations have completed
|
||||
*/
|
||||
abstract void onDragEnd(float x, float y);
|
||||
abstract void onDragEnd();
|
||||
|
||||
/**
|
||||
* Called when the dragged bubble is released outside of the dismiss target area and will
|
||||
@@ -560,7 +435,6 @@ public class BubbleDragController {
|
||||
|
||||
private void startDragging(@NonNull View view) {
|
||||
onDragStart();
|
||||
BubbleDragController.this.setIsDragging(true);
|
||||
mActivity.setTaskbarWindowFullscreen(true);
|
||||
mAnimator = new BubbleDragAnimator(view);
|
||||
mAnimator.animateFocused();
|
||||
@@ -577,11 +451,10 @@ public class BubbleDragController {
|
||||
}
|
||||
|
||||
private void stopDragging(@NonNull View view, @NonNull MotionEvent event) {
|
||||
BubbleDragController.this.setIsDragging(false);
|
||||
Runnable onComplete = () -> {
|
||||
mActivity.setTaskbarWindowFullscreen(false);
|
||||
cleanUp(view);
|
||||
onDragEnd(event.getRawX(), event.getRawY());
|
||||
onDragEnd();
|
||||
};
|
||||
|
||||
if (mBubbleDismissController.handleTouchEvent(event)) {
|
||||
@@ -589,17 +462,8 @@ public class BubbleDragController {
|
||||
mAnimator.animateDismiss(mViewInitialPosition, onComplete);
|
||||
} else {
|
||||
onDragRelease();
|
||||
if (BubbleAnythingFlagHelper.enableBubbleToFullscreen()) {
|
||||
if (mBubbleDragZoneChangedListener.isDraggedToFullscreen()) {
|
||||
onComplete.run();
|
||||
} else {
|
||||
mAnimator.animateToRestingState(getRestingPosition(), getCurrentVelocity(),
|
||||
onComplete);
|
||||
}
|
||||
} else {
|
||||
mAnimator.animateToRestingState(getRestingPosition(), getCurrentVelocity(),
|
||||
mAnimator.animateToRestingState(getRestingPosition(), getCurrentVelocity(),
|
||||
onComplete);
|
||||
}
|
||||
}
|
||||
mBubbleDismissController.hideDismissView();
|
||||
}
|
||||
@@ -639,46 +503,4 @@ public class BubbleDragController {
|
||||
return new PointF(mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
|
||||
}
|
||||
}
|
||||
|
||||
private class BubbleDragZoneChangedListener implements DragZoneChangedListener {
|
||||
|
||||
private BubbleBarLocation mBubbleBarLocation = BubbleBarLocation.DEFAULT;
|
||||
private DragZone mDragZone;
|
||||
|
||||
boolean isDraggedToFullscreen() {
|
||||
return mDragZone instanceof DragZone.FullScreen;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitialDragZoneSet(@NonNull DragZone dragZone) {
|
||||
mDragZone = dragZone;
|
||||
if (dragZone instanceof DragZone.Bubble.Left) {
|
||||
mBubbleBarLocation = BubbleBarLocation.LEFT;
|
||||
} else if (dragZone instanceof DragZone.Bubble.Right) {
|
||||
mBubbleBarLocation = BubbleBarLocation.RIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDragZoneChanged(@NonNull DraggedObject draggedObject, @NonNull DragZone from,
|
||||
@NonNull DragZone to) {
|
||||
mDragZone = to;
|
||||
if (to instanceof DragZone.Bubble.Left
|
||||
&& mBubbleBarLocation != BubbleBarLocation.LEFT) {
|
||||
if (draggedObject instanceof DraggedObject.Bubble) {
|
||||
mBubbleBarController.animateBubbleBarLocation(BubbleBarLocation.LEFT);
|
||||
}
|
||||
mBubbleBarLocation = BubbleBarLocation.LEFT;
|
||||
} else if (to instanceof DragZone.Bubble.Right
|
||||
&& mBubbleBarLocation != BubbleBarLocation.RIGHT) {
|
||||
if (draggedObject instanceof DraggedObject.Bubble) {
|
||||
mBubbleBarController.animateBubbleBarLocation(BubbleBarLocation.RIGHT);
|
||||
}
|
||||
mBubbleBarLocation = BubbleBarLocation.RIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDragEnded(@NonNull DragZone zone) {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,9 +27,8 @@ import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.view.updateLayoutParams
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
|
||||
import com.android.wm.shell.shared.bubbles.BaseBubblePinController
|
||||
import com.android.wm.shell.shared.bubbles.BubbleBarLocation
|
||||
import com.android.wm.shell.common.bubbles.BaseBubblePinController
|
||||
import com.android.wm.shell.common.bubbles.BubbleBarLocation
|
||||
|
||||
/** Controller to manage pinning bubble bar to left or right when dragging starts from a bubble */
|
||||
class BubblePinController(
|
||||
|
||||
+81
-118
@@ -21,7 +21,6 @@ import static android.view.View.VISIBLE;
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Rect;
|
||||
@@ -29,51 +28,47 @@ import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.RevealOutlineAnimation;
|
||||
import com.android.launcher3.anim.RoundedRectRevealOutlineProvider;
|
||||
import com.android.launcher3.taskbar.StashedHandleView;
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext;
|
||||
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController;
|
||||
import com.android.launcher3.taskbar.TaskbarControllers;
|
||||
import com.android.launcher3.util.Executors;
|
||||
import com.android.launcher3.util.MultiPropertyFactory;
|
||||
import com.android.launcher3.util.MultiValueAlpha;
|
||||
import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
|
||||
import com.android.wm.shell.common.bubbles.BubbleBarLocation;
|
||||
import com.android.wm.shell.shared.animation.PhysicsAnimator;
|
||||
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
|
||||
import com.android.wm.shell.shared.handles.RegionSamplingHelper;
|
||||
|
||||
/**
|
||||
* Handles properties/data collection, then passes the results to our stashed handle View to render.
|
||||
* Handles properties/data collection, then passes the results to our stashed
|
||||
* handle View to render.
|
||||
*/
|
||||
public class BubbleStashedHandleViewController {
|
||||
|
||||
private final TaskbarActivityContext mActivity;
|
||||
private final StashedHandleView mStashedHandleView;
|
||||
private final MultiValueAlpha mStashedHandleAlpha;
|
||||
private float mTranslationForSwipeY;
|
||||
private float mTranslationForStashY;
|
||||
|
||||
// Initialized in init.
|
||||
private BubbleBarViewController mBarViewController;
|
||||
private BubbleStashController mBubbleStashController;
|
||||
private RegionSamplingHelper mRegionSamplingHelper;
|
||||
// Height of the area for the stash handle. Handle will be drawn in the center of this.
|
||||
// This is also the area where touch is handled on the handle.
|
||||
private int mStashedBubbleBarHeight;
|
||||
private int mBarSize;
|
||||
private int mStashedTaskbarHeight;
|
||||
private int mStashedHandleWidth;
|
||||
private int mStashedHandleHeight;
|
||||
|
||||
// The bounds of the stashed handle in settled state.
|
||||
// The bounds we want to clip to in the settled state when showing the stashed
|
||||
// handle.
|
||||
private final Rect mStashedHandleBounds = new Rect();
|
||||
private float mStashedHandleRadius;
|
||||
|
||||
// When the reveal animation is cancelled, we can assume it's about to create a new animation,
|
||||
// When the reveal animation is cancelled, we can assume it's about to create a
|
||||
// new animation,
|
||||
// which should start off at the same point the cancelled one left off.
|
||||
private float mStartProgressForNextRevealAnim;
|
||||
// Use a nullable boolean to handle initial case where the last animation direction is not known
|
||||
@Nullable
|
||||
private Boolean mWasLastRevealAnimReversed = null;
|
||||
private boolean mWasLastRevealAnimReversed;
|
||||
|
||||
// XXX: if there are more of these maybe do state flags instead
|
||||
private boolean mHiddenForSysui;
|
||||
@@ -85,39 +80,32 @@ public class BubbleStashedHandleViewController {
|
||||
mActivity = activity;
|
||||
mStashedHandleView = stashedHandleView;
|
||||
mStashedHandleAlpha = new MultiValueAlpha(mStashedHandleView, 1);
|
||||
mStashedHandleAlpha.setUpdateVisibility(true);
|
||||
}
|
||||
|
||||
/** Initialize controller. */
|
||||
public void init(BubbleControllers bubbleControllers) {
|
||||
public void init(TaskbarControllers controllers, BubbleControllers bubbleControllers) {
|
||||
mBarViewController = bubbleControllers.bubbleBarViewController;
|
||||
mBubbleStashController = bubbleControllers.bubbleStashController;
|
||||
|
||||
DeviceProfile deviceProfile = mActivity.getDeviceProfile();
|
||||
Resources resources = mActivity.getResources();
|
||||
mStashedHandleHeight = resources.getDimensionPixelSize(
|
||||
R.dimen.bubblebar_stashed_handle_height);
|
||||
mStashedHandleWidth = resources.getDimensionPixelSize(
|
||||
R.dimen.bubblebar_stashed_handle_width);
|
||||
mBarSize = resources.getDimensionPixelSize(R.dimen.bubblebar_size);
|
||||
|
||||
int barSize = resources.getDimensionPixelSize(R.dimen.bubblebar_size);
|
||||
// Use the max translation for bubble bar whether it is on the home screen or in app.
|
||||
// Use values directly from device profile to avoid referencing other bubble controllers
|
||||
// during init flow.
|
||||
int maxTy = Math.max(deviceProfile.hotseatBarBottomSpacePx,
|
||||
deviceProfile.taskbarBottomMargin);
|
||||
// Adjust handle view size to accommodate the handle morphing into the bubble bar
|
||||
mStashedHandleView.getLayoutParams().height = barSize + maxTy;
|
||||
final int bottomMargin = resources.getDimensionPixelSize(
|
||||
R.dimen.transient_taskbar_bottom_margin);
|
||||
mStashedHandleView.getLayoutParams().height = mBarSize + bottomMargin;
|
||||
|
||||
mStashedHandleAlpha.get(0).setValue(0);
|
||||
|
||||
mStashedBubbleBarHeight = resources.getDimensionPixelSize(
|
||||
mStashedTaskbarHeight = resources.getDimensionPixelSize(
|
||||
R.dimen.bubblebar_stashed_size);
|
||||
mStashedHandleView.setOutlineProvider(new ViewOutlineProvider() {
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
mStashedHandleRadius = view.getHeight() / 2f;
|
||||
outline.setRoundRect(mStashedHandleBounds, mStashedHandleRadius);
|
||||
float stashedHandleRadius = view.getHeight() / 2f;
|
||||
outline.setRoundRect(mStashedHandleBounds, stashedHandleRadius);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -132,10 +120,10 @@ public class BubbleStashedHandleViewController {
|
||||
public Rect getSampledRegion(View sampledView) {
|
||||
return mStashedHandleView.getSampledRegion();
|
||||
}
|
||||
}, Executors.MAIN_EXECUTOR, Executors.UI_HELPER_EXECUTOR);
|
||||
}, Executors.UI_HELPER_EXECUTOR);
|
||||
|
||||
mStashedHandleView.addOnLayoutChangeListener((view, i, i1, i2, i3, i4, i5, i6, i7) ->
|
||||
updateBounds(mBarViewController.getBubbleBarLocation()));
|
||||
mStashedHandleView.addOnLayoutChangeListener(
|
||||
(view, i, i1, i2, i3, i4, i5, i6, i7) -> updateBounds(mBarViewController.getBubbleBarLocation()));
|
||||
}
|
||||
|
||||
/** Returns the [PhysicsAnimator] for the stashed handle view. */
|
||||
@@ -144,27 +132,30 @@ public class BubbleStashedHandleViewController {
|
||||
}
|
||||
|
||||
private void updateBounds(BubbleBarLocation bubbleBarLocation) {
|
||||
// As more bubbles get added, the icon bounds become larger. To ensure a consistent
|
||||
// As more bubbles get added, the icon bounds become larger. To ensure a
|
||||
// consistent
|
||||
// handle bar position, we pin it to the edge of the screen.
|
||||
final int stashedCenterY = mStashedHandleView.getHeight() - mStashedBubbleBarHeight / 2;
|
||||
final int stashedCenterX;
|
||||
final int stashedCenterY = mStashedHandleView.getHeight() - mStashedTaskbarHeight / 2;
|
||||
if (bubbleBarLocation.isOnLeft(mStashedHandleView.isLayoutRtl())) {
|
||||
final int left = mBarViewController.getHorizontalMargin();
|
||||
stashedCenterX = left + mStashedHandleWidth / 2;
|
||||
mStashedHandleBounds.set(
|
||||
left,
|
||||
stashedCenterY - mStashedHandleHeight / 2,
|
||||
left + mStashedHandleWidth,
|
||||
stashedCenterY + mStashedHandleHeight / 2);
|
||||
mStashedHandleView.setPivotX(0);
|
||||
} else {
|
||||
final int right =
|
||||
mStashedHandleView.getRight() - mBarViewController.getHorizontalMargin();
|
||||
stashedCenterX = right - mStashedHandleWidth / 2;
|
||||
final int right = mActivity.getDeviceProfile().widthPx - mBarViewController.getHorizontalMargin();
|
||||
mStashedHandleBounds.set(
|
||||
right - mStashedHandleWidth,
|
||||
stashedCenterY - mStashedHandleHeight / 2,
|
||||
right,
|
||||
stashedCenterY + mStashedHandleHeight / 2);
|
||||
mStashedHandleView.setPivotX(mStashedHandleView.getWidth());
|
||||
}
|
||||
mStashedHandleBounds.set(
|
||||
stashedCenterX - mStashedHandleWidth / 2,
|
||||
stashedCenterY - mStashedHandleHeight / 2,
|
||||
stashedCenterX + mStashedHandleWidth / 2,
|
||||
stashedCenterY + mStashedHandleHeight / 2
|
||||
);
|
||||
|
||||
mStashedHandleView.updateSampledRegion(mStashedHandleBounds);
|
||||
mStashedHandleView.setPivotX(stashedCenterX);
|
||||
mStashedHandleView.setPivotY(stashedCenterY);
|
||||
mStashedHandleView.setPivotY(mStashedHandleView.getHeight() - mStashedTaskbarHeight / 2f);
|
||||
}
|
||||
|
||||
public void onDestroy() {
|
||||
@@ -172,13 +163,6 @@ public class BubbleStashedHandleViewController {
|
||||
mRegionSamplingHelper = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the width of the stashed handle.
|
||||
*/
|
||||
public int getStashedWidth() {
|
||||
return mStashedHandleWidth;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the height of the stashed handle.
|
||||
*/
|
||||
@@ -187,14 +171,16 @@ public class BubbleStashedHandleViewController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bounds of the stashed handle view
|
||||
* Returns the height when the bubble bar is unstashed (so the height of the
|
||||
* bubble bar).
|
||||
*/
|
||||
public void getBounds(Rect bounds) {
|
||||
bounds.set(mStashedHandleBounds);
|
||||
public int getUnstashedHeight() {
|
||||
return mBarSize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when system ui state changes. Bubbles don't show when the device is locked.
|
||||
* Called when system ui state changes. Bubbles don't show when the device is
|
||||
* locked.
|
||||
*/
|
||||
public void setHiddenForSysui(boolean hidden) {
|
||||
if (mHiddenForSysui != hidden) {
|
||||
@@ -204,7 +190,8 @@ public class BubbleStashedHandleViewController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the handle should be hidden (or shown) because there are no bubbles
|
||||
* Called when the handle should be hidden (or shown) because there are no
|
||||
* bubbles
|
||||
* (or 1+ bubbles).
|
||||
*/
|
||||
public void setHiddenForBubbles(boolean hidden) {
|
||||
@@ -215,7 +202,8 @@ public class BubbleStashedHandleViewController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the home button is enabled / disabled. Bubbles don't show if home is disabled.
|
||||
* Called when the home button is enabled / disabled. Bubbles don't show if home
|
||||
* is disabled.
|
||||
*/
|
||||
// TODO: is this needed for bubbles?
|
||||
public void setIsHomeButtonDisabled(boolean homeDisabled) {
|
||||
@@ -235,7 +223,8 @@ public class BubbleStashedHandleViewController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when bubble bar is stash state changes so that updates to the stashed handle color
|
||||
* Called when bubble bar is stash state changes so that updates to the stashed
|
||||
* handle color
|
||||
* can be started or stopped.
|
||||
*/
|
||||
public void onIsStashedChanged() {
|
||||
@@ -260,56 +249,37 @@ public class BubbleStashedHandleViewController {
|
||||
* Sets the translation of the stashed handle during the swipe up gesture.
|
||||
*/
|
||||
public void setTranslationYForSwipe(float transY) {
|
||||
mTranslationForSwipeY = transY;
|
||||
updateTranslationY();
|
||||
mStashedHandleView.setTranslationY(transY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the translation of the stashed handle during the spring on stash animation.
|
||||
*/
|
||||
public void setTranslationYForStash(float transY) {
|
||||
mTranslationForStashY = transY;
|
||||
updateTranslationY();
|
||||
}
|
||||
|
||||
/** Sets translation X for stash handle. */
|
||||
public void setTranslationX(float translationX) {
|
||||
mStashedHandleView.setTranslationX(translationX);
|
||||
}
|
||||
|
||||
private void updateTranslationY() {
|
||||
mStashedHandleView.setTranslationY(mTranslationForSwipeY + mTranslationForStashY);
|
||||
}
|
||||
|
||||
/** Returns the translation of the stashed handle. */
|
||||
public float getTranslationY() {
|
||||
return mStashedHandleView.getTranslationY();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by {@link BubbleStashController} to animate the handle when stashing or un stashing.
|
||||
* Used by {@link BubbleStashController} to animate the handle when stashing or
|
||||
* un stashing.
|
||||
*/
|
||||
public MultiPropertyFactory<View> getStashedHandleAlpha() {
|
||||
return mStashedHandleAlpha;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates and returns an Animator that updates the stashed handle shape and size.
|
||||
* When stashed, the shape is a thin rounded pill. When unstashed, the shape morphs into
|
||||
* Creates and returns an Animator that updates the stashed handle shape and
|
||||
* size.
|
||||
* When stashed, the shape is a thin rounded pill. When unstashed, the shape
|
||||
* morphs into
|
||||
* the size of where the bubble bar icons will be.
|
||||
*/
|
||||
public Animator createRevealAnimToIsStashed(boolean isStashed) {
|
||||
Rect bubbleBarBounds = getLocalBubbleBarBounds();
|
||||
Rect bubbleBarBounds = new Rect(mBarViewController.getBubbleBarBounds());
|
||||
|
||||
float bubbleBarRadius = bubbleBarBounds.height() / 2f;
|
||||
// Account for the full visual height of the bubble bar
|
||||
int heightDiff = (mBarSize - bubbleBarBounds.height()) / 2;
|
||||
bubbleBarBounds.top -= heightDiff;
|
||||
bubbleBarBounds.bottom += heightDiff;
|
||||
float stashedHandleRadius = mStashedHandleView.getHeight() / 2f;
|
||||
final RevealOutlineAnimation handleRevealProvider = new RoundedRectRevealOutlineProvider(
|
||||
bubbleBarRadius, mStashedHandleRadius, bubbleBarBounds, mStashedHandleBounds);
|
||||
stashedHandleRadius, stashedHandleRadius, bubbleBarBounds, mStashedHandleBounds);
|
||||
|
||||
boolean isReversed = !isStashed;
|
||||
// We are only changing direction when mWasLastRevealAnimReversed is set at least once
|
||||
boolean changingDirection =
|
||||
mWasLastRevealAnimReversed != null && mWasLastRevealAnimReversed != isReversed;
|
||||
|
||||
boolean changingDirection = mWasLastRevealAnimReversed != isReversed;
|
||||
mWasLastRevealAnimReversed = isReversed;
|
||||
if (changingDirection) {
|
||||
mStartProgressForNextRevealAnim = 1f - mStartProgressForNextRevealAnim;
|
||||
@@ -327,32 +297,25 @@ public class BubbleStashedHandleViewController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get bounds for the bubble bar in the space of the handle view
|
||||
* Checks that the stash handle is visible and that the motion event is within
|
||||
* bounds.
|
||||
*/
|
||||
private Rect getLocalBubbleBarBounds() {
|
||||
// Position the bubble bar bounds to the space of handle view
|
||||
Rect bubbleBarBounds = new Rect(mBarViewController.getBubbleBarBounds());
|
||||
// Start by moving bubble bar bounds to the bottom of handle view
|
||||
int height = bubbleBarBounds.height();
|
||||
bubbleBarBounds.bottom = mStashedHandleView.getHeight();
|
||||
bubbleBarBounds.top = bubbleBarBounds.bottom - height;
|
||||
// Then apply translation that is applied to the bubble bar
|
||||
bubbleBarBounds.offset(0, (int) mBubbleStashController.getBubbleBarTranslationY());
|
||||
return bubbleBarBounds;
|
||||
}
|
||||
|
||||
/** Checks that the stash handle is visible and that the motion event is within bounds. */
|
||||
public boolean isEventOverHandle(MotionEvent ev) {
|
||||
if (mStashedHandleView.getVisibility() != VISIBLE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// the bounds of the handle only include the visible part, so we check that the Y coordinate
|
||||
// is anywhere within the stashed height of bubble bar (same as taskbar stashed height).
|
||||
final int top = mActivity.getDeviceProfile().heightPx - mStashedBubbleBarHeight;
|
||||
final float x = ev.getRawX();
|
||||
return ev.getRawY() >= top && x >= mStashedHandleBounds.left
|
||||
&& x <= mStashedHandleBounds.right;
|
||||
// the bounds of the handle only include the visible part, so we check that the
|
||||
// Y coordinate
|
||||
// is anywhere within the stashed taskbar height.
|
||||
int top = mActivity.getDeviceProfile().heightPx - mStashedTaskbarHeight;
|
||||
|
||||
return (int) ev.getRawY() >= top && containsX((int) ev.getRawX());
|
||||
}
|
||||
|
||||
/** Checks if the given x coordinate is within the stashed handle bounds. */
|
||||
public boolean containsX(int x) {
|
||||
return x >= mStashedHandleBounds.left && x <= mStashedHandleBounds.right;
|
||||
}
|
||||
|
||||
/** Set a bubble bar location */
|
||||
|
||||
@@ -16,30 +16,24 @@
|
||||
package com.android.launcher3.taskbar.bubbles;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.app.Notification;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Outline;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.os.Bundle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.accessibility.AccessibilityNodeInfo;
|
||||
import android.view.View;
|
||||
import android.view.ViewOutlineProvider;
|
||||
import android.widget.ImageView;
|
||||
|
||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.icons.DotRenderer;
|
||||
import com.android.wm.shell.shared.animation.Interpolators;
|
||||
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
|
||||
import com.android.wm.shell.shared.bubbles.BubbleInfo;
|
||||
|
||||
import com.android.launcher3.icons.IconNormalizer;
|
||||
import com.android.wm.shell.animation.Interpolators;
|
||||
import com.patrykmichalik.opto.core.PreferenceExtensionsKt;
|
||||
import app.lawnchair.preferences2.PreferenceManager2;
|
||||
import app.lawnchair.theme.color.ColorOption;
|
||||
@@ -49,18 +43,34 @@ import java.util.EnumSet;
|
||||
// TODO: (b/276978250) This is will be similar to WMShell's BadgedImageView, it'd be nice to share.
|
||||
|
||||
/**
|
||||
* View that displays a bubble icon, along with an app badge on either the left or
|
||||
* View that displays a bubble icon, along with an app badge on either the left
|
||||
* or
|
||||
* right side of the view.
|
||||
*/
|
||||
public class BubbleView extends ConstraintLayout {
|
||||
|
||||
public static final int DEFAULT_PATH_SIZE = 100;
|
||||
/** Duration for animating the scale of the dot and badge. */
|
||||
private static final int SCALE_ANIMATION_DURATION_MS = 200;
|
||||
|
||||
/**
|
||||
* Flags that suppress the visibility of the 'new' dot or the app badge, for one
|
||||
* reason or
|
||||
* another. If any of these flags are set, the dot will not be shown.
|
||||
* If {@link SuppressionFlag#BEHIND_STACK} then the app badge will not be shown.
|
||||
*/
|
||||
enum SuppressionFlag {
|
||||
// TODO: (b/277815200) implement flyout
|
||||
// Suppressed because the flyout is visible - it will morph into the dot via
|
||||
// animation.
|
||||
FLYOUT_VISIBLE,
|
||||
// Suppressed because this bubble is behind others in the collapsed stack.
|
||||
BEHIND_STACK,
|
||||
}
|
||||
|
||||
private final EnumSet<SuppressionFlag> mSuppressionFlags = EnumSet.noneOf(SuppressionFlag.class);
|
||||
|
||||
private final ImageView mBubbleIcon;
|
||||
private final ImageView mAppIcon;
|
||||
private int mBubbleSize;
|
||||
private final int mBubbleSize;
|
||||
|
||||
private float mDragTranslationX;
|
||||
private float mOffsetX;
|
||||
@@ -76,22 +86,12 @@ public class BubbleView extends ConstraintLayout {
|
||||
private float mAnimatingToDotScale;
|
||||
// The current scale value of the dot
|
||||
private float mDotScale;
|
||||
private boolean mDotSuppressedForBubbleUpdate = false;
|
||||
|
||||
// TODO: (b/273310265) handle RTL
|
||||
// Whether the bubbles are positioned on the left or right side of the screen
|
||||
private boolean mOnLeft = false;
|
||||
|
||||
private BubbleBarItem mBubble;
|
||||
private boolean mIsOverflow;
|
||||
|
||||
private Bitmap mIcon;
|
||||
|
||||
@Nullable
|
||||
private Controller mController;
|
||||
|
||||
@Nullable
|
||||
private BubbleBarBubbleIconsFactory mIconFactory = null;
|
||||
|
||||
PreferenceManager2 preferenceManager2;
|
||||
|
||||
@@ -114,6 +114,8 @@ public class BubbleView extends ConstraintLayout {
|
||||
setLayoutDirection(LAYOUT_DIRECTION_LTR);
|
||||
|
||||
LayoutInflater.from(context).inflate(R.layout.bubble_view, this);
|
||||
|
||||
mBubbleSize = getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size);
|
||||
mBubbleIcon = findViewById(R.id.icon_view);
|
||||
mAppIcon = findViewById(R.id.app_icon_view);
|
||||
|
||||
@@ -123,26 +125,24 @@ public class BubbleView extends ConstraintLayout {
|
||||
|
||||
setFocusable(true);
|
||||
setClickable(true);
|
||||
|
||||
// We manage the shadow ourselves when creating the bitmap
|
||||
setOutlineAmbientShadowColor(Color.TRANSPARENT);
|
||||
setOutlineSpotShadowColor(Color.TRANSPARENT);
|
||||
setOutlineProvider(new ViewOutlineProvider() {
|
||||
@Override
|
||||
public void getOutline(View view, Outline outline) {
|
||||
BubbleView.this.getOutline(outline);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updateBubbleSizeAndDotRender() {
|
||||
int updatedBubbleSize = Math.min(getWidth(), getHeight());
|
||||
if (updatedBubbleSize == mBubbleSize) return;
|
||||
mBubbleSize = updatedBubbleSize;
|
||||
mIconFactory = new BubbleBarBubbleIconsFactory(mContext, mBubbleSize);
|
||||
updateBubbleIcon();
|
||||
if (mBubble == null || mBubble instanceof BubbleBarOverflow) return;
|
||||
Path dotPath = ((BubbleBarBubble) mBubble).getDotPath();
|
||||
mDotRenderer = new DotRenderer(mBubbleSize, dotPath, DEFAULT_PATH_SIZE);
|
||||
private void getOutline(Outline outline) {
|
||||
final int normalizedSize = IconNormalizer.getNormalizedCircleSize(mBubbleSize);
|
||||
final int inset = (mBubbleSize - normalizedSize) / 2;
|
||||
outline.setOval(inset, inset, inset + normalizedSize, inset + normalizedSize);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set translation-x while this bubble is being dragged.
|
||||
* Translation applied to the view is a sum of {@code translationX} and offset defined by
|
||||
* Translation applied to the view is a sum of {@code translationX} and offset
|
||||
* defined by
|
||||
* {@link #setOffsetX(float)}.
|
||||
*/
|
||||
public void setDragTranslationX(float translationX) {
|
||||
@@ -159,9 +159,11 @@ public class BubbleView extends ConstraintLayout {
|
||||
|
||||
/**
|
||||
* Set offset on x-axis while dragging.
|
||||
* Used to counter parent translation in order to keep the dragged view at the current position
|
||||
* Used to counter parent translation in order to keep the dragged view at the
|
||||
* current position
|
||||
* on screen.
|
||||
* Translation applied to the view is a sum of {@code offsetX} and translation defined by
|
||||
* Translation applied to the view is a sum of {@code offsetX} and translation
|
||||
* defined by
|
||||
* {@link #setDragTranslationX(float)}
|
||||
*/
|
||||
public void setOffsetX(float offsetX) {
|
||||
@@ -173,12 +175,6 @@ public class BubbleView extends ConstraintLayout {
|
||||
setTranslationX(mDragTranslationX + mOffsetX);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
super.onLayout(changed, left, top, right, bottom);
|
||||
updateBubbleSizeAndDotRender();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatchDraw(Canvas canvas) {
|
||||
super.dispatchDraw(canvas);
|
||||
@@ -189,78 +185,19 @@ public class BubbleView extends ConstraintLayout {
|
||||
|
||||
getDrawingRect(mTempBounds);
|
||||
|
||||
mDrawParams.dotColor = mDotColor;
|
||||
mDrawParams.appColor = mDotColor;
|
||||
mDrawParams.iconBounds = mTempBounds;
|
||||
mDrawParams.leftAlign = mOnLeft;
|
||||
mDrawParams.scale = mDotScale;
|
||||
|
||||
mDotRenderer.draw(canvas, mDrawParams);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
|
||||
super.onInitializeAccessibilityNodeInfoInternal(info);
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_COLLAPSE);
|
||||
if (mBubble instanceof BubbleBarBubble) {
|
||||
info.addAction(AccessibilityNodeInfo.ACTION_DISMISS);
|
||||
}
|
||||
if (mController != null) {
|
||||
if (mController.getBubbleBarLocation().isOnLeft(isLayoutRtl())) {
|
||||
info.addAction(new AccessibilityNodeInfo.AccessibilityAction(R.id.action_move_right,
|
||||
getResources().getString(R.string.bubble_bar_action_move_right)));
|
||||
} else {
|
||||
info.addAction(new AccessibilityNodeInfo.AccessibilityAction(R.id.action_move_left,
|
||||
getResources().getString(R.string.bubble_bar_action_move_left)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
|
||||
if (super.performAccessibilityActionInternal(action, arguments)) {
|
||||
return true;
|
||||
}
|
||||
if (action == AccessibilityNodeInfo.ACTION_COLLAPSE) {
|
||||
if (mController != null) {
|
||||
mController.collapse();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (action == AccessibilityNodeInfo.ACTION_DISMISS) {
|
||||
if (mController != null) {
|
||||
mController.dismiss(this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (action == R.id.action_move_left) {
|
||||
if (mController != null) {
|
||||
mController.updateBubbleBarLocation(BubbleBarLocation.LEFT,
|
||||
BubbleBarLocation.UpdateSource.A11Y_ACTION_BUBBLE);
|
||||
}
|
||||
}
|
||||
if (action == R.id.action_move_right) {
|
||||
if (mController != null) {
|
||||
mController.updateBubbleBarLocation(BubbleBarLocation.RIGHT,
|
||||
BubbleBarLocation.UpdateSource.A11Y_ACTION_BUBBLE);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void setController(@Nullable Controller controller) {
|
||||
mController = controller;
|
||||
mDotRenderer.draw(canvas, mDrawParams, -1);
|
||||
}
|
||||
|
||||
/** Sets the bubble being rendered in this view. */
|
||||
public void setBubble(BubbleBarBubble bubble) {
|
||||
mBubble = bubble;
|
||||
mIcon = bubble.getIcon();
|
||||
updateBubbleIcon();
|
||||
if (bubble.getInfo().showAppBadge()) {
|
||||
mAppIcon.setImageBitmap(bubble.getBadge());
|
||||
} else {
|
||||
mAppIcon.setVisibility(GONE);
|
||||
}
|
||||
mBubbleIcon.setImageBitmap(bubble.getIcon());
|
||||
mAppIcon.setImageBitmap(bubble.getBadge());
|
||||
mDotColor = bubble.getDotColor();
|
||||
ColorOption dotColorOption = PreferenceExtensionsKt.firstBlocking(preferenceManager2.getNotificationDotColor());
|
||||
int dotColor = dotColorOption.getColorPreferenceEntry().getLightColor().invoke(getContext());
|
||||
@@ -281,53 +218,32 @@ public class BubbleView extends ConstraintLayout {
|
||||
setContentDescription(contentDesc);
|
||||
}
|
||||
|
||||
private void updateBubbleIcon() {
|
||||
Bitmap icon = null;
|
||||
if (mIcon != null) {
|
||||
icon = mIcon;
|
||||
if (mIconFactory != null) {
|
||||
BitmapDrawable iconDrawable = new BitmapDrawable(getResources(), icon);
|
||||
icon = mIconFactory.createShadowedIconBitmap(iconDrawable, /* scale = */ 1f);
|
||||
}
|
||||
}
|
||||
mBubbleIcon.setImageBitmap(icon);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets that this bubble represents the overflow. The overflow appears in the list of bubbles
|
||||
* but does not represent app content, instead it shows recent bubbles that couldn't fit into
|
||||
* the list of bubbles. It doesn't show an app icon because it is part of system UI / doesn't
|
||||
* Sets that this bubble represents the overflow. The overflow appears in the
|
||||
* list of bubbles
|
||||
* but does not represent app content, instead it shows recent bubbles that
|
||||
* couldn't fit into
|
||||
* the list of bubbles. It doesn't show an app icon because it is part of system
|
||||
* UI / doesn't
|
||||
* come from an app.
|
||||
*/
|
||||
public void setOverflow(BubbleBarOverflow overflow, Bitmap bitmap) {
|
||||
mBubble = overflow;
|
||||
mIsOverflow = true;
|
||||
mIcon = bitmap;
|
||||
updateBubbleIcon();
|
||||
mBubbleIcon.setImageBitmap(bitmap);
|
||||
mAppIcon.setVisibility(GONE); // Overflow doesn't show the app badge
|
||||
setContentDescription(getResources().getString(R.string.bubble_bar_overflow_description));
|
||||
}
|
||||
|
||||
/** Whether this view represents the overflow button. */
|
||||
public boolean isOverflow() {
|
||||
return mIsOverflow;
|
||||
}
|
||||
|
||||
/** Returns the bubble being rendered in this view. */
|
||||
@Nullable
|
||||
public BubbleBarItem getBubble() {
|
||||
return mBubble;
|
||||
}
|
||||
|
||||
/** Updates the dot visibility if it's not suppressed based on whether it has unseen content. */
|
||||
public void updateDotVisibility(boolean animate) {
|
||||
if (mDotSuppressedForBubbleUpdate) {
|
||||
// if the dot is suppressed for an update, there's nothing to do
|
||||
return;
|
||||
}
|
||||
final float targetScale = hasUnseenContent() ? 1f : 0f;
|
||||
void updateDotVisibility(boolean animate) {
|
||||
final float targetScale = shouldDrawDot() ? 1f : 0f;
|
||||
if (animate) {
|
||||
animateDotScale(targetScale);
|
||||
animateDotScale();
|
||||
} else {
|
||||
mDotScale = targetScale;
|
||||
mAnimatingToDotScale = targetScale;
|
||||
@@ -335,132 +251,77 @@ public class BubbleView extends ConstraintLayout {
|
||||
}
|
||||
}
|
||||
|
||||
void setBadgeScale(float fraction) {
|
||||
if (hasBadge()) {
|
||||
mAppIcon.setScaleX(fraction);
|
||||
mAppIcon.setScaleY(fraction);
|
||||
}
|
||||
}
|
||||
|
||||
void showBadge() {
|
||||
animateBadgeScale(1);
|
||||
}
|
||||
|
||||
void hideBadge() {
|
||||
animateBadgeScale(0);
|
||||
}
|
||||
|
||||
private boolean hasBadge() {
|
||||
return mAppIcon.getVisibility() == VISIBLE;
|
||||
}
|
||||
|
||||
private void animateBadgeScale(float scale) {
|
||||
if (!hasBadge()) {
|
||||
void updateBadgeVisibility() {
|
||||
if (mBubble instanceof BubbleBarOverflow) {
|
||||
// The overflow bubble does not have a badge, so just bail.
|
||||
return;
|
||||
}
|
||||
mAppIcon.clearAnimation();
|
||||
mAppIcon.animate()
|
||||
.setDuration(SCALE_ANIMATION_DURATION_MS)
|
||||
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
|
||||
.scaleX(scale)
|
||||
.scaleY(scale)
|
||||
.start();
|
||||
BubbleBarBubble bubble = (BubbleBarBubble) mBubble;
|
||||
Bitmap appBadgeBitmap = bubble.getBadge();
|
||||
int translationX = mOnLeft
|
||||
? -(bubble.getIcon().getWidth() - appBadgeBitmap.getWidth())
|
||||
: 0;
|
||||
mAppIcon.setTranslationX(translationX);
|
||||
mAppIcon.setVisibility(isBehindStack() ? GONE : VISIBLE);
|
||||
}
|
||||
|
||||
/** Suppresses drawing the dot due to an update for this bubble. */
|
||||
public void suppressDotForBubbleUpdate() {
|
||||
mDotSuppressedForBubbleUpdate = true;
|
||||
setDotScale(0);
|
||||
/** Sets whether this bubble is in the stack & not the first bubble. **/
|
||||
void setBehindStack(boolean behindStack, boolean animate) {
|
||||
if (behindStack) {
|
||||
mSuppressionFlags.add(SuppressionFlag.BEHIND_STACK);
|
||||
} else {
|
||||
mSuppressionFlags.remove(SuppressionFlag.BEHIND_STACK);
|
||||
}
|
||||
updateDotVisibility(animate);
|
||||
updateBadgeVisibility();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsuppresses the dot after the bubble update finished animating.
|
||||
*
|
||||
* @param animate whether or not to animate the dot back in
|
||||
*/
|
||||
public void unsuppressDotForBubbleUpdate(boolean animate) {
|
||||
mDotSuppressedForBubbleUpdate = false;
|
||||
showDotIfNeeded(animate);
|
||||
/** Whether this bubble is in the stack & not the first bubble. **/
|
||||
boolean isBehindStack() {
|
||||
return mSuppressionFlags.contains(SuppressionFlag.BEHIND_STACK);
|
||||
}
|
||||
|
||||
boolean hasUnseenContent() {
|
||||
return mBubble != null
|
||||
&& mBubble instanceof BubbleBarBubble
|
||||
&& !((BubbleBarBubble) mBubble).getInfo().isNotificationSuppressed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to determine if we can skip drawing frames.
|
||||
*
|
||||
* <p>Generally we should draw the dot when it is requested to be shown and there is unseen
|
||||
* content. But when the dot is removed, we still want to draw frames so that it can be scaled
|
||||
* out.
|
||||
*/
|
||||
/** Whether the dot indicating unseen content in a bubble should be shown. */
|
||||
private boolean shouldDrawDot() {
|
||||
// if there's no dot there's nothing to draw, unless the dot was removed and we're in the
|
||||
// middle of removing it
|
||||
return hasUnseenContent() || mDotIsAnimating;
|
||||
boolean bubbleHasUnseenContent = mBubble != null
|
||||
&& mBubble instanceof BubbleBarBubble
|
||||
&& mSuppressionFlags.isEmpty()
|
||||
&& !((BubbleBarBubble) mBubble).getInfo().isNotificationSuppressed();
|
||||
|
||||
// Always render the dot if it's animating, since it could be animating out.
|
||||
// Otherwise, show
|
||||
// it if the bubble wants to show it, and we aren't suppressing it.
|
||||
return bubbleHasUnseenContent || mDotIsAnimating;
|
||||
}
|
||||
|
||||
/** Updates the dot scale to the specified fraction from 0 to 1. */
|
||||
/** How big the dot should be, fraction from 0 to 1. */
|
||||
private void setDotScale(float fraction) {
|
||||
if (!shouldDrawDot()) {
|
||||
return;
|
||||
}
|
||||
mDotScale = fraction;
|
||||
invalidate();
|
||||
}
|
||||
|
||||
void showDotIfNeeded(float fraction) {
|
||||
if (!hasUnseenContent()) {
|
||||
return;
|
||||
}
|
||||
setDotScale(fraction);
|
||||
}
|
||||
|
||||
void showDotIfNeeded(boolean animate) {
|
||||
// only show the dot if we have unseen content and it's not suppressed
|
||||
if (!hasUnseenContent() || mDotSuppressedForBubbleUpdate) {
|
||||
return;
|
||||
}
|
||||
if (animate) {
|
||||
animateDotScale(1f);
|
||||
} else {
|
||||
setDotScale(1f);
|
||||
}
|
||||
}
|
||||
|
||||
void hideDot() {
|
||||
animateDotScale(0f);
|
||||
}
|
||||
|
||||
/** Marks this bubble such that it no longer has unseen content, and hides the dot. */
|
||||
void markSeen() {
|
||||
if (mBubble instanceof BubbleBarBubble bubble) {
|
||||
BubbleInfo info = bubble.getInfo();
|
||||
info.setFlags(
|
||||
info.getFlags() | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION);
|
||||
hideDot();
|
||||
}
|
||||
}
|
||||
|
||||
/** Animates the dot to the given scale. */
|
||||
private void animateDotScale(float toScale) {
|
||||
boolean isDotScaleChanging = Float.compare(mDotScale, toScale) != 0;
|
||||
|
||||
// Don't restart the animation if we're already animating to the given value or if the dot
|
||||
// scale is not changing
|
||||
if ((mDotIsAnimating && mAnimatingToDotScale == toScale) || !isDotScaleChanging) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* Animates the dot to the given scale.
|
||||
*/
|
||||
private void animateDotScale() {
|
||||
float toScale = shouldDrawDot() ? 1f : 0f;
|
||||
mDotIsAnimating = true;
|
||||
|
||||
// Don't restart the animation if we're already animating to the given value.
|
||||
if (mAnimatingToDotScale == toScale || !shouldDrawDot()) {
|
||||
mDotIsAnimating = false;
|
||||
return;
|
||||
}
|
||||
|
||||
mAnimatingToDotScale = toScale;
|
||||
|
||||
final boolean showDot = toScale > 0f;
|
||||
|
||||
// Do NOT wait until after animation ends to setShowDot
|
||||
// to avoid overriding more recent showDot states.
|
||||
clearAnimation();
|
||||
animate()
|
||||
.setDuration(SCALE_ANIMATION_DURATION_MS)
|
||||
.setDuration(200)
|
||||
.setInterpolator(Interpolators.FAST_OUT_SLOW_IN)
|
||||
.setUpdateListener((valueAnimator) -> {
|
||||
float fraction = valueAnimator.getAnimatedFraction();
|
||||
@@ -472,42 +333,9 @@ public class BubbleView extends ConstraintLayout {
|
||||
}).start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the distance from the top left corner of this bubble view to the center of its dot.
|
||||
*/
|
||||
public PointF getDotCenter() {
|
||||
float[] dotPosition =
|
||||
mOnLeft ? mDotRenderer.getLeftDotPosition() : mDotRenderer.getRightDotPosition();
|
||||
getDrawingRect(mTempBounds);
|
||||
float dotCenterX = mTempBounds.width() * dotPosition[0];
|
||||
float dotCenterY = mTempBounds.height() * dotPosition[1];
|
||||
return new PointF(dotCenterX, dotCenterY);
|
||||
}
|
||||
|
||||
/** Returns the dot color. */
|
||||
public int getDotColor() {
|
||||
return mDotColor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
String toString = mBubble != null ? mBubble.getKey() : "null";
|
||||
return "BubbleView{" + toString + "}";
|
||||
}
|
||||
|
||||
/** Interface for BubbleView to communicate with its controller */
|
||||
public interface Controller {
|
||||
/** Get current bubble bar {@link BubbleBarLocation} */
|
||||
BubbleBarLocation getBubbleBarLocation();
|
||||
|
||||
/** This bubble should be dismissed */
|
||||
void dismiss(BubbleView bubble);
|
||||
|
||||
/** Collapse the bubble bar */
|
||||
void collapse();
|
||||
|
||||
/** Request bubble bar location to be updated to the given location */
|
||||
void updateBubbleBarLocation(BubbleBarLocation location,
|
||||
@BubbleBarLocation.UpdateSource int source);
|
||||
}
|
||||
}
|
||||
|
||||
+69
-484
@@ -16,21 +16,14 @@
|
||||
|
||||
package com.android.launcher3.taskbar.bubbles.animation
|
||||
|
||||
import android.animation.Animator
|
||||
import android.animation.AnimatorListenerAdapter
|
||||
import android.animation.ObjectAnimator
|
||||
import android.view.View
|
||||
import android.view.View.VISIBLE
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation
|
||||
import androidx.dynamicanimation.animation.SpringForce
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarBubble
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarParentViewHeightUpdateNotifier
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleBarView
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleStashController
|
||||
import com.android.launcher3.taskbar.bubbles.BubbleView
|
||||
import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutController
|
||||
import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutMessage
|
||||
import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController
|
||||
import com.android.wm.shell.shared.animation.PhysicsAnimator
|
||||
|
||||
/** Handles animations for bubble bar bubbles. */
|
||||
@@ -39,69 +32,26 @@ class BubbleBarViewAnimator
|
||||
constructor(
|
||||
private val bubbleBarView: BubbleBarView,
|
||||
private val bubbleStashController: BubbleStashController,
|
||||
private val bubbleBarFlyoutController: BubbleBarFlyoutController,
|
||||
private val bubbleBarParentViewHeightUpdateNotifier: BubbleBarParentViewHeightUpdateNotifier,
|
||||
private val onExpanded: Runnable,
|
||||
private val onBubbleBarVisible: Runnable,
|
||||
private val scheduler: Scheduler = HandlerScheduler(bubbleBarView),
|
||||
private val scheduler: Scheduler = HandlerScheduler(bubbleBarView)
|
||||
) {
|
||||
|
||||
private var animatingBubble: AnimatingBubble? = null
|
||||
private val bubbleBarBounceDistanceInPx =
|
||||
bubbleBarView.resources.getDimensionPixelSize(R.dimen.bubblebar_bounce_distance)
|
||||
|
||||
fun hasAnimation() = animatingBubble != null
|
||||
|
||||
val isAnimating: Boolean
|
||||
get() {
|
||||
val animatingBubble = animatingBubble ?: return false
|
||||
return animatingBubble.state != AnimatingBubble.State.CREATED
|
||||
}
|
||||
|
||||
private var interceptedHandleAnimator = false
|
||||
|
||||
private companion object {
|
||||
/** The time to show the flyout. */
|
||||
const val FLYOUT_DELAY_MS: Long = 3000
|
||||
const val FLYOUT_DELAY_MS: Long = 2500
|
||||
/** The initial scale Y value that the new bubble is set to before the animation starts. */
|
||||
const val BUBBLE_ANIMATION_INITIAL_SCALE_Y = 0.3f
|
||||
/** The minimum alpha value to make the bubble bar touchable. */
|
||||
const val MIN_ALPHA_FOR_TOUCHABLE = 0.5f
|
||||
/** The duration of the bounce animation. */
|
||||
const val BUBBLE_BAR_BOUNCE_ANIMATION_DURATION_MS = 250L
|
||||
}
|
||||
|
||||
/** Wrapper around the animating bubble with its show and hide animations. */
|
||||
private data class AnimatingBubble(
|
||||
val bubbleView: BubbleView,
|
||||
val showAnimation: Runnable,
|
||||
val hideAnimation: Runnable,
|
||||
val expand: Boolean,
|
||||
val state: State = State.CREATED,
|
||||
) {
|
||||
|
||||
/**
|
||||
* The state of the animation.
|
||||
*
|
||||
* The animation is initially created but will be scheduled later using the [Scheduler].
|
||||
*
|
||||
* The normal uninterrupted cycle is for the bubble notification to animate in, then be in a
|
||||
* transient state and eventually to animate out.
|
||||
*
|
||||
* However different events, such as touch and external signals, may cause the animation to
|
||||
* end earlier.
|
||||
*/
|
||||
enum class State {
|
||||
/** The animation is created but not started yet. */
|
||||
CREATED,
|
||||
/** The bubble notification is animating in. */
|
||||
ANIMATING_IN,
|
||||
/** The bubble notification is now fully showing and waiting to be hidden. */
|
||||
IN,
|
||||
/** The bubble notification is animating out. */
|
||||
ANIMATING_OUT,
|
||||
}
|
||||
}
|
||||
val hideAnimation: Runnable
|
||||
)
|
||||
|
||||
/** An interface for scheduling jobs. */
|
||||
interface Scheduler {
|
||||
@@ -135,34 +85,19 @@ constructor(
|
||||
private val springConfig =
|
||||
PhysicsAnimator.SpringConfig(
|
||||
stiffness = SpringForce.STIFFNESS_LOW,
|
||||
dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY,
|
||||
dampingRatio = SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY
|
||||
)
|
||||
|
||||
private fun cancelAnimationIfPending() {
|
||||
val animatingBubble = animatingBubble ?: return
|
||||
if (animatingBubble.state != AnimatingBubble.State.CREATED) return
|
||||
scheduler.cancel(animatingBubble.showAnimation)
|
||||
scheduler.cancel(animatingBubble.hideAnimation)
|
||||
}
|
||||
|
||||
/** Animates a bubble for the state where the bubble bar is stashed. */
|
||||
fun animateBubbleInForStashed(b: BubbleBarBubble, isExpanding: Boolean) {
|
||||
if (isAnimating) {
|
||||
interruptAndUpdateAnimatingBubble(b.view, isExpanding)
|
||||
return
|
||||
}
|
||||
cancelAnimationIfPending()
|
||||
|
||||
fun animateBubbleInForStashed(b: BubbleBarBubble) {
|
||||
val bubbleView = b.view
|
||||
val animator = PhysicsAnimator.getInstance(bubbleView)
|
||||
if (animator.isRunning()) animator.cancel()
|
||||
// the animation of a new bubble is divided into 2 parts. The first part transforms the
|
||||
// handle to the bubble bar and then shows the flyout. The second part hides the flyout and
|
||||
// transforms the bubble bar back to the handle.
|
||||
// the animation of a new bubble is divided into 2 parts. The first part shows the bubble
|
||||
// and the second part hides it after a delay.
|
||||
val showAnimation = buildHandleToBubbleBarAnimation()
|
||||
val hideAnimation = if (isExpanding) Runnable {} else buildBubbleBarToHandleAnimation()
|
||||
animatingBubble =
|
||||
AnimatingBubble(bubbleView, showAnimation, hideAnimation, expand = isExpanding)
|
||||
val hideAnimation = buildBubbleBarToHandleAnimation()
|
||||
animatingBubble = AnimatingBubble(bubbleView, showAnimation, hideAnimation)
|
||||
scheduler.post(showAnimation)
|
||||
scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
|
||||
}
|
||||
@@ -181,48 +116,40 @@ constructor(
|
||||
* 3. The third part is the overshoot of the spring animation, where we make the bubble fully
|
||||
* visible which helps avoiding further updates when we re-enter the second part.
|
||||
*/
|
||||
private fun buildHandleToBubbleBarAnimation(initialVelocity: Float? = null) = Runnable {
|
||||
moveToState(AnimatingBubble.State.ANIMATING_IN)
|
||||
// prepare the bubble bar for the animation if we're starting fresh
|
||||
if (initialVelocity == null) {
|
||||
bubbleBarView.visibility = VISIBLE
|
||||
bubbleBarView.alpha = 0f
|
||||
bubbleBarView.translationY = 0f
|
||||
bubbleBarView.scaleX = 1f
|
||||
bubbleBarView.scaleY = BUBBLE_ANIMATION_INITIAL_SCALE_Y
|
||||
bubbleBarView.setBackgroundScaleX(1f)
|
||||
bubbleBarView.setBackgroundScaleY(1f)
|
||||
bubbleBarView.relativePivotY = 0.5f
|
||||
}
|
||||
private fun buildHandleToBubbleBarAnimation() = Runnable {
|
||||
// prepare the bubble bar for the animation
|
||||
bubbleBarView.onAnimatingBubbleStarted()
|
||||
bubbleBarView.visibility = VISIBLE
|
||||
bubbleBarView.alpha = 0f
|
||||
bubbleBarView.translationY = 0f
|
||||
bubbleBarView.scaleX = 1f
|
||||
bubbleBarView.scaleY = BUBBLE_ANIMATION_INITIAL_SCALE_Y
|
||||
bubbleBarView.relativePivotY = 0.5f
|
||||
|
||||
// this is the offset between the center of the bubble bar and the center of the stash
|
||||
// handle. when the handle becomes invisible and we start animating in the bubble bar,
|
||||
// the translation y is offset by this value to make the transition from the handle to the
|
||||
// bar smooth.
|
||||
val offset = bubbleStashController.getDiffBetweenHandleAndBarCenters()
|
||||
val stashedHandleTranslationYForAnimation =
|
||||
bubbleStashController.getStashedHandleTranslationForNewBubbleAnimation()
|
||||
val offset = bubbleStashController.diffBetweenHandleAndBarCenters
|
||||
val stashedHandleTranslationY =
|
||||
bubbleStashController.getHandleTranslationY() ?: return@Runnable
|
||||
val translationTracker = TranslationTracker(stashedHandleTranslationY)
|
||||
bubbleStashController.stashedHandleTranslationForNewBubbleAnimation
|
||||
|
||||
// this is the total distance that both the stashed handle and the bubble will be traveling
|
||||
// at the end of the animation the bubble bar will be positioned in the same place when it
|
||||
// shows while we're in an app.
|
||||
val totalTranslationY = bubbleStashController.bubbleBarTranslationYForTaskbar + offset
|
||||
val animator = bubbleStashController.getStashedHandlePhysicsAnimator() ?: return@Runnable
|
||||
val animator = bubbleStashController.stashedHandlePhysicsAnimator
|
||||
animator.setDefaultSpringConfig(springConfig)
|
||||
animator.spring(DynamicAnimation.TRANSLATION_Y, totalTranslationY, initialVelocity ?: 0f)
|
||||
animator.spring(DynamicAnimation.TRANSLATION_Y, totalTranslationY)
|
||||
animator.addUpdateListener { handle, values ->
|
||||
val ty = values[DynamicAnimation.TRANSLATION_Y]?.value ?: return@addUpdateListener
|
||||
if (animatingBubble == null) return@addUpdateListener
|
||||
when {
|
||||
ty >= stashedHandleTranslationYForAnimation -> {
|
||||
ty >= stashedHandleTranslationY -> {
|
||||
// we're in the first leg of the animation. only animate the handle. the bubble
|
||||
// bar remains hidden during this part of the animation
|
||||
|
||||
// map the path [0, stashedHandleTranslationY] to [0,1]
|
||||
val fraction = ty / stashedHandleTranslationYForAnimation
|
||||
val fraction = ty / stashedHandleTranslationY
|
||||
handle.alpha = 1 - fraction
|
||||
}
|
||||
ty >= totalTranslationY -> {
|
||||
@@ -236,8 +163,8 @@ constructor(
|
||||
if (bubbleBarView.alpha != 1f) {
|
||||
// map the path [stashedHandleTranslationY, totalTranslationY] to [0, 1]
|
||||
val fraction =
|
||||
(ty - stashedHandleTranslationYForAnimation) /
|
||||
(totalTranslationY - stashedHandleTranslationYForAnimation)
|
||||
(ty - stashedHandleTranslationY) /
|
||||
(totalTranslationY - stashedHandleTranslationY)
|
||||
bubbleBarView.alpha = fraction
|
||||
bubbleBarView.scaleY =
|
||||
BUBBLE_ANIMATION_INITIAL_SCALE_Y +
|
||||
@@ -257,17 +184,18 @@ constructor(
|
||||
bubbleStashController.updateTaskbarTouchRegion()
|
||||
}
|
||||
}
|
||||
translationTracker.updateTyAndExpandIfNeeded(ty)
|
||||
}
|
||||
animator.addEndListener { _, _, _, canceled, _, _, _ ->
|
||||
// if the show animation was canceled, also cancel the hide animation. this is typically
|
||||
// canceled in this class, but could potentially be canceled elsewhere.
|
||||
if (canceled || animatingBubble?.expand == true) {
|
||||
cancelHideAnimation()
|
||||
if (canceled) {
|
||||
val hideAnimation = animatingBubble?.hideAnimation ?: return@addEndListener
|
||||
scheduler.cancel(hideAnimation)
|
||||
animatingBubble = null
|
||||
bubbleBarView.onAnimatingBubbleCompleted()
|
||||
bubbleBarView.relativePivotY = 1f
|
||||
return@addEndListener
|
||||
}
|
||||
setupAndShowFlyout()
|
||||
|
||||
// the bubble bar is now fully settled in. update taskbar touch region so it's touchable
|
||||
bubbleStashController.updateTaskbarTouchRegion()
|
||||
}
|
||||
@@ -290,14 +218,13 @@ constructor(
|
||||
*/
|
||||
private fun buildBubbleBarToHandleAnimation() = Runnable {
|
||||
if (animatingBubble == null) return@Runnable
|
||||
moveToState(AnimatingBubble.State.ANIMATING_OUT)
|
||||
val offset = bubbleStashController.getDiffBetweenHandleAndBarCenters()
|
||||
val offset = bubbleStashController.diffBetweenHandleAndBarCenters
|
||||
val stashedHandleTranslationY =
|
||||
bubbleStashController.getStashedHandleTranslationForNewBubbleAnimation()
|
||||
bubbleStashController.stashedHandleTranslationForNewBubbleAnimation
|
||||
// this is the total distance that both the stashed handle and the bar will be traveling
|
||||
val totalTranslationY = bubbleStashController.bubbleBarTranslationYForTaskbar + offset
|
||||
bubbleStashController.setHandleTranslationY(totalTranslationY)
|
||||
val animator = bubbleStashController.getStashedHandlePhysicsAnimator() ?: return@Runnable
|
||||
val animator = bubbleStashController.stashedHandlePhysicsAnimator
|
||||
animator.setDefaultSpringConfig(springConfig)
|
||||
animator.spring(DynamicAnimation.TRANSLATION_Y, 0f)
|
||||
animator.addUpdateListener { handle, values ->
|
||||
@@ -333,430 +260,88 @@ constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
animator.addEndListener { _, _, _, canceled, _, finalVelocity, _ ->
|
||||
// PhysicsAnimator calls the end listeners when the animation is replaced with a new one
|
||||
// if we're not in ANIMATING_OUT state, then this animation never started and we should
|
||||
// return
|
||||
if (animatingBubble?.state != AnimatingBubble.State.ANIMATING_OUT) return@addEndListener
|
||||
if (interceptedHandleAnimator) {
|
||||
interceptedHandleAnimator = false
|
||||
// post this to give a PhysicsAnimator a chance to clean up its internal listeners.
|
||||
// otherwise this end listener will be called as soon as we create a new spring
|
||||
// animation
|
||||
scheduler.post(buildHandleToBubbleBarAnimation(initialVelocity = finalVelocity))
|
||||
return@addEndListener
|
||||
}
|
||||
clearAnimatingBubble()
|
||||
animator.addEndListener { _, _, _, canceled, _, _, _ ->
|
||||
animatingBubble = null
|
||||
if (!canceled) bubbleStashController.stashBubbleBarImmediate()
|
||||
bubbleBarView.onAnimatingBubbleCompleted()
|
||||
bubbleBarView.relativePivotY = 1f
|
||||
bubbleBarView.scaleY = 1f
|
||||
bubbleStashController.updateTaskbarTouchRegion()
|
||||
}
|
||||
|
||||
val bubble = animatingBubble?.bubbleView?.bubble as? BubbleBarBubble
|
||||
val flyout = bubble?.flyoutMessage
|
||||
if (flyout != null) {
|
||||
bubbleBarFlyoutController.collapseFlyout {
|
||||
onFlyoutRemoved()
|
||||
animator.start()
|
||||
}
|
||||
} else {
|
||||
animator.start()
|
||||
}
|
||||
animator.start()
|
||||
}
|
||||
|
||||
/** Animates to the initial state of the bubble bar, when there are no previous bubbles. */
|
||||
fun animateToInitialState(
|
||||
b: BubbleBarBubble,
|
||||
isInApp: Boolean,
|
||||
isExpanding: Boolean,
|
||||
isDragging: Boolean = false,
|
||||
) {
|
||||
fun animateToInitialState(b: BubbleBarBubble, isInApp: Boolean, isExpanding: Boolean) {
|
||||
val bubbleView = b.view
|
||||
val animator = PhysicsAnimator.getInstance(bubbleView)
|
||||
if (animator.isRunning()) animator.cancel()
|
||||
// the animation of a new bubble is divided into 2 parts. The first part slides in the
|
||||
// bubble bar and shows the flyout. The second part hides the flyout and transforms the
|
||||
// bubble bar to the handle if we're in an app.
|
||||
val showAnimation = buildBubbleBarSpringInAnimation()
|
||||
// the animation of a new bubble is divided into 2 parts. The first part shows the bubble
|
||||
// and the second part hides it after a delay if we are in an app.
|
||||
val showAnimation = buildBubbleBarBounceAnimation()
|
||||
val hideAnimation =
|
||||
if (isInApp && !isExpanding && !isDragging) {
|
||||
if (isInApp && !isExpanding) {
|
||||
buildBubbleBarToHandleAnimation()
|
||||
} else {
|
||||
// in this case the bubble bar remains visible so not much to do. once we implement
|
||||
// the flyout we'll update this runnable to hide it.
|
||||
Runnable {
|
||||
collapseFlyoutAndUpdateState()
|
||||
if (isDragging) return@Runnable
|
||||
animatingBubble = null
|
||||
bubbleStashController.showBubbleBarImmediate()
|
||||
bubbleBarView.onAnimatingBubbleCompleted()
|
||||
bubbleStashController.updateTaskbarTouchRegion()
|
||||
}
|
||||
}
|
||||
animatingBubble =
|
||||
AnimatingBubble(bubbleView, showAnimation, hideAnimation, expand = isExpanding)
|
||||
animatingBubble = AnimatingBubble(bubbleView, showAnimation, hideAnimation)
|
||||
scheduler.post(showAnimation)
|
||||
scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
|
||||
}
|
||||
|
||||
private fun buildBubbleBarSpringInAnimation() = Runnable {
|
||||
moveToState(AnimatingBubble.State.ANIMATING_IN)
|
||||
private fun buildBubbleBarBounceAnimation() = Runnable {
|
||||
// prepare the bubble bar for the animation
|
||||
bubbleBarView.onAnimatingBubbleStarted()
|
||||
bubbleBarView.translationY = bubbleBarView.height.toFloat()
|
||||
bubbleBarView.visibility = VISIBLE
|
||||
onBubbleBarVisible.run()
|
||||
bubbleBarView.alpha = 1f
|
||||
bubbleBarView.scaleX = 1f
|
||||
bubbleBarView.scaleY = 1f
|
||||
bubbleBarView.setBackgroundScaleX(1f)
|
||||
bubbleBarView.setBackgroundScaleY(1f)
|
||||
|
||||
val translationTracker = TranslationTracker(bubbleBarView.translationY)
|
||||
|
||||
val animator = PhysicsAnimator.getInstance(bubbleBarView)
|
||||
animator.setDefaultSpringConfig(springConfig)
|
||||
animator.spring(DynamicAnimation.TRANSLATION_Y, bubbleStashController.bubbleBarTranslationY)
|
||||
animator.addUpdateListener { _, values ->
|
||||
val ty = values[DynamicAnimation.TRANSLATION_Y]?.value ?: return@addUpdateListener
|
||||
translationTracker.updateTyAndExpandIfNeeded(ty)
|
||||
bubbleStashController.updateTaskbarTouchRegion()
|
||||
}
|
||||
animator.addUpdateListener { _, _ -> bubbleStashController.updateTaskbarTouchRegion() }
|
||||
animator.addEndListener { _, _, _, _, _, _, _ ->
|
||||
if (animatingBubble?.expand == true) {
|
||||
cancelHideAnimation()
|
||||
} else {
|
||||
setupAndShowFlyout()
|
||||
}
|
||||
// the bubble bar is now fully settled in. update taskbar touch region so it's touchable
|
||||
bubbleStashController.updateTaskbarTouchRegion()
|
||||
}
|
||||
animator.start()
|
||||
}
|
||||
|
||||
fun animateBubbleBarForCollapsed(b: BubbleBarBubble, isExpanding: Boolean) {
|
||||
if (isAnimating) {
|
||||
interruptAndUpdateAnimatingBubble(b.view, isExpanding)
|
||||
return
|
||||
}
|
||||
cancelAnimationIfPending()
|
||||
|
||||
val bubbleView = b.view
|
||||
val animator = PhysicsAnimator.getInstance(bubbleView)
|
||||
if (animator.isRunning()) animator.cancel()
|
||||
// first bounce the bubble bar and show the flyout. Then hide the flyout.
|
||||
val showAnimation = buildBubbleBarBounceAnimation()
|
||||
val hideAnimation = Runnable {
|
||||
collapseFlyoutAndUpdateState()
|
||||
bubbleStashController.showBubbleBarImmediate()
|
||||
bubbleStashController.updateTaskbarTouchRegion()
|
||||
}
|
||||
animatingBubble =
|
||||
AnimatingBubble(bubbleView, showAnimation, hideAnimation, expand = isExpanding)
|
||||
scheduler.post(showAnimation)
|
||||
scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
|
||||
}
|
||||
|
||||
private fun collapseFlyoutAndUpdateState() {
|
||||
moveToState(AnimatingBubble.State.ANIMATING_OUT)
|
||||
bubbleBarFlyoutController.collapseFlyout {
|
||||
onFlyoutRemoved()
|
||||
clearAnimatingBubble()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The bubble bar animation when it is collapsed is divided into 2 chained animations. The first
|
||||
* animation is a regular accelerate animation that moves the bubble bar upwards. When it ends
|
||||
* the bubble bar moves back to its initial position with a spring animation.
|
||||
*/
|
||||
private fun buildBubbleBarBounceAnimation() = Runnable {
|
||||
moveToState(AnimatingBubble.State.ANIMATING_IN)
|
||||
val ty = bubbleStashController.bubbleBarTranslationY
|
||||
|
||||
val springBackAnimation = PhysicsAnimator.getInstance(bubbleBarView)
|
||||
springBackAnimation.setDefaultSpringConfig(springConfig)
|
||||
springBackAnimation.spring(DynamicAnimation.TRANSLATION_Y, ty)
|
||||
springBackAnimation.addEndListener { _, _, _, _, _, _, _ ->
|
||||
if (animatingBubble?.expand == true) {
|
||||
expandBubbleBar()
|
||||
cancelHideAnimation()
|
||||
} else {
|
||||
setupAndShowFlyout()
|
||||
}
|
||||
}
|
||||
|
||||
// animate the bubble bar up and start the spring back down animation when it ends.
|
||||
ObjectAnimator.ofFloat(bubbleBarView, View.TRANSLATION_Y, ty - bubbleBarBounceDistanceInPx)
|
||||
.withDuration(BUBBLE_BAR_BOUNCE_ANIMATION_DURATION_MS)
|
||||
.withEndAction {
|
||||
springBackAnimation.start()
|
||||
if (animatingBubble?.expand == true) expandBubbleBar()
|
||||
}
|
||||
.start()
|
||||
}
|
||||
|
||||
private fun setupAndShowFlyout() {
|
||||
val bubbleView = animatingBubble?.bubbleView
|
||||
val bubble = bubbleView?.bubble as? BubbleBarBubble
|
||||
val flyout = bubble?.flyoutMessage
|
||||
if (flyout != null) {
|
||||
bubbleBarFlyoutController.setUpAndShowFlyout(
|
||||
BubbleBarFlyoutMessage(flyout.icon, flyout.title, flyout.message),
|
||||
onInit = { bubbleView.suppressDotForBubbleUpdate() },
|
||||
onEnd = {
|
||||
moveToState(AnimatingBubble.State.IN)
|
||||
bubbleStashController.updateTaskbarTouchRegion()
|
||||
},
|
||||
)
|
||||
} else {
|
||||
moveToState(AnimatingBubble.State.IN)
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelFlyout() {
|
||||
animatingBubble?.bubbleView?.unsuppressDotForBubbleUpdate(/* animate= */ true)
|
||||
bubbleBarFlyoutController.cancelFlyout { bubbleStashController.updateTaskbarTouchRegion() }
|
||||
}
|
||||
|
||||
private fun onFlyoutRemoved() {
|
||||
animatingBubble?.bubbleView?.unsuppressDotForBubbleUpdate(/* animate= */ false)
|
||||
bubbleStashController.updateTaskbarTouchRegion()
|
||||
}
|
||||
|
||||
/** Interrupts the animation due to touching the bubble bar or flyout. */
|
||||
fun interruptForTouch() {
|
||||
animatingBubble?.hideAnimation?.let { scheduler.cancel(it) }
|
||||
/** Handles touching the animating bubble bar. */
|
||||
fun onBubbleBarTouchedWhileAnimating() {
|
||||
PhysicsAnimator.getInstance(bubbleBarView).cancelIfRunning()
|
||||
bubbleStashController.getStashedHandlePhysicsAnimator().cancelIfRunning()
|
||||
cancelFlyout()
|
||||
resetBubbleBarPropertiesOnInterrupt()
|
||||
clearAnimatingBubble()
|
||||
bubbleStashController.stashedHandlePhysicsAnimator.cancelIfRunning()
|
||||
val hideAnimation = animatingBubble?.hideAnimation ?: return
|
||||
scheduler.cancel(hideAnimation)
|
||||
bubbleBarView.onAnimatingBubbleCompleted()
|
||||
bubbleBarView.relativePivotY = 1f
|
||||
animatingBubble = null
|
||||
}
|
||||
|
||||
/** Notifies the animator that the taskbar area was touched during an animation. */
|
||||
fun onStashStateChangingWhileAnimating() {
|
||||
animatingBubble?.hideAnimation?.let { scheduler.cancel(it) }
|
||||
cancelFlyout()
|
||||
clearAnimatingBubble()
|
||||
bubbleStashController.getStashedHandlePhysicsAnimator().cancelIfRunning()
|
||||
resetBubbleBarPropertiesOnInterrupt()
|
||||
bubbleStashController.onNewBubbleAnimationInterrupted(
|
||||
/* isStashed= */ bubbleStashController.isStashed,
|
||||
bubbleBarView.translationY,
|
||||
)
|
||||
}
|
||||
|
||||
/** Interrupts the animation due to the IME becoming visible. */
|
||||
fun interruptForIme() {
|
||||
cancelFlyout()
|
||||
val hideAnimation = animatingBubble?.hideAnimation ?: return
|
||||
scheduler.cancel(hideAnimation)
|
||||
animatingBubble = null
|
||||
bubbleStashController.getStashedHandlePhysicsAnimator().cancelIfRunning()
|
||||
resetBubbleBarPropertiesOnInterrupt()
|
||||
// stash the bubble bar since the IME is now visible
|
||||
bubbleStashController.stashedHandlePhysicsAnimator.cancel()
|
||||
bubbleBarView.onAnimatingBubbleCompleted()
|
||||
bubbleBarView.relativePivotY = 1f
|
||||
bubbleStashController.onNewBubbleAnimationInterrupted(
|
||||
/* isStashed= */ true,
|
||||
bubbleBarView.translationY,
|
||||
/* isStashed= */ bubbleBarView.alpha == 0f,
|
||||
bubbleBarView.translationY
|
||||
)
|
||||
}
|
||||
|
||||
fun expandedWhileAnimating() {
|
||||
val animatingBubble = animatingBubble ?: return
|
||||
this.animatingBubble = animatingBubble.copy(expand = true)
|
||||
// if we're fully in and waiting to hide, cancel the hide animation and clean up
|
||||
if (animatingBubble.state == AnimatingBubble.State.IN) {
|
||||
cancelFlyout()
|
||||
expandBubbleBar()
|
||||
cancelHideAnimation()
|
||||
}
|
||||
}
|
||||
|
||||
private fun interruptAndUpdateAnimatingBubble(bubbleView: BubbleView, isExpanding: Boolean) {
|
||||
val animatingBubble = animatingBubble ?: return
|
||||
when (animatingBubble.state) {
|
||||
AnimatingBubble.State.CREATED -> {} // nothing to do since the animation hasn't started
|
||||
AnimatingBubble.State.ANIMATING_IN ->
|
||||
updateAnimationWhileAnimatingIn(animatingBubble, bubbleView, isExpanding)
|
||||
AnimatingBubble.State.IN ->
|
||||
updateAnimationWhileIn(animatingBubble, bubbleView, isExpanding)
|
||||
AnimatingBubble.State.ANIMATING_OUT ->
|
||||
updateAnimationWhileAnimatingOut(animatingBubble, bubbleView, isExpanding)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAnimationWhileAnimatingIn(
|
||||
animatingBubble: AnimatingBubble,
|
||||
bubbleView: BubbleView,
|
||||
isExpanding: Boolean,
|
||||
) {
|
||||
this.animatingBubble = animatingBubble.copy(bubbleView = bubbleView, expand = isExpanding)
|
||||
if (!bubbleBarFlyoutController.hasFlyout()) {
|
||||
// if the flyout does not yet exist, then we're only animating the bubble bar.
|
||||
// the animating bubble has been updated, so the when the flyout expands it will
|
||||
// show the right message. we only need to update the dot visibility.
|
||||
bubbleView.updateDotVisibility(/* animate= */ !bubbleStashController.isStashed)
|
||||
return
|
||||
}
|
||||
|
||||
val bubble = bubbleView.bubble as? BubbleBarBubble
|
||||
val flyout = bubble?.flyoutMessage
|
||||
if (flyout != null) {
|
||||
// the flyout is currently expanding and we need to update it with new data
|
||||
bubbleView.suppressDotForBubbleUpdate()
|
||||
bubbleBarFlyoutController.updateFlyoutWhileExpanding(flyout)
|
||||
} else {
|
||||
// the flyout is expanding but we don't have new flyout data to update it with,
|
||||
// so cancel the expanding flyout.
|
||||
cancelFlyout()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAnimationWhileIn(
|
||||
animatingBubble: AnimatingBubble,
|
||||
bubbleView: BubbleView,
|
||||
isExpanding: Boolean,
|
||||
) {
|
||||
// unsuppress the current bubble because we are about to hide its flyout
|
||||
animatingBubble.bubbleView.unsuppressDotForBubbleUpdate(/* animate= */ false)
|
||||
this.animatingBubble = animatingBubble.copy(bubbleView = bubbleView, expand = isExpanding)
|
||||
|
||||
// we're currently idle, waiting for the hide animation to start. update the flyout
|
||||
// data and reschedule the hide animation to run later to give the user a chance to
|
||||
// see the new flyout.
|
||||
val hideAnimation = animatingBubble.hideAnimation
|
||||
scheduler.cancel(hideAnimation)
|
||||
scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
|
||||
|
||||
val bubble = bubbleView.bubble as? BubbleBarBubble
|
||||
val flyout = bubble?.flyoutMessage
|
||||
if (flyout != null) {
|
||||
bubbleView.suppressDotForBubbleUpdate()
|
||||
bubbleBarFlyoutController.updateFlyoutFullyExpanded(flyout) {
|
||||
bubbleStashController.updateTaskbarTouchRegion()
|
||||
}
|
||||
} else {
|
||||
cancelFlyout()
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateAnimationWhileAnimatingOut(
|
||||
animatingBubble: AnimatingBubble,
|
||||
bubbleView: BubbleView,
|
||||
isExpanding: Boolean,
|
||||
) {
|
||||
// unsuppress the current bubble because we are about to hide its flyout
|
||||
animatingBubble.bubbleView.unsuppressDotForBubbleUpdate(/* animate= */ false)
|
||||
this.animatingBubble = animatingBubble.copy(bubbleView = bubbleView, expand = isExpanding)
|
||||
|
||||
// the hide animation already started so it can't be canceled, just post it again
|
||||
val hideAnimation = animatingBubble.hideAnimation
|
||||
scheduler.postDelayed(FLYOUT_DELAY_MS, hideAnimation)
|
||||
|
||||
val bubble = bubbleView.bubble as? BubbleBarBubble
|
||||
val flyout = bubble?.flyoutMessage
|
||||
if (bubbleBarFlyoutController.hasFlyout()) {
|
||||
// the flyout is collapsing. update it with the new flyout
|
||||
if (flyout != null) {
|
||||
moveToState(AnimatingBubble.State.ANIMATING_IN)
|
||||
bubbleView.suppressDotForBubbleUpdate()
|
||||
bubbleBarFlyoutController.updateFlyoutWhileCollapsing(flyout) {
|
||||
moveToState(AnimatingBubble.State.IN)
|
||||
bubbleStashController.updateTaskbarTouchRegion()
|
||||
}
|
||||
} else {
|
||||
cancelFlyout()
|
||||
moveToState(AnimatingBubble.State.IN)
|
||||
}
|
||||
} else {
|
||||
// the flyout is already gone. if we're animating the handle cancel it. the
|
||||
// animation itself can handle morphing back into the bubble bar and restarting
|
||||
// and show the flyout.
|
||||
val handleAnimator = bubbleStashController.getStashedHandlePhysicsAnimator()
|
||||
if (handleAnimator != null && handleAnimator.isRunning()) {
|
||||
interceptedHandleAnimator = true
|
||||
handleAnimator.cancel()
|
||||
}
|
||||
|
||||
// if we're not animating the handle, then the hide animation simply hides the
|
||||
// flyout, but if the flyout is gone then the animation has ended.
|
||||
}
|
||||
}
|
||||
|
||||
private fun cancelHideAnimation() {
|
||||
val hideAnimation = animatingBubble?.hideAnimation ?: return
|
||||
scheduler.cancel(hideAnimation)
|
||||
clearAnimatingBubble()
|
||||
bubbleBarView.relativePivotY = 1f
|
||||
bubbleStashController.showBubbleBarImmediate()
|
||||
}
|
||||
|
||||
private fun resetBubbleBarPropertiesOnInterrupt() {
|
||||
bubbleBarView.relativePivotY = 1f
|
||||
bubbleBarView.scaleX = 1f
|
||||
bubbleBarView.scaleY = 1f
|
||||
}
|
||||
|
||||
private fun <T> PhysicsAnimator<T>?.cancelIfRunning() {
|
||||
if (this?.isRunning() == true) cancel()
|
||||
}
|
||||
|
||||
private fun ObjectAnimator.withDuration(duration: Long): ObjectAnimator {
|
||||
setDuration(duration)
|
||||
return this
|
||||
}
|
||||
|
||||
private fun ObjectAnimator.withEndAction(endAction: () -> Unit): ObjectAnimator {
|
||||
addListener(
|
||||
object : AnimatorListenerAdapter() {
|
||||
override fun onAnimationEnd(animation: Animator) {
|
||||
endAction()
|
||||
}
|
||||
}
|
||||
)
|
||||
return this
|
||||
}
|
||||
|
||||
private fun moveToState(state: AnimatingBubble.State) {
|
||||
val animatingBubble = this.animatingBubble ?: return
|
||||
this.animatingBubble = animatingBubble.copy(state = state)
|
||||
if (state == AnimatingBubble.State.ANIMATING_IN) {
|
||||
bubbleBarParentViewHeightUpdateNotifier.updateTopBoundary()
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearAnimatingBubble() {
|
||||
animatingBubble = null
|
||||
bubbleBarParentViewHeightUpdateNotifier.updateTopBoundary()
|
||||
}
|
||||
|
||||
private fun expandBubbleBar() {
|
||||
bubbleBarView.isExpanded = true
|
||||
onExpanded.run()
|
||||
}
|
||||
|
||||
/**
|
||||
* Tracks the translation Y of the bubble bar during the animation. When the bubble bar expands
|
||||
* as part of the animation, the expansion should start after the bubble bar reaches the peak
|
||||
* position.
|
||||
*/
|
||||
private inner class TranslationTracker(initialTy: Float) {
|
||||
private var previousTy = initialTy
|
||||
private var startedExpanding = false
|
||||
private var reachedPeak = false
|
||||
|
||||
fun updateTyAndExpandIfNeeded(ty: Float) {
|
||||
if (!reachedPeak) {
|
||||
// the bubble bar is positioned at the bottom of the screen and moves up using
|
||||
// negative ty values. the peak is reached the first time we see a value that is
|
||||
// greater than the previous.
|
||||
if (ty > previousTy) {
|
||||
reachedPeak = true
|
||||
}
|
||||
}
|
||||
val expand = animatingBubble?.expand ?: false
|
||||
if (reachedPeak && expand && !startedExpanding) {
|
||||
expandBubbleBar()
|
||||
startedExpanding = true
|
||||
}
|
||||
previousTy = ty
|
||||
}
|
||||
private fun <T> PhysicsAnimator<T>.cancelIfRunning() {
|
||||
if (isRunning()) cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,13 +13,15 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.launcher3.taskbar.customization
|
||||
|
||||
import androidx.annotation.Dimension
|
||||
|
||||
/**
|
||||
* Interface to be implemented by all taskbar container to expose [spaceNeeded] for each container.
|
||||
*/
|
||||
interface TaskbarContainer {
|
||||
@get:Dimension(unit = Dimension.DP) val spaceNeeded: Int
|
||||
/** Enums for all feature container that taskbar supports. */
|
||||
enum class TaskbarContainer {
|
||||
ALL_APPS,
|
||||
DIVIDER,
|
||||
APP_ICONS,
|
||||
RECENTS,
|
||||
NAV_BUTTONS,
|
||||
BUBBLES,
|
||||
}
|
||||
|
||||
+12
-31
@@ -16,48 +16,29 @@
|
||||
|
||||
package com.android.launcher3.taskbar.customization
|
||||
|
||||
import com.android.launcher3.Flags.enableRecentsInTaskbar
|
||||
import com.android.launcher3.config.FeatureFlags.enableTaskbarPinning
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext
|
||||
import com.android.launcher3.taskbar.TaskbarControllers
|
||||
import com.android.launcher3.taskbar.TaskbarRecentAppsController
|
||||
import com.android.launcher3.util.DisplayController
|
||||
|
||||
/** Evaluates all the features taskbar can have. */
|
||||
class TaskbarFeatureEvaluator
|
||||
private constructor(private val taskbarActivityContext: TaskbarActivityContext) {
|
||||
class TaskbarFeatureEvaluator(
|
||||
private val taskbarActivityContext: TaskbarActivityContext,
|
||||
private val taskbarControllers: TaskbarControllers,
|
||||
) {
|
||||
|
||||
val hasAllApps = true
|
||||
val hasAppIcons = true
|
||||
val hasBubbles = false
|
||||
val hasNavButtons = taskbarActivityContext.isThreeButtonNav
|
||||
|
||||
val isRecentsEnabled: Boolean
|
||||
get() = enableRecentsInTaskbar()
|
||||
val hasRecents: Boolean
|
||||
get() = taskbarControllers.taskbarRecentAppsController.isEnabled
|
||||
|
||||
val hasDivider: Boolean
|
||||
get() = enableTaskbarPinning() || isRecentsEnabled
|
||||
get() = enableTaskbarPinning() || hasRecents
|
||||
|
||||
val isTransient: Boolean
|
||||
get() = taskbarActivityContext.isTransientTaskbar
|
||||
|
||||
val isLandscape: Boolean
|
||||
get() = taskbarActivityContext.deviceProfile.isLandscape
|
||||
|
||||
val supportsPinningPopup: Boolean
|
||||
get() = !hasNavButtons
|
||||
|
||||
fun onDestroy() {
|
||||
taskbarFeatureEvaluator = null
|
||||
}
|
||||
|
||||
companion object {
|
||||
@Volatile private var taskbarFeatureEvaluator: TaskbarFeatureEvaluator? = null
|
||||
|
||||
@JvmStatic
|
||||
fun getInstance(taskbarActivityContext: TaskbarActivityContext): TaskbarFeatureEvaluator {
|
||||
synchronized(this) {
|
||||
if (taskbarFeatureEvaluator == null) {
|
||||
taskbarFeatureEvaluator = TaskbarFeatureEvaluator(taskbarActivityContext)
|
||||
}
|
||||
return taskbarFeatureEvaluator!!
|
||||
}
|
||||
}
|
||||
}
|
||||
get() = DisplayController.isTransientTaskbar(taskbarActivityContext)
|
||||
}
|
||||
|
||||
@@ -19,35 +19,23 @@ package com.android.launcher3.taskbar.customization
|
||||
/** Taskbar Icon Specs */
|
||||
object TaskbarIconSpecs {
|
||||
|
||||
// Mapping of visual icon size to icon specs value http://b/235886078
|
||||
val iconSize40dp = TaskbarIconSize(44)
|
||||
val iconSize44dp = TaskbarIconSize(48)
|
||||
val iconSize48dp = TaskbarIconSize(52)
|
||||
val iconSize52dp = TaskbarIconSize(57)
|
||||
val iconSize40dp = TaskbarIconSize(40)
|
||||
val iconSize44dp = TaskbarIconSize(44)
|
||||
val iconSize48dp = TaskbarIconSize(48)
|
||||
val iconSize52dp = TaskbarIconSize(52)
|
||||
|
||||
val transientTaskbarIconSizes = arrayOf(iconSize44dp, iconSize48dp, iconSize52dp)
|
||||
|
||||
val defaultPersistentIconSize = iconSize40dp
|
||||
val defaultTransientIconSize = iconSize44dp
|
||||
|
||||
val minimumIconSize = iconSize40dp
|
||||
|
||||
val defaultPersistentIconMargin = TaskbarIconMarginSize(6)
|
||||
val defaultTransientIconMargin = TaskbarIconMarginSize(12)
|
||||
|
||||
val minimumTaskbarIconTouchSize = TaskbarIconSize(48)
|
||||
|
||||
val transientOrPinnedTaskbarIconPaddingSize = iconSize52dp
|
||||
|
||||
// defined as row, columns
|
||||
val transientTaskbarIconSizeByGridSize =
|
||||
mapOf(
|
||||
TransientTaskbarIconSizeKey(6, 5, false) to iconSize52dp,
|
||||
TransientTaskbarIconSizeKey(6, 5, true) to iconSize52dp,
|
||||
TransientTaskbarIconSizeKey(4, 4, false) to iconSize48dp,
|
||||
TransientTaskbarIconSizeKey(4, 4, true) to iconSize52dp,
|
||||
TransientTaskbarIconSizeKey(4, 5, false) to iconSize48dp,
|
||||
TransientTaskbarIconSizeKey(4, 5, true) to iconSize48dp,
|
||||
TransientTaskbarIconSizeKey(5, 5, false) to iconSize44dp,
|
||||
TransientTaskbarIconSizeKey(5, 5, true) to iconSize44dp,
|
||||
Pair(6, 5) to iconSize52dp,
|
||||
Pair(4, 5) to iconSize48dp,
|
||||
Pair(5, 4) to iconSize48dp,
|
||||
Pair(4, 4) to iconSize48dp,
|
||||
Pair(5, 6) to iconSize44dp,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -16,43 +16,13 @@
|
||||
|
||||
package com.android.launcher3.taskbar.customization
|
||||
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext
|
||||
|
||||
/** Evaluates the taskbar specs based on the taskbar grid size and the taskbar icon size. */
|
||||
class TaskbarSpecsEvaluator(
|
||||
private val taskbarActivityContext: TaskbarActivityContext,
|
||||
private val taskbarFeatureEvaluator: TaskbarFeatureEvaluator,
|
||||
numRows: Int = taskbarActivityContext.deviceProfile.inv.numRows,
|
||||
numColumns: Int = taskbarActivityContext.deviceProfile.inv.numColumns,
|
||||
) {
|
||||
var taskbarIconSize: TaskbarIconSize = getIconSizeByGrid(numColumns, numRows)
|
||||
val numShownHotseatIcons
|
||||
get() = taskbarActivityContext.deviceProfile.numShownHotseatIcons
|
||||
class TaskbarSpecsEvaluator(private val taskbarFeatureEvaluator: TaskbarFeatureEvaluator) {
|
||||
|
||||
// TODO(b/341146605) : initialize it to taskbar container in later cl.
|
||||
private var taskbarContainer: List<TaskbarContainer> = emptyList()
|
||||
|
||||
val taskbarIconPadding: Int =
|
||||
if (
|
||||
TaskbarIconSpecs.transientOrPinnedTaskbarIconPaddingSize.size > taskbarIconSize.size &&
|
||||
!taskbarFeatureEvaluator.hasNavButtons
|
||||
) {
|
||||
(TaskbarIconSpecs.iconSize52dp.size - taskbarIconSize.size) / 2
|
||||
} else {
|
||||
0
|
||||
}
|
||||
|
||||
val taskbarIconMargin: TaskbarIconMarginSize =
|
||||
if (taskbarFeatureEvaluator.isTransient) {
|
||||
TaskbarIconSpecs.defaultTransientIconMargin
|
||||
} else {
|
||||
TaskbarIconSpecs.defaultPersistentIconMargin
|
||||
}
|
||||
|
||||
fun getIconSizeByGrid(columns: Int, rows: Int): TaskbarIconSize {
|
||||
fun getIconSizeByGrid(row: Int, column: Int): TaskbarIconSize {
|
||||
return if (taskbarFeatureEvaluator.isTransient) {
|
||||
TaskbarIconSpecs.transientTaskbarIconSizeByGridSize.getOrDefault(
|
||||
TransientTaskbarIconSizeKey(columns, rows, taskbarFeatureEvaluator.isLandscape),
|
||||
Pair(row, column),
|
||||
TaskbarIconSpecs.defaultTransientIconSize,
|
||||
)
|
||||
} else {
|
||||
@@ -66,11 +36,8 @@ class TaskbarSpecsEvaluator(
|
||||
val currentIconSizeIndex = TaskbarIconSpecs.transientTaskbarIconSizes.indexOf(iconSize)
|
||||
// return the current icon size if supplied icon size is unknown or we have reached the
|
||||
// min icon size.
|
||||
return if (currentIconSizeIndex == -1 || currentIconSizeIndex == 0) {
|
||||
iconSize
|
||||
} else {
|
||||
TaskbarIconSpecs.transientTaskbarIconSizes[currentIconSizeIndex - 1]
|
||||
}
|
||||
return if (currentIconSizeIndex == -1 || currentIconSizeIndex == 0) iconSize
|
||||
else TaskbarIconSpecs.transientTaskbarIconSizes[currentIconSizeIndex - 1]
|
||||
}
|
||||
|
||||
fun getIconSizeStepUp(iconSize: TaskbarIconSize): TaskbarIconSize {
|
||||
@@ -85,28 +52,9 @@ class TaskbarSpecsEvaluator(
|
||||
) {
|
||||
iconSize
|
||||
} else {
|
||||
TaskbarIconSpecs.transientTaskbarIconSizes[currentIconSizeIndex + 1]
|
||||
TaskbarIconSpecs.transientTaskbarIconSizes.get(currentIconSizeIndex + 1)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(jagrutdesai) : Call this in init once the containers are ready.
|
||||
private fun calculateTaskbarIconSize() {
|
||||
while (
|
||||
taskbarIconSize != TaskbarIconSpecs.minimumIconSize &&
|
||||
taskbarActivityContext.transientTaskbarBounds.width() <
|
||||
calculateSpaceNeeded(taskbarContainer)
|
||||
) {
|
||||
taskbarIconSize = getIconSizeStepDown(taskbarIconSize)
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculateSpaceNeeded(containers: List<TaskbarContainer>): Int {
|
||||
return containers.sumOf { it.spaceNeeded }
|
||||
}
|
||||
}
|
||||
|
||||
data class TaskbarIconSize(val size: Int)
|
||||
|
||||
data class TransientTaskbarIconSizeKey(val columns: Int, val rows: Int, val isLandscape: Boolean)
|
||||
|
||||
data class TaskbarIconMarginSize(val size: Int)
|
||||
|
||||
+11
-18
@@ -27,6 +27,7 @@ import android.widget.Space
|
||||
import com.android.launcher3.DeviceProfile
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.Utilities
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext
|
||||
import com.android.launcher3.taskbar.navbutton.NavButtonLayoutFactory.NavButtonLayoutter
|
||||
|
||||
/**
|
||||
@@ -47,7 +48,7 @@ abstract class AbstractNavButtonLayoutter(
|
||||
protected val startContextualContainer: ViewGroup,
|
||||
protected val imeSwitcher: ImageView?,
|
||||
protected val a11yButton: ImageView?,
|
||||
protected val space: Space?,
|
||||
protected val space: Space?
|
||||
) : NavButtonLayoutter {
|
||||
protected val homeButton: ImageView? = navButtonContainer.findViewById(R.id.home)
|
||||
protected val recentsButton: ImageView? = navButtonContainer.findViewById(R.id.recent_apps)
|
||||
@@ -68,34 +69,26 @@ abstract class AbstractNavButtonLayoutter(
|
||||
val params =
|
||||
FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT
|
||||
)
|
||||
params.gravity = Gravity.CENTER
|
||||
return params
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjusts the layout parameters of the nav bar container for setup in phone mode.
|
||||
*
|
||||
* @param nearestTouchFrameLayoutParams The layout parameters of the navButtonsView, which is
|
||||
* the ViewGroup that contains start, end, nav button ViewGroups
|
||||
* @param deviceProfile The device profile containing information about the device's
|
||||
* configuration.
|
||||
*/
|
||||
fun adjustForSetupInPhoneMode(
|
||||
nearestTouchFrameLayoutParams: FrameLayout.LayoutParams,
|
||||
deviceProfile: DeviceProfile,
|
||||
navButtonsLayoutParams: FrameLayout.LayoutParams,
|
||||
navButtonsViewLayoutParams: FrameLayout.LayoutParams,
|
||||
deviceProfile: DeviceProfile
|
||||
) {
|
||||
val phoneOrPortraitSetupMargin =
|
||||
resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_suw_margin)
|
||||
nearestTouchFrameLayoutParams.marginStart = phoneOrPortraitSetupMargin
|
||||
nearestTouchFrameLayoutParams.bottomMargin =
|
||||
navButtonsLayoutParams.marginStart = phoneOrPortraitSetupMargin
|
||||
navButtonsLayoutParams.bottomMargin =
|
||||
if (!deviceProfile.isLandscape) 0
|
||||
else
|
||||
phoneOrPortraitSetupMargin -
|
||||
resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size) / 2
|
||||
|
||||
nearestTouchFrameLayoutParams.height =
|
||||
resources.getDimensionPixelSize(R.dimen.taskbar_nav_buttons_size) / 2
|
||||
navButtonsViewLayoutParams.height =
|
||||
resources.getDimensionPixelSize(R.dimen.taskbar_contextual_button_suw_height)
|
||||
}
|
||||
|
||||
@@ -104,7 +97,7 @@ abstract class AbstractNavButtonLayoutter(
|
||||
buttonSize: Int,
|
||||
barAxisMarginStart: Int,
|
||||
barAxisMarginEnd: Int,
|
||||
gravity: Int,
|
||||
gravity: Int
|
||||
) {
|
||||
val contextualContainerParams =
|
||||
FrameLayout.LayoutParams(buttonSize, ViewGroup.LayoutParams.MATCH_PARENT)
|
||||
|
||||
@@ -66,7 +66,7 @@ class NavButtonLayoutFactory {
|
||||
isInSetup: Boolean,
|
||||
isThreeButtonNav: Boolean,
|
||||
phoneMode: Boolean,
|
||||
@Rotation surfaceRotation: Int,
|
||||
@Rotation surfaceRotation: Int
|
||||
): NavButtonLayoutter {
|
||||
val navButtonContainer =
|
||||
navButtonsView.requireViewById<LinearLayout>(ID_END_NAV_BUTTONS)
|
||||
@@ -77,18 +77,6 @@ class NavButtonLayoutFactory {
|
||||
val isPhoneNavMode = phoneMode && isThreeButtonNav
|
||||
val isPhoneGestureMode = phoneMode && !isThreeButtonNav
|
||||
return when {
|
||||
isInSetup -> {
|
||||
SetupNavLayoutter(
|
||||
resources,
|
||||
navButtonsView,
|
||||
navButtonContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
a11yButton,
|
||||
space,
|
||||
)
|
||||
}
|
||||
isPhoneNavMode -> {
|
||||
if (!deviceProfile.isLandscape) {
|
||||
navButtonsView.setIsVertical(false)
|
||||
@@ -99,7 +87,7 @@ class NavButtonLayoutFactory {
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
a11yButton,
|
||||
space,
|
||||
space
|
||||
)
|
||||
} else if (surfaceRotation == ROTATION_90) {
|
||||
navButtonsView.setIsVertical(true)
|
||||
@@ -110,7 +98,7 @@ class NavButtonLayoutFactory {
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
a11yButton,
|
||||
space,
|
||||
space
|
||||
)
|
||||
} else {
|
||||
navButtonsView.setIsVertical(true)
|
||||
@@ -121,23 +109,36 @@ class NavButtonLayoutFactory {
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
a11yButton,
|
||||
space,
|
||||
space
|
||||
)
|
||||
}
|
||||
}
|
||||
isPhoneGestureMode -> {
|
||||
PhoneGestureLayoutter(
|
||||
resources,
|
||||
navButtonsView,
|
||||
navButtonContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
a11yButton,
|
||||
space,
|
||||
space
|
||||
)
|
||||
}
|
||||
deviceProfile.isTaskbarPresent -> {
|
||||
return when {
|
||||
isInSetup -> {
|
||||
SetupNavLayoutter(
|
||||
resources,
|
||||
navButtonsView,
|
||||
navButtonContainer,
|
||||
endContextualContainer,
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
a11yButton,
|
||||
space
|
||||
)
|
||||
}
|
||||
isKidsMode -> {
|
||||
KidsNavLayoutter(
|
||||
resources,
|
||||
@@ -146,7 +147,7 @@ class NavButtonLayoutFactory {
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
a11yButton,
|
||||
space,
|
||||
space
|
||||
)
|
||||
}
|
||||
else ->
|
||||
@@ -157,7 +158,7 @@ class NavButtonLayoutFactory {
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
a11yButton,
|
||||
space,
|
||||
space
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +194,6 @@ public class NearestTouchFrame extends FrameLayout {
|
||||
event.offsetLocation(mTouchingChild.getWidth() / 2 - x,
|
||||
mTouchingChild.getHeight() / 2 - y);
|
||||
return mTouchingChild.getVisibility() == VISIBLE
|
||||
&& mTouchingChild.isAttachedToWindow()
|
||||
&& mTouchingChild.dispatchTouchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,21 +17,25 @@
|
||||
package com.android.launcher3.taskbar.navbutton
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.view.Gravity
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Space
|
||||
import com.android.launcher3.DeviceProfile
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext
|
||||
|
||||
/** Layoutter for showing gesture navigation on phone screen. No buttons here, no-op container */
|
||||
class PhoneGestureLayoutter(
|
||||
resources: Resources,
|
||||
navButtonsView: NearestTouchFrame,
|
||||
navBarContainer: LinearLayout,
|
||||
endContextualContainer: ViewGroup,
|
||||
startContextualContainer: ViewGroup,
|
||||
imeSwitcher: ImageView?,
|
||||
a11yButton: ImageView?,
|
||||
space: Space?,
|
||||
space: Space?
|
||||
) :
|
||||
AbstractNavButtonLayoutter(
|
||||
resources,
|
||||
@@ -40,10 +44,33 @@ class PhoneGestureLayoutter(
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
a11yButton,
|
||||
space,
|
||||
space
|
||||
) {
|
||||
private val mNavButtonsView = navButtonsView
|
||||
|
||||
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
|
||||
// TODO: look into if we should use SetupNavLayoutter instead.
|
||||
if (!context.isUserSetupComplete) {
|
||||
// Since setup wizard only has back button enabled, it looks strange to be
|
||||
// end-aligned, so start-align instead.
|
||||
val navButtonsLayoutParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
|
||||
val navButtonsViewLayoutParams =
|
||||
mNavButtonsView.layoutParams as FrameLayout.LayoutParams
|
||||
val deviceProfile: DeviceProfile = context.deviceProfile
|
||||
|
||||
navButtonsLayoutParams.marginEnd = 0
|
||||
navButtonsLayoutParams.gravity = Gravity.START
|
||||
context.setTaskbarWindowSize(context.setupWindowSize)
|
||||
|
||||
adjustForSetupInPhoneMode(
|
||||
navButtonsLayoutParams,
|
||||
navButtonsViewLayoutParams,
|
||||
deviceProfile
|
||||
)
|
||||
mNavButtonsView.layoutParams = navButtonsViewLayoutParams
|
||||
navButtonContainer.layoutParams = navButtonsLayoutParams
|
||||
}
|
||||
|
||||
endContextualContainer.removeAllViews()
|
||||
startContextualContainer.removeAllViews()
|
||||
}
|
||||
|
||||
@@ -17,7 +17,6 @@
|
||||
package com.android.launcher3.taskbar.navbutton
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.os.SystemProperties
|
||||
import android.view.Gravity
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
@@ -29,21 +28,18 @@ import com.android.launcher3.DeviceProfile
|
||||
import com.android.launcher3.R
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext
|
||||
|
||||
const val SUW_THEME_SYSTEM_PROPERTY = "setupwizard.theme"
|
||||
const val GLIF_EXPRESSIVE_THEME = "glif_expressive"
|
||||
const val GLIF_EXPRESSIVE_LIGHT_THEME = "glif_expressive_light"
|
||||
const val SQUARE_ASPECT_RATIO_BOTTOM_BOUND = 0.95
|
||||
const val SQUARE_ASPECT_RATIO_UPPER_BOUND = 1.05
|
||||
|
||||
class SetupNavLayoutter(
|
||||
resources: Resources,
|
||||
nearestTouchFrame: NearestTouchFrame,
|
||||
navButtonsView: NearestTouchFrame,
|
||||
navButtonContainer: LinearLayout,
|
||||
endContextualContainer: ViewGroup,
|
||||
startContextualContainer: ViewGroup,
|
||||
imeSwitcher: ImageView?,
|
||||
a11yButton: ImageView?,
|
||||
space: Space?,
|
||||
space: Space?
|
||||
) :
|
||||
AbstractNavButtonLayoutter(
|
||||
resources,
|
||||
@@ -52,21 +48,15 @@ class SetupNavLayoutter(
|
||||
startContextualContainer,
|
||||
imeSwitcher,
|
||||
a11yButton,
|
||||
space,
|
||||
space
|
||||
) {
|
||||
// mNearestTouchFrame is a ViewGroup that contains start, end, nav button ViewGroups
|
||||
private val mNearestTouchFrame = nearestTouchFrame
|
||||
private val mNavButtonsView = navButtonsView
|
||||
|
||||
override fun layoutButtons(context: TaskbarActivityContext, isA11yButtonPersistent: Boolean) {
|
||||
val SUWTheme = SystemProperties.get(SUW_THEME_SYSTEM_PROPERTY, "")
|
||||
if (SUWTheme == GLIF_EXPRESSIVE_THEME || SUWTheme == GLIF_EXPRESSIVE_LIGHT_THEME) {
|
||||
return
|
||||
}
|
||||
// Since setup wizard only has back button enabled, it looks strange to be
|
||||
// end-aligned, so start-align instead.
|
||||
val navButtonsLayoutParams = navButtonContainer.layoutParams as FrameLayout.LayoutParams
|
||||
val navButtonsOverallViewGroupLayoutParams =
|
||||
mNearestTouchFrame.layoutParams as FrameLayout.LayoutParams
|
||||
val navButtonsViewLayoutParams = mNavButtonsView.layoutParams as FrameLayout.LayoutParams
|
||||
val deviceProfile: DeviceProfile = context.deviceProfile
|
||||
|
||||
navButtonsLayoutParams.marginEnd = 0
|
||||
@@ -82,14 +72,18 @@ class SetupNavLayoutter(
|
||||
) {
|
||||
navButtonsLayoutParams.marginStart =
|
||||
resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_start_margin)
|
||||
navButtonsOverallViewGroupLayoutParams.bottomMargin =
|
||||
navButtonsViewLayoutParams.bottomMargin =
|
||||
resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_bottom_margin)
|
||||
navButtonsLayoutParams.height =
|
||||
resources.getDimensionPixelSize(R.dimen.taskbar_back_button_suw_height)
|
||||
} else {
|
||||
adjustForSetupInPhoneMode(navButtonsOverallViewGroupLayoutParams, deviceProfile)
|
||||
adjustForSetupInPhoneMode(
|
||||
navButtonsLayoutParams,
|
||||
navButtonsViewLayoutParams,
|
||||
deviceProfile
|
||||
)
|
||||
}
|
||||
mNearestTouchFrame.layoutParams = navButtonsOverallViewGroupLayoutParams
|
||||
mNavButtonsView.layoutParams = navButtonsViewLayoutParams
|
||||
navButtonContainer.layoutParams = navButtonsLayoutParams
|
||||
|
||||
endContextualContainer.removeAllViews()
|
||||
@@ -103,7 +97,7 @@ class SetupNavLayoutter(
|
||||
WRAP_CONTENT,
|
||||
contextualMargin,
|
||||
contextualMargin,
|
||||
Gravity.START,
|
||||
Gravity.START
|
||||
)
|
||||
|
||||
if (imeSwitcher != null) {
|
||||
|
||||
@@ -18,11 +18,12 @@ package com.android.launcher3.taskbar.overlay;
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.dot.DotInfo;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.popup.PopupDataProvider;
|
||||
import com.android.launcher3.taskbar.BaseTaskbarContext;
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext;
|
||||
@@ -31,7 +32,6 @@ import com.android.launcher3.taskbar.TaskbarDragController;
|
||||
import com.android.launcher3.taskbar.TaskbarUIController;
|
||||
import com.android.launcher3.taskbar.allapps.TaskbarAllAppsContainerView;
|
||||
import com.android.launcher3.taskbar.allapps.TaskbarSearchSessionController;
|
||||
import com.android.launcher3.util.NavigationMode;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
|
||||
|
||||
/**
|
||||
@@ -57,7 +57,7 @@ public class TaskbarOverlayContext extends BaseTaskbarContext {
|
||||
Context windowContext,
|
||||
TaskbarActivityContext taskbarContext,
|
||||
TaskbarControllers controllers) {
|
||||
super(windowContext, taskbarContext.isPrimaryDisplay());
|
||||
super(windowContext);
|
||||
mTaskbarContext = taskbarContext;
|
||||
mOverlayController = controllers.taskbarOverlayController;
|
||||
mDragController = new TaskbarDragController(this);
|
||||
@@ -66,12 +66,6 @@ public class TaskbarOverlayContext extends BaseTaskbarContext {
|
||||
mStashedTaskbarHeight = controllers.taskbarStashController.getStashedHeight();
|
||||
|
||||
mUiController = controllers.uiController;
|
||||
onViewCreated();
|
||||
}
|
||||
|
||||
/** Called when the controller is destroyed. */
|
||||
public void onDestroy() {
|
||||
mDragController.onDestroy();
|
||||
}
|
||||
|
||||
public @Nullable TaskbarSearchSessionController getSearchSessionController() {
|
||||
@@ -125,6 +119,11 @@ public class TaskbarOverlayContext extends BaseTaskbarContext {
|
||||
return mDragLayer.findViewById(R.id.apps_view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isBindingItems() {
|
||||
return mTaskbarContext.isBindingItems();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View.OnClickListener getItemOnClickListener() {
|
||||
return mTaskbarContext.getItemOnClickListener();
|
||||
@@ -135,7 +134,6 @@ public class TaskbarOverlayContext extends BaseTaskbarContext {
|
||||
return mDragController::startDragOnLongClick;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public PopupDataProvider getPopupDataProvider() {
|
||||
return mTaskbarContext.getPopupDataProvider();
|
||||
@@ -147,38 +145,8 @@ public class TaskbarOverlayContext extends BaseTaskbarContext {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isTransientTaskbar() {
|
||||
return mTaskbarContext.isTransientTaskbar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPinnedTaskbar() {
|
||||
return mTaskbarContext.isPinnedTaskbar();
|
||||
}
|
||||
|
||||
@Override
|
||||
public NavigationMode getNavigationMode() {
|
||||
return mTaskbarContext.getNavigationMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInDesktopMode() {
|
||||
return mTaskbarContext.isInDesktopMode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showLockedTaskbarOnHome() {
|
||||
return mTaskbarContext.showLockedTaskbarOnHome();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showDesktopTaskbarForFreeformDisplay() {
|
||||
return mTaskbarContext.showDesktopTaskbarForFreeformDisplay();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPrimaryDisplay() {
|
||||
return mTaskbarContext.isPrimaryDisplay();
|
||||
public DotInfo getDotInfoForItem(ItemInfo info) {
|
||||
return mTaskbarContext.getDotInfoForItem(info);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -26,12 +26,8 @@ import static com.android.launcher3.LauncherState.ALL_APPS;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.util.Log;
|
||||
import android.view.AttachedSurfaceControl;
|
||||
import android.view.Gravity;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.SurfaceControl;
|
||||
import android.view.ViewRootImpl;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
|
||||
@@ -39,11 +35,8 @@ import androidx.annotation.Nullable;
|
||||
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Flags;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.taskbar.TaskbarActivityContext;
|
||||
import com.android.launcher3.taskbar.TaskbarControllers;
|
||||
import com.android.systemui.shared.system.BlurUtils;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListener;
|
||||
import com.android.systemui.shared.system.TaskStackChangeListeners;
|
||||
|
||||
@@ -58,14 +51,12 @@ import java.util.Optional;
|
||||
*/
|
||||
public final class TaskbarOverlayController {
|
||||
|
||||
private static final String TAG = "TaskbarOverlayController";
|
||||
private static final String WINDOW_TITLE = "Taskbar Overlay";
|
||||
|
||||
private final TaskbarActivityContext mTaskbarContext;
|
||||
private final Context mWindowContext;
|
||||
private final TaskbarOverlayProxyView mProxyView;
|
||||
private final LayoutParams mLayoutParams;
|
||||
private final int mMaxBlurRadius;
|
||||
|
||||
private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
|
||||
@Override
|
||||
@@ -96,8 +87,6 @@ public final class TaskbarOverlayController {
|
||||
private DeviceProfile mLauncherDeviceProfile;
|
||||
private @Nullable TaskbarOverlayContext mOverlayContext;
|
||||
private TaskbarControllers mControllers; // Initialized in init.
|
||||
// True if we have alerted surface flinger of an expensive call for blur.
|
||||
private boolean mInEarlyWakeUp;
|
||||
|
||||
public TaskbarOverlayController(
|
||||
TaskbarActivityContext taskbarContext, DeviceProfile launcherDeviceProfile) {
|
||||
@@ -106,8 +95,6 @@ public final class TaskbarOverlayController {
|
||||
mProxyView = new TaskbarOverlayProxyView();
|
||||
mLayoutParams = createLayoutParams();
|
||||
mLauncherDeviceProfile = launcherDeviceProfile;
|
||||
mMaxBlurRadius = mTaskbarContext.getResources().getDimensionPixelSize(
|
||||
R.dimen.max_depth_blur_radius_enhanced);
|
||||
}
|
||||
|
||||
/** Initialize the controller. */
|
||||
@@ -162,13 +149,9 @@ public final class TaskbarOverlayController {
|
||||
/** Destroys the controller and any overlay window if present. */
|
||||
public void onDestroy() {
|
||||
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
|
||||
Optional.ofNullable(mOverlayContext).ifPresent(c -> {
|
||||
c.onDestroy();
|
||||
WindowManager wm = c.getSystemService(WindowManager.class);
|
||||
if (wm != null) {
|
||||
wm.removeViewImmediate(mOverlayContext.getDragLayer());
|
||||
}
|
||||
});
|
||||
Optional.ofNullable(mOverlayContext)
|
||||
.map(c -> c.getSystemService(WindowManager.class))
|
||||
.ifPresent(m -> m.removeViewImmediate(mOverlayContext.getDragLayer()));
|
||||
mOverlayContext = null;
|
||||
}
|
||||
|
||||
@@ -200,7 +183,7 @@ public final class TaskbarOverlayController {
|
||||
private LayoutParams createLayoutParams() {
|
||||
LayoutParams layoutParams = new LayoutParams(
|
||||
TYPE_APPLICATION_OVERLAY,
|
||||
/* flags = */ 0,
|
||||
LayoutParams.FLAG_SPLIT_TOUCH,
|
||||
PixelFormat.TRANSLUCENT);
|
||||
layoutParams.setTitle(WINDOW_TITLE);
|
||||
layoutParams.gravity = Gravity.BOTTOM;
|
||||
@@ -212,73 +195,6 @@ public final class TaskbarOverlayController {
|
||||
return layoutParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the blur radius for the overlay window.
|
||||
*
|
||||
* @param radius the blur radius in pixels. This will automatically change to {@code 0} if blurs
|
||||
* are unsupported on the device.
|
||||
*/
|
||||
public void setBackgroundBlurRadius(int radius) {
|
||||
if (!Flags.allAppsBlur()) {
|
||||
return;
|
||||
}
|
||||
if (!BlurUtils.supportsBlursOnWindows()) {
|
||||
Log.d(TAG, "setBackgroundBlurRadius: not supported, setting to 0");
|
||||
radius = 0;
|
||||
// intentionally falling through in case a non-0 blur was previously set.
|
||||
}
|
||||
if (mOverlayContext == null) {
|
||||
Log.w(TAG, "setBackgroundBlurRadius: no overlay context");
|
||||
return;
|
||||
}
|
||||
TaskbarOverlayDragLayer dragLayer = mOverlayContext.getDragLayer();
|
||||
if (dragLayer == null) {
|
||||
Log.w(TAG, "setBackgroundBlurRadius: no drag layer");
|
||||
return;
|
||||
}
|
||||
ViewRootImpl dragLayerViewRoot = dragLayer.getViewRootImpl();
|
||||
if (dragLayerViewRoot == null) {
|
||||
Log.w(TAG, "setBackgroundBlurRadius: dragLayerViewRoot is null");
|
||||
return;
|
||||
}
|
||||
AttachedSurfaceControl rootSurfaceControl = dragLayer.getRootSurfaceControl();
|
||||
if (rootSurfaceControl == null) {
|
||||
Log.w(TAG, "setBackgroundBlurRadius: rootSurfaceControl is null");
|
||||
return;
|
||||
}
|
||||
SurfaceControl surfaceControl = dragLayerViewRoot.getSurfaceControl();
|
||||
if (surfaceControl == null || !surfaceControl.isValid()) {
|
||||
Log.w(TAG, "setBackgroundBlurRadius: surfaceControl is null or invalid");
|
||||
return;
|
||||
}
|
||||
Log.v(TAG, "setBackgroundBlurRadius: " + radius);
|
||||
SurfaceControl.Transaction transaction =
|
||||
new SurfaceControl.Transaction().setBackgroundBlurRadius(surfaceControl, radius);
|
||||
|
||||
// Set early wake-up flags when we know we're executing an expensive operation, this way
|
||||
// SurfaceFlinger will adjust its internal offsets to avoid jank.
|
||||
boolean wantsEarlyWakeUp = radius > 0 && radius < mMaxBlurRadius;
|
||||
if (wantsEarlyWakeUp && !mInEarlyWakeUp) {
|
||||
Log.d(TAG, "setBackgroundBlurRadius: setting early wakeup");
|
||||
try {
|
||||
transaction.setEarlyWakeupStart();
|
||||
} catch (NoSuchMethodError e) {
|
||||
// LC-Ignored: wtf?
|
||||
}
|
||||
mInEarlyWakeUp = true;
|
||||
} else if (!wantsEarlyWakeUp && mInEarlyWakeUp) {
|
||||
Log.d(TAG, "setBackgroundBlurRadius: clearing early wakeup");
|
||||
try {
|
||||
transaction.setEarlyWakeupEnd();
|
||||
} catch (NoSuchMethodError e) {
|
||||
// LC-Ignored: wtf?
|
||||
}
|
||||
mInEarlyWakeUp = false;
|
||||
}
|
||||
|
||||
rootSurfaceControl.applyTransactionOnDraw(transaction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Proxy view connecting taskbar drag layer to the overlay window.
|
||||
*
|
||||
@@ -300,13 +216,6 @@ public final class TaskbarOverlayController {
|
||||
@Override
|
||||
protected void handleClose(boolean animate) {
|
||||
if (!mIsOpen) return;
|
||||
if (Flags.taskbarOverflow()) {
|
||||
// Mark the view closed before attempting to remove it, so the drag layer does not
|
||||
// schedule another call to close. Needed for taskbar overflow in case the KQS
|
||||
// view shown for taskbar overflow needs to be reshown - delayed close call would
|
||||
// would result in reshown KQS view getting hidden.
|
||||
mIsOpen = false;
|
||||
}
|
||||
mTaskbarContext.getDragLayer().removeView(this);
|
||||
Optional.ofNullable(mOverlayContext).ifPresent(c -> {
|
||||
if (canCloseWindow()) {
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.android.app.viewcapture.ViewCaptureFactory;
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
import com.android.launcher3.testing.TestLogging;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
import com.android.launcher3.util.DisplayController;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
import com.android.launcher3.views.BaseDragLayer;
|
||||
|
||||
@@ -72,7 +73,7 @@ public class TaskbarOverlayDragLayer extends
|
||||
@Override
|
||||
public void recreateControllers() {
|
||||
List<TouchController> controllers = new ArrayList<>();
|
||||
controllers.add(mContainer.getDragController());
|
||||
controllers.add(mActivity.getDragController());
|
||||
controllers.addAll(mTouchControllers);
|
||||
mControllers = controllers.toArray(new TouchController[0]);
|
||||
}
|
||||
@@ -86,7 +87,7 @@ public class TaskbarOverlayDragLayer extends
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
if (event.getAction() == ACTION_UP && event.getKeyCode() == KEYCODE_BACK) {
|
||||
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mContainer);
|
||||
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
|
||||
if (topView != null && topView.canHandleBack()) {
|
||||
topView.onBackInvoked();
|
||||
return true;
|
||||
@@ -95,7 +96,7 @@ public class TaskbarOverlayDragLayer extends
|
||||
&& event.getKeyCode() == KeyEvent.KEYCODE_ESCAPE && event.hasNoModifiers()) {
|
||||
// Ignore escape if pressed in conjunction with any modifier keys. Close each
|
||||
// floating view one at a time for each key press.
|
||||
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mContainer);
|
||||
AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mActivity);
|
||||
if (topView != null) {
|
||||
topView.close(/* animate= */ true);
|
||||
return true;
|
||||
@@ -106,7 +107,7 @@ public class TaskbarOverlayDragLayer extends
|
||||
|
||||
@Override
|
||||
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
|
||||
if (mContainer.isAnySystemDragInProgress()) {
|
||||
if (mActivity.isAnySystemDragInProgress()) {
|
||||
inoutInfo.touchableRegion.setEmpty();
|
||||
inoutInfo.setTouchableInsets(TOUCHABLE_INSETS_REGION);
|
||||
}
|
||||
@@ -122,7 +123,7 @@ public class TaskbarOverlayDragLayer extends
|
||||
@Override
|
||||
public void onViewRemoved(View child) {
|
||||
super.onViewRemoved(child);
|
||||
mContainer.getOverlayController().maybeCloseWindow();
|
||||
mActivity.getOverlayController().maybeCloseWindow();
|
||||
}
|
||||
|
||||
/** Adds a {@link TouchController} to this drag layer. */
|
||||
@@ -146,14 +147,14 @@ public class TaskbarOverlayDragLayer extends
|
||||
* 2) Sets tappableInsets bottom inset to 0.
|
||||
*/
|
||||
private WindowInsets updateInsetsDueToStashing(WindowInsets oldInsets) {
|
||||
if (!mContainer.isTransientTaskbar()) {
|
||||
if (!DisplayController.isTransientTaskbar(mActivity)) {
|
||||
return oldInsets;
|
||||
}
|
||||
WindowInsets.Builder updatedInsetsBuilder = new WindowInsets.Builder(oldInsets);
|
||||
|
||||
Insets oldNavInsets = oldInsets.getInsets(WindowInsets.Type.navigationBars());
|
||||
Insets newNavInsets = Insets.of(oldNavInsets.left, oldNavInsets.top, oldNavInsets.right,
|
||||
mContainer.getStashedTaskbarHeight());
|
||||
mActivity.getStashedTaskbarHeight());
|
||||
updatedInsetsBuilder.setInsets(WindowInsets.Type.navigationBars(), newNavInsets);
|
||||
|
||||
Insets oldTappableInsets = oldInsets.getInsets(WindowInsets.Type.tappableElement());
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
/*
|
||||
* Copyright (C) 2019 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.uioverrides;
|
||||
|
||||
import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
|
||||
import static com.android.app.animation.Interpolators.AGGRESSIVE_EASE_IN_OUT;
|
||||
import static com.android.app.animation.Interpolators.FINAL_FRAME;
|
||||
import static com.android.app.animation.Interpolators.INSTANT;
|
||||
import static com.android.app.animation.Interpolators.LINEAR;
|
||||
import static com.android.launcher3.LauncherState.QUICK_SWITCH_FROM_HOME;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_FADE;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_MODAL;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SCALE;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_X;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.ANIM_OVERVIEW_TRANSLATE_Y;
|
||||
import static com.android.launcher3.states.StateAnimationConfig.SKIP_OVERVIEW;
|
||||
import static com.android.quickstep.views.RecentsView.ADJACENT_PAGE_HORIZONTAL_OFFSET;
|
||||
import static com.android.quickstep.views.RecentsView.RECENTS_GRID_PROGRESS;
|
||||
import static com.android.quickstep.views.RecentsView.RECENTS_SCALE_PROPERTY;
|
||||
import static com.android.quickstep.views.RecentsView.TASK_SECONDARY_TRANSLATION;
|
||||
import static com.android.quickstep.views.RecentsView.TASK_THUMBNAIL_SPLASH_ALPHA;
|
||||
|
||||
import android.util.FloatProperty;
|
||||
import android.view.animation.Interpolator;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.statemanager.StateManager.StateHandler;
|
||||
import com.android.launcher3.states.StateAnimationConfig;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
|
||||
/**
|
||||
* State handler for recents view. Manages UI changes and animations for recents view based off the
|
||||
* current {@link LauncherState}.
|
||||
*
|
||||
* @param <T> the recents view
|
||||
*/
|
||||
public abstract class BaseRecentsViewStateController<T extends RecentsView>
|
||||
implements StateHandler<LauncherState> {
|
||||
protected final T mRecentsView;
|
||||
protected final QuickstepLauncher mLauncher;
|
||||
|
||||
public BaseRecentsViewStateController(@NonNull QuickstepLauncher launcher) {
|
||||
mLauncher = launcher;
|
||||
mRecentsView = launcher.getOverviewPanel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setState(@NonNull LauncherState state) {
|
||||
float[] scaleAndOffset = state.getOverviewScaleAndOffset(mLauncher);
|
||||
RECENTS_SCALE_PROPERTY.set(mRecentsView, scaleAndOffset[0]);
|
||||
ADJACENT_PAGE_HORIZONTAL_OFFSET.set(mRecentsView, scaleAndOffset[1]);
|
||||
TASK_SECONDARY_TRANSLATION.set(mRecentsView, 0f);
|
||||
|
||||
getContentAlphaProperty().set(mRecentsView, state.isRecentsViewVisible ? 1f : 0);
|
||||
getTaskModalnessProperty().set(mRecentsView, state.getOverviewModalness());
|
||||
RECENTS_GRID_PROGRESS.set(mRecentsView,
|
||||
state.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f);
|
||||
TASK_THUMBNAIL_SPLASH_ALPHA.set(mRecentsView, state.showTaskThumbnailSplash() ? 1f : 0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setStateWithAnimation(LauncherState toState, StateAnimationConfig config,
|
||||
PendingAnimation builder) {
|
||||
if (config.hasAnimationFlag(SKIP_OVERVIEW)) {
|
||||
return;
|
||||
}
|
||||
setStateWithAnimationInternal(toState, config, builder);
|
||||
builder.addEndListener(success -> {
|
||||
if (!success) {
|
||||
mRecentsView.reset();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Core logic for animating the recents view UI.
|
||||
*
|
||||
* @param toState state to animate to
|
||||
* @param config current animation config
|
||||
* @param setter animator set builder
|
||||
*/
|
||||
void setStateWithAnimationInternal(@NonNull final LauncherState toState,
|
||||
@NonNull StateAnimationConfig config, @NonNull PendingAnimation setter) {
|
||||
float[] scaleAndOffset = toState.getOverviewScaleAndOffset(mLauncher);
|
||||
setter.setFloat(mRecentsView, RECENTS_SCALE_PROPERTY, scaleAndOffset[0],
|
||||
config.getInterpolator(ANIM_OVERVIEW_SCALE, LINEAR));
|
||||
setter.setFloat(mRecentsView, ADJACENT_PAGE_HORIZONTAL_OFFSET, scaleAndOffset[1],
|
||||
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_X, LINEAR));
|
||||
setter.setFloat(mRecentsView, TASK_SECONDARY_TRANSLATION, 0f,
|
||||
config.getInterpolator(ANIM_OVERVIEW_TRANSLATE_Y, LINEAR));
|
||||
|
||||
boolean exitingOverview =
|
||||
!FeatureFlags.enableSplitContextually() && !toState.isRecentsViewVisible;
|
||||
if (mRecentsView.isSplitSelectionActive() && exitingOverview) {
|
||||
setter.add(mRecentsView.getSplitSelectController().getSplitAnimationController()
|
||||
.createPlaceholderDismissAnim(mLauncher, LAUNCHER_SPLIT_SELECTION_EXIT_HOME,
|
||||
setter.getDuration()));
|
||||
setter.setViewAlpha(
|
||||
mRecentsView.getSplitInstructionsView(),
|
||||
0,
|
||||
config.getInterpolator(
|
||||
ANIM_OVERVIEW_SPLIT_SELECT_INSTRUCTIONS_FADE,
|
||||
LINEAR
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
setter.setFloat(mRecentsView, getContentAlphaProperty(),
|
||||
toState.isRecentsViewVisible ? 1 : 0,
|
||||
config.getInterpolator(ANIM_OVERVIEW_FADE, AGGRESSIVE_EASE_IN_OUT));
|
||||
|
||||
setter.setFloat(
|
||||
mRecentsView, getTaskModalnessProperty(),
|
||||
toState.getOverviewModalness(),
|
||||
config.getInterpolator(ANIM_OVERVIEW_MODAL, LINEAR));
|
||||
|
||||
LauncherState fromState = mLauncher.getStateManager().getState();
|
||||
setter.setFloat(mRecentsView, TASK_THUMBNAIL_SPLASH_ALPHA,
|
||||
toState.showTaskThumbnailSplash() ? 1f : 0f,
|
||||
getOverviewInterpolator(fromState, toState));
|
||||
|
||||
setter.setFloat(mRecentsView, RECENTS_GRID_PROGRESS,
|
||||
toState.displayOverviewTasksAsGrid(mLauncher.getDeviceProfile()) ? 1f : 0f,
|
||||
getOverviewInterpolator(fromState, toState));
|
||||
}
|
||||
|
||||
private Interpolator getOverviewInterpolator(LauncherState fromState, LauncherState toState) {
|
||||
return fromState == QUICK_SWITCH_FROM_HOME
|
||||
? ACCELERATE_DECELERATE
|
||||
: toState.isRecentsViewVisible ? INSTANT : FINAL_FRAME;
|
||||
}
|
||||
|
||||
abstract FloatProperty getTaskModalnessProperty();
|
||||
|
||||
/**
|
||||
* Get property for content alpha for the recents view.
|
||||
*
|
||||
* @return the float property for the view's content alpha
|
||||
*/
|
||||
abstract FloatProperty getContentAlphaProperty();
|
||||
}
|
||||
@@ -16,16 +16,17 @@
|
||||
package com.android.launcher3.uioverrides;
|
||||
|
||||
import static com.android.app.animation.Interpolators.ACCELERATE_DECELERATE;
|
||||
import static com.android.launcher3.icons.BitmapInfo.FLAG_THEMED;
|
||||
import static com.android.launcher3.icons.FastBitmapDrawable.getDisabledColorFilter;
|
||||
import static com.android.launcher3.icons.IconNormalizer.ICON_VISIBLE_AREA_FACTOR;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.AnimatorSet;
|
||||
import android.animation.ArgbEvaluator;
|
||||
import android.animation.Keyframe;
|
||||
import android.animation.ObjectAnimator;
|
||||
import android.animation.PropertyValuesHolder;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.graphics.BlurMaskFilter;
|
||||
import android.graphics.Canvas;
|
||||
@@ -35,25 +36,24 @@ import android.graphics.Paint;
|
||||
import android.graphics.Path;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Process;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.FloatProperty;
|
||||
import android.util.Log;
|
||||
import android.util.Property;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.core.graphics.ColorUtils;
|
||||
|
||||
import com.android.launcher3.DeviceProfile;
|
||||
import com.android.launcher3.Flags;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherSettings;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.anim.AnimatedFloat;
|
||||
import com.android.launcher3.anim.AnimatorListeners;
|
||||
import com.android.launcher3.celllayout.CellLayoutLayoutParams;
|
||||
import com.android.launcher3.celllayout.DelegatedCellDrawing;
|
||||
import com.android.launcher3.graphics.ThemeManager;
|
||||
import com.android.launcher3.icons.FastBitmapDrawable;
|
||||
import com.android.launcher3.icons.BitmapInfo;
|
||||
import com.android.launcher3.icons.GraphicsUtils;
|
||||
import com.android.launcher3.icons.IconNormalizer;
|
||||
import com.android.launcher3.icons.LauncherIcons;
|
||||
import com.android.launcher3.model.data.ItemInfoWithIcon;
|
||||
import com.android.launcher3.model.data.WorkspaceItemInfo;
|
||||
@@ -62,6 +62,10 @@ import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.launcher3.views.ActivityContext;
|
||||
import com.android.launcher3.views.DoubleShadowBubbleTextView;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import app.lawnchair.theme.color.tokens.ColorTokens;
|
||||
|
||||
/**
|
||||
@@ -69,26 +73,12 @@ import app.lawnchair.theme.color.tokens.ColorTokens;
|
||||
*/
|
||||
public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
|
||||
private static final float RING_SCALE_START_VALUE = 0.75f;
|
||||
private static final int RING_SHADOW_COLOR = 0x99000000;
|
||||
private static final float RING_EFFECT_RATIO = Flags.enableLauncherIconShapes() ? 0.1f : 0.095f;
|
||||
private static final float RING_EFFECT_RATIO = 0.095f;
|
||||
|
||||
private static final long ICON_CHANGE_ANIM_DURATION = 360;
|
||||
private static final long ICON_CHANGE_ANIM_STAGGER = 50;
|
||||
|
||||
private static final Property<PredictedAppIcon, Float> RING_SCALE_PROPERTY =
|
||||
new Property<>(Float.TYPE, "ringScale") {
|
||||
@Override
|
||||
public Float get(PredictedAppIcon icon) {
|
||||
return icon.mRingScale;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(PredictedAppIcon icon, Float value) {
|
||||
icon.mRingScale = value;
|
||||
icon.invalidate();
|
||||
}
|
||||
};
|
||||
|
||||
boolean mIsDrawingDot = false;
|
||||
private final DeviceProfile mDeviceProfile;
|
||||
private final Paint mIconRingPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
|
||||
@@ -100,23 +90,16 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
private final BlurMaskFilter mShadowFilter;
|
||||
|
||||
private boolean mIsPinned = false;
|
||||
private final AnimColorHolder mPlateColor = new AnimColorHolder();
|
||||
private int mPlateColor;
|
||||
boolean mDrawForDrag = false;
|
||||
|
||||
// Used for the "slot-machine" animation when prediction changes.
|
||||
private final Rect mSlotIconBound = new Rect(0, 0, getIconSize(), getIconSize());
|
||||
private Drawable mSlotMachineIcon;
|
||||
// Used for the "slot-machine" education animation.
|
||||
private List<Drawable> mSlotMachineIcons;
|
||||
private Animator mSlotMachineAnim;
|
||||
private float mSlotMachineIconTranslationY;
|
||||
|
||||
// Used to animate the "ring" around predicted icons
|
||||
private float mRingScale = 1f;
|
||||
private boolean mForceHideRing = false;
|
||||
private Animator mRingScaleAnim;
|
||||
|
||||
private int mWidth;
|
||||
|
||||
private static final FloatProperty<PredictedAppIcon> SLOT_MACHINE_TRANSLATION_Y =
|
||||
new FloatProperty<PredictedAppIcon>("slotMachineTranslationY") {
|
||||
private static final FloatProperty<PredictedAppIcon> SLOT_MACHINE_TRANSLATION_Y = new FloatProperty<PredictedAppIcon>(
|
||||
"slotMachineTranslationY") {
|
||||
@Override
|
||||
public void setValue(PredictedAppIcon predictedAppIcon, float transY) {
|
||||
predictedAppIcon.mSlotMachineIconTranslationY = transY;
|
||||
@@ -137,39 +120,50 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
Context mContext;
|
||||
|
||||
public PredictedAppIcon(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
mContext = context;
|
||||
mDeviceProfile = ActivityContext.lookupContext(context).getDeviceProfile();
|
||||
mNormalizedIconSize = Math.round(getIconSize() * ICON_VISIBLE_AREA_FACTOR);
|
||||
mNormalizedIconSize = IconNormalizer.getNormalizedCircleSize(getIconSize());
|
||||
int shadowSize = context.getResources().getDimensionPixelSize(
|
||||
R.dimen.blur_size_thin_outline);
|
||||
mShadowFilter = new BlurMaskFilter(shadowSize, BlurMaskFilter.Blur.OUTER);
|
||||
mShapePath = ThemeManager.INSTANCE.get(context).getIconShape().getPath(mNormalizedIconSize);
|
||||
mShapePath = GraphicsUtils.getShapePath(context, mNormalizedIconSize);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas canvas) {
|
||||
int count = canvas.save();
|
||||
boolean isSlotMachineAnimRunning = mSlotMachineIcon != null;
|
||||
boolean isSlotMachineAnimRunning = mSlotMachineAnim != null;
|
||||
if (!mIsPinned) {
|
||||
drawRingEffect(canvas);
|
||||
drawEffect(canvas);
|
||||
if (isSlotMachineAnimRunning) {
|
||||
// Clip to to outside of the ring during the slot machine animation.
|
||||
canvas.clipPath(mRingPath);
|
||||
}
|
||||
canvas.scale(1 - 2f * RING_EFFECT_RATIO, 1 - 2f * RING_EFFECT_RATIO,
|
||||
getWidth() * .5f, getHeight() * .5f);
|
||||
if (isSlotMachineAnimRunning) {
|
||||
canvas.translate(0, mSlotMachineIconTranslationY);
|
||||
mSlotMachineIcon.setBounds(mSlotIconBound);
|
||||
mSlotMachineIcon.draw(canvas);
|
||||
canvas.translate(0, getSlotMachineIconPlusSpacingSize());
|
||||
}
|
||||
canvas.translate(getWidth() * RING_EFFECT_RATIO, getHeight() * RING_EFFECT_RATIO);
|
||||
canvas.scale(1 - 2 * RING_EFFECT_RATIO, 1 - 2 * RING_EFFECT_RATIO);
|
||||
}
|
||||
if (isSlotMachineAnimRunning) {
|
||||
drawSlotMachineIcons(canvas);
|
||||
} else {
|
||||
super.onDraw(canvas);
|
||||
}
|
||||
super.onDraw(canvas);
|
||||
canvas.restoreToCount(count);
|
||||
}
|
||||
|
||||
private void drawSlotMachineIcons(Canvas canvas) {
|
||||
canvas.translate((getWidth() - getIconSize()) / 2f,
|
||||
(getHeight() - getIconSize()) / 2f + mSlotMachineIconTranslationY);
|
||||
for (Drawable icon : mSlotMachineIcons) {
|
||||
icon.setBounds(0, 0, getIconSize(), getIconSize());
|
||||
icon.draw(canvas);
|
||||
canvas.translate(0, getSlotMachineIconPlusSpacingSize());
|
||||
}
|
||||
}
|
||||
|
||||
private float getSlotMachineIconPlusSpacingSize() {
|
||||
return getIconSize() + getOutlineOffsetY();
|
||||
}
|
||||
@@ -185,95 +179,118 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
mIsDrawingDot = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the newInfo differs from the current getTag().
|
||||
*/
|
||||
private boolean shouldAnimateIconChange(WorkspaceItemInfo newInfo) {
|
||||
boolean changedIcons = getTag() instanceof WorkspaceItemInfo oldInfo
|
||||
&& oldInfo.getTargetComponent() != null
|
||||
&& newInfo.getTargetComponent() != null
|
||||
&& !oldInfo.getTargetComponent().equals(newInfo.getTargetComponent());
|
||||
return changedIcons && isShown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void applyIconAndLabel(ItemInfoWithIcon info) {
|
||||
super.applyIconAndLabel(info);
|
||||
public void applyFromWorkspaceItem(WorkspaceItemInfo info, boolean animate, int staggerIndex) {
|
||||
// Create the slot machine animation first, since it uses the current icon to
|
||||
// start.
|
||||
Animator slotMachineAnim = animate
|
||||
? createSlotMachineAnim(Collections.singletonList(info.bitmap), false)
|
||||
: null;
|
||||
super.applyFromWorkspaceItem(info, animate, staggerIndex);
|
||||
int oldPlateColor = mPlateColor;
|
||||
|
||||
int newPlateColor;
|
||||
if (getIcon().isThemed()) {
|
||||
mPlateColor.endColor = ColorTokens.PredictedPlateColor.resolveColor(getContext());
|
||||
newPlateColor = ColorTokens.PredictedPlateColor.resolveColor(getContext());
|
||||
} else {
|
||||
float[] hctPlateColor = new float[3];
|
||||
ColorUtils.colorToM3HCT(mDotParams.appColor, hctPlateColor);
|
||||
mPlateColor.endColor = ColorUtils.M3HCTToColor(hctPlateColor[0], 36, 85);
|
||||
newPlateColor = ColorUtils.M3HCTToColor(hctPlateColor[0], 36, 85);
|
||||
}
|
||||
mPlateColor.onUpdate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to apply the icon with animation and returns true if the icon was indeed animated
|
||||
*/
|
||||
public boolean applyFromWorkspaceItemWithAnimation(WorkspaceItemInfo info, int staggerIndex) {
|
||||
boolean animate = shouldAnimateIconChange(info);
|
||||
Drawable oldIcon = getIcon();
|
||||
int oldPlateColor = mPlateColor.currentColor;
|
||||
applyFromWorkspaceItem(info);
|
||||
|
||||
setContentDescription(
|
||||
mIsPinned ? info.contentDescription :
|
||||
getContext().getString(R.string.hotseat_prediction_content_description,
|
||||
info.contentDescription));
|
||||
|
||||
if (!animate) {
|
||||
mPlateColor.startColor = mPlateColor.endColor;
|
||||
mPlateColor.progress.value = 1;
|
||||
mPlateColor.onUpdate();
|
||||
mPlateColor = newPlateColor;
|
||||
}
|
||||
if (mIsPinned) {
|
||||
setContentDescription(info.contentDescription);
|
||||
} else {
|
||||
mPlateColor.startColor = oldPlateColor;
|
||||
mPlateColor.progress.value = 0;
|
||||
mPlateColor.onUpdate();
|
||||
setContentDescription(
|
||||
getContext().getString(R.string.hotseat_prediction_content_description,
|
||||
info.contentDescription));
|
||||
}
|
||||
|
||||
if (animate) {
|
||||
ValueAnimator plateColorAnim = ValueAnimator.ofObject(new ArgbEvaluator(),
|
||||
oldPlateColor, newPlateColor);
|
||||
plateColorAnim.addUpdateListener(valueAnimator -> {
|
||||
mPlateColor = (int) valueAnimator.getAnimatedValue();
|
||||
invalidate();
|
||||
});
|
||||
AnimatorSet changeIconAnim = new AnimatorSet();
|
||||
|
||||
ObjectAnimator plateColorAnim =
|
||||
ObjectAnimator.ofFloat(mPlateColor.progress, AnimatedFloat.VALUE, 0, 1);
|
||||
plateColorAnim.setAutoCancel(true);
|
||||
changeIconAnim.play(plateColorAnim);
|
||||
|
||||
if (!mIsPinned && oldIcon != null) {
|
||||
// Play the slot machine icon
|
||||
mSlotMachineIcon = oldIcon;
|
||||
|
||||
float finalTrans = -getSlotMachineIconPlusSpacingSize();
|
||||
Keyframe[] keyframes = new Keyframe[] {
|
||||
Keyframe.ofFloat(0f, 0f),
|
||||
Keyframe.ofFloat(0.82f, finalTrans - getOutlineOffsetY() / 2f), // Overshoot
|
||||
Keyframe.ofFloat(1f, finalTrans) // Ease back into the final position
|
||||
};
|
||||
keyframes[1].setInterpolator(ACCELERATE_DECELERATE);
|
||||
keyframes[2].setInterpolator(ACCELERATE_DECELERATE);
|
||||
|
||||
ObjectAnimator slotMachineAnim = ObjectAnimator.ofPropertyValuesHolder(this,
|
||||
PropertyValuesHolder.ofKeyframe(SLOT_MACHINE_TRANSLATION_Y, keyframes));
|
||||
slotMachineAnim.addListener(AnimatorListeners.forEndCallback(() -> {
|
||||
mSlotMachineIcon = null;
|
||||
mSlotMachineIconTranslationY = 0;
|
||||
invalidate();
|
||||
}));
|
||||
slotMachineAnim.setAutoCancel(true);
|
||||
if (slotMachineAnim != null) {
|
||||
changeIconAnim.play(slotMachineAnim);
|
||||
}
|
||||
|
||||
changeIconAnim.play(plateColorAnim);
|
||||
changeIconAnim.setStartDelay(staggerIndex * ICON_CHANGE_ANIM_STAGGER);
|
||||
changeIconAnim.setDuration(ICON_CHANGE_ANIM_DURATION).start();
|
||||
}
|
||||
return animate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Animator that translates the given icons in a "slot-machine"
|
||||
* fashion, beginning
|
||||
* and ending with the original icon.
|
||||
*/
|
||||
public @Nullable Animator createSlotMachineAnim(List<BitmapInfo> iconsToAnimate) {
|
||||
return createSlotMachineAnim(iconsToAnimate, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an Animator that translates the given icons in a "slot-machine"
|
||||
* fashion, beginning
|
||||
* with the original icon, then cycling through the given icons, optionally
|
||||
* ending back with
|
||||
* the original icon.
|
||||
*
|
||||
* @param endWithOriginalIcon Whether we should land back on the icon we started
|
||||
* with, rather
|
||||
* than the last item in iconsToAnimate.
|
||||
*/
|
||||
public @Nullable Animator createSlotMachineAnim(List<BitmapInfo> iconsToAnimate,
|
||||
boolean endWithOriginalIcon) {
|
||||
if (mIsPinned || iconsToAnimate == null || iconsToAnimate.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
if (mSlotMachineAnim != null) {
|
||||
mSlotMachineAnim.end();
|
||||
}
|
||||
|
||||
// Bookend the other animating icons with the original icon on both ends.
|
||||
mSlotMachineIcons = new ArrayList<>(iconsToAnimate.size() + 2);
|
||||
mSlotMachineIcons.add(getIcon());
|
||||
iconsToAnimate.stream()
|
||||
.map(iconInfo -> iconInfo.newIcon(mContext))
|
||||
.forEach(mSlotMachineIcons::add);
|
||||
if (endWithOriginalIcon) {
|
||||
mSlotMachineIcons.add(getIcon());
|
||||
}
|
||||
|
||||
float finalTrans = -getSlotMachineIconPlusSpacingSize() * (mSlotMachineIcons.size() - 1);
|
||||
Keyframe[] keyframes = new Keyframe[] {
|
||||
Keyframe.ofFloat(0f, 0f),
|
||||
Keyframe.ofFloat(0.82f, finalTrans - getOutlineOffsetY() / 2f), // Overshoot
|
||||
Keyframe.ofFloat(1f, finalTrans) // Ease back into the final position
|
||||
};
|
||||
keyframes[1].setInterpolator(ACCELERATE_DECELERATE);
|
||||
keyframes[2].setInterpolator(ACCELERATE_DECELERATE);
|
||||
|
||||
mSlotMachineAnim = ObjectAnimator.ofPropertyValuesHolder(this,
|
||||
PropertyValuesHolder.ofKeyframe(SLOT_MACHINE_TRANSLATION_Y, keyframes));
|
||||
mSlotMachineAnim.addListener(AnimatorListeners.forEndCallback(() -> {
|
||||
mSlotMachineIcons = null;
|
||||
mSlotMachineAnim = null;
|
||||
mSlotMachineIconTranslationY = 0;
|
||||
invalidate();
|
||||
}));
|
||||
return mSlotMachineAnim;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes prediction ring from app icon
|
||||
*/
|
||||
public void pin(WorkspaceItemInfo info) {
|
||||
if (mIsPinned) return;
|
||||
if (mIsPinned)
|
||||
return;
|
||||
mIsPinned = true;
|
||||
applyFromWorkspaceItem(info);
|
||||
setOnLongClickListener(ItemLongClickListener.INSTANCE_WORKSPACE);
|
||||
@@ -305,13 +322,7 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
}
|
||||
|
||||
private int getOutlineOffsetX() {
|
||||
int measuredWidth = getMeasuredWidth();
|
||||
if (mDisplay != DISPLAY_TASKBAR) {
|
||||
Log.d("b/387844520", "getOutlineOffsetX: measured width = " + measuredWidth
|
||||
+ ", mNormalizedIconSize = " + mNormalizedIconSize
|
||||
+ ", last updated width = " + mWidth);
|
||||
}
|
||||
return (mWidth - mNormalizedIconSize) / 2;
|
||||
return (getMeasuredWidth() - mNormalizedIconSize) / 2;
|
||||
}
|
||||
|
||||
private int getOutlineOffsetY() {
|
||||
@@ -324,11 +335,6 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
@Override
|
||||
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
|
||||
super.onSizeChanged(w, h, oldw, oldh);
|
||||
mWidth = w;
|
||||
mSlotIconBound.offsetTo((w - getIconSize()) / 2, (h - getIconSize()) / 2);
|
||||
if (mDisplay != DISPLAY_TASKBAR) {
|
||||
Log.d("b/387844520", "calling updateRingPath from onSizeChanged");
|
||||
}
|
||||
updateRingPath();
|
||||
}
|
||||
|
||||
@@ -339,13 +345,18 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
}
|
||||
|
||||
private void updateRingPath() {
|
||||
mRingPath.reset();
|
||||
mTmpMatrix.reset();
|
||||
mTmpMatrix.setTranslate(getOutlineOffsetX(), getOutlineOffsetY());
|
||||
mRingPath.addPath(mShapePath, mTmpMatrix);
|
||||
boolean isBadged = false;
|
||||
if (getTag() instanceof WorkspaceItemInfo) {
|
||||
WorkspaceItemInfo info = (WorkspaceItemInfo) getTag();
|
||||
isBadged = !Process.myUserHandle().equals(info.user)
|
||||
|| info.itemType == LauncherSettings.Favorites.ITEM_TYPE_DEEP_SHORTCUT;
|
||||
}
|
||||
|
||||
FastBitmapDrawable icon = getIcon();
|
||||
if (icon != null && icon.getBadge() != null) {
|
||||
mRingPath.reset();
|
||||
mTmpMatrix.setTranslate(getOutlineOffsetX(), getOutlineOffsetY());
|
||||
|
||||
mRingPath.addPath(mShapePath, mTmpMatrix);
|
||||
if (isBadged) {
|
||||
float outlineSize = mNormalizedIconSize * RING_EFFECT_RATIO;
|
||||
float iconSize = getIconSize() * (1 - 2 * RING_EFFECT_RATIO);
|
||||
float badgeSize = LauncherIcons.getBadgeSizeForIconSize((int) iconSize) + outlineSize;
|
||||
@@ -355,73 +366,19 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
mTmpMatrix.preTranslate(-mNormalizedIconSize, -mNormalizedIconSize);
|
||||
mRingPath.addPath(mShapePath, mTmpMatrix);
|
||||
}
|
||||
invalidate();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setForceHideRing(boolean forceHideRing) {
|
||||
if (mForceHideRing == forceHideRing) {
|
||||
return;
|
||||
}
|
||||
mForceHideRing = forceHideRing;
|
||||
|
||||
if (forceHideRing) {
|
||||
invalidate();
|
||||
} else {
|
||||
animateRingScale(RING_SCALE_START_VALUE, 1);
|
||||
}
|
||||
}
|
||||
|
||||
private void cancelRingScaleAnim() {
|
||||
if (mRingScaleAnim != null) {
|
||||
mRingScaleAnim.cancel();
|
||||
}
|
||||
}
|
||||
|
||||
private void animateRingScale(float... ringScale) {
|
||||
cancelRingScaleAnim();
|
||||
mRingScaleAnim = ObjectAnimator.ofFloat(this, RING_SCALE_PROPERTY, ringScale);
|
||||
mRingScaleAnim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
mRingScaleAnim = null;
|
||||
}
|
||||
});
|
||||
mRingScaleAnim.start();
|
||||
}
|
||||
|
||||
private void drawRingEffect(Canvas canvas) {
|
||||
// Don't draw ring effect if item is about to be dragged or if the icon is not visible.
|
||||
if (mDrawForDrag || !mIsIconVisible || mForceHideRing) {
|
||||
private void drawEffect(Canvas canvas) {
|
||||
// Don't draw ring effect if item is about to be dragged.
|
||||
if (mDrawForDrag) {
|
||||
return;
|
||||
}
|
||||
mIconRingPaint.setColor(RING_SHADOW_COLOR);
|
||||
mIconRingPaint.setMaskFilter(mShadowFilter);
|
||||
int count = canvas.save();
|
||||
if (Flags.enableLauncherIconShapes()) {
|
||||
// Scale canvas properly to for ring to be inner stroke and not exceed bounds.
|
||||
// Since STROKE draws half on either side of Path, scale canvas down by 1x stroke ratio.
|
||||
canvas.scale(
|
||||
mRingScale * (1f - RING_EFFECT_RATIO),
|
||||
mRingScale * (1f - RING_EFFECT_RATIO),
|
||||
getWidth() / 2f,
|
||||
getHeight() / 2f);
|
||||
} else if (Float.compare(1, mRingScale) != 0) {
|
||||
canvas.scale(mRingScale, mRingScale, getWidth() / 2f, getHeight() / 2f);
|
||||
}
|
||||
// Draw ring shadow around canvas.
|
||||
canvas.drawPath(mRingPath, mIconRingPaint);
|
||||
mIconRingPaint.setColor(mPlateColor.currentColor);
|
||||
if (Flags.enableLauncherIconShapes()) {
|
||||
mIconRingPaint.setStrokeWidth(getWidth() * RING_EFFECT_RATIO);
|
||||
// Using FILL_AND_STROKE as there is still some gap to fill,
|
||||
// between inner curve of ring / outer curve of icon.
|
||||
mIconRingPaint.setStyle(Paint.Style.FILL_AND_STROKE);
|
||||
}
|
||||
mIconRingPaint.setColor(mPlateColor);
|
||||
mIconRingPaint.setMaskFilter(null);
|
||||
// Draw ring around canvas.
|
||||
canvas.drawPath(mRingPath, mIconRingPaint);
|
||||
canvas.restoreToCount(count);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -470,21 +427,6 @@ public class PredictedAppIcon extends DoubleShadowBubbleTextView {
|
||||
return icon;
|
||||
}
|
||||
|
||||
private class AnimColorHolder {
|
||||
|
||||
public final AnimatedFloat progress = new AnimatedFloat(this::onUpdate, 1);
|
||||
public final ArgbEvaluator evaluator = ArgbEvaluator.getInstance();
|
||||
public Integer startColor = 0;
|
||||
public Integer endColor = 0;
|
||||
|
||||
public int currentColor = 0;
|
||||
|
||||
private void onUpdate() {
|
||||
currentColor = (Integer) evaluator.evaluate(progress.value, startColor, endColor);
|
||||
invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws Predicted Icon outline on cell layout
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
/**
|
||||
* Copyright (C) 2022 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.uioverrides;
|
||||
|
||||
import static com.android.launcher3.widget.LauncherWidgetHolder.APPWIDGET_HOST_ID;
|
||||
|
||||
import android.appwidget.AppWidgetHost;
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.Context;
|
||||
import android.os.Looper;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.launcher3.LauncherAppState;
|
||||
import com.android.launcher3.util.Executors;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
|
||||
import com.android.launcher3.widget.LauncherWidgetHolder;
|
||||
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
/**
|
||||
* {@link AppWidgetHost} that is used to receive the changes to the widgets without
|
||||
* storing any {@code Activity} info like that of the launcher.
|
||||
*/
|
||||
final class QuickstepAppWidgetHost extends AppWidgetHost {
|
||||
private final @NonNull Context mContext;
|
||||
private final @NonNull IntConsumer mAppWidgetRemovedCallback;
|
||||
private final @NonNull LauncherWidgetHolder.ProviderChangedListener mProvidersChangedListener;
|
||||
|
||||
QuickstepAppWidgetHost(@NonNull Context context, @NonNull IntConsumer appWidgetRemovedCallback,
|
||||
@NonNull LauncherWidgetHolder.ProviderChangedListener listener,
|
||||
@NonNull Looper looper) {
|
||||
super(context, APPWIDGET_HOST_ID, null, looper);
|
||||
mContext = context;
|
||||
mAppWidgetRemovedCallback = appWidgetRemovedCallback;
|
||||
mProvidersChangedListener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProvidersChanged() {
|
||||
mProvidersChangedListener.notifyWidgetProvidersChanged();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppWidgetRemoved(int appWidgetId) {
|
||||
// Route the call via model thread, in case it comes while a loader-bind is in progress
|
||||
Executors.MODEL_EXECUTOR.execute(
|
||||
() -> Executors.MAIN_EXECUTOR.execute(
|
||||
() -> mAppWidgetRemovedCallback.accept(appWidgetId)));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProviderChanged(int appWidgetId, @NonNull AppWidgetProviderInfo appWidget) {
|
||||
LauncherAppWidgetProviderInfo info = LauncherAppWidgetProviderInfo.fromProviderInfo(
|
||||
mContext, appWidget);
|
||||
super.onProviderChanged(appWidgetId, info);
|
||||
// The super method updates the dimensions of the providerInfo. Update the
|
||||
// launcher spans accordingly.
|
||||
info.initSpans(mContext, LauncherAppState.getIDP(mContext));
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import android.app.ActivityTaskManager;
|
||||
import android.app.IActivityTaskManagerHidden;
|
||||
import android.app.PendingIntent;
|
||||
import android.content.Intent;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
@@ -30,19 +31,20 @@ import android.view.View;
|
||||
import android.widget.RemoteViews;
|
||||
import android.window.SplashScreen;
|
||||
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.logging.StatsLogManager;
|
||||
import com.android.launcher3.model.data.ItemInfo;
|
||||
import com.android.launcher3.util.ActivityOptionsWrapper;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetHostView;
|
||||
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import dev.rikka.tools.refine.Refine;
|
||||
import app.lawnchair.LawnchairApp;
|
||||
import dev.rikka.tools.refine.Refine;
|
||||
|
||||
/** Provides a Quickstep specific animation when launching an activity from an app widget. */
|
||||
class QuickstepInteractionHandler implements RemoteViews.InteractionHandler,
|
||||
Consumer<LauncherAppWidgetHostView> {
|
||||
/**
|
||||
* Provides a Quickstep specific animation when launching an activity from an
|
||||
* app widget.
|
||||
*/
|
||||
public class QuickstepInteractionHandler implements RemoteViews.InteractionHandler {
|
||||
|
||||
private static final String TAG = "QuickstepInteractionHandler";
|
||||
|
||||
@@ -52,15 +54,10 @@ class QuickstepInteractionHandler implements RemoteViews.InteractionHandler,
|
||||
mLauncher = launcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(LauncherAppWidgetHostView host) {
|
||||
host.setInteractionHandler(this);
|
||||
}
|
||||
|
||||
@SuppressWarnings("NewApi")
|
||||
@Override
|
||||
public boolean onInteraction(View view, PendingIntent pendingIntent,
|
||||
RemoteViews.RemoteResponse remoteResponse) {
|
||||
RemoteViews.RemoteResponse remoteResponse) {
|
||||
LauncherAppWidgetHostView hostView = findHostViewAncestor(view);
|
||||
if (hostView == null) {
|
||||
Log.e(TAG, "View did not have a LauncherAppWidgetHostView ancestor.");
|
||||
@@ -75,14 +72,8 @@ class QuickstepInteractionHandler implements RemoteViews.InteractionHandler,
|
||||
return true;
|
||||
}
|
||||
Pair<Intent, ActivityOptions> options = remoteResponse.getLaunchOptions(view);
|
||||
|
||||
ActivityOptionsWrapper activityOptions = null;
|
||||
try {
|
||||
activityOptions = mLauncher.getAppTransitionManager()
|
||||
.getActivityLaunchOptions(hostView, (ItemInfo) hostView.getTag());
|
||||
} catch (NullPointerException e) {
|
||||
Log.e("pE(C7evQZDJ)", "Failed to get activity launch options");
|
||||
}
|
||||
ActivityOptionsWrapper activityOptions = mLauncher.getAppTransitionManager()
|
||||
.getActivityLaunchOptions(hostView);
|
||||
if (!pendingIntent.isActivity()) {
|
||||
// In the event this pending intent eventually launches an activity, i.e. a trampoline,
|
||||
// use the Quickstep transition animation.
|
||||
@@ -98,8 +89,7 @@ class QuickstepInteractionHandler implements RemoteViews.InteractionHandler,
|
||||
pendingIntent.getCreatorPackage(),
|
||||
activityOptions.options.getRemoteAnimationAdapter());
|
||||
}
|
||||
} catch (NullPointerException | RemoteException e) {
|
||||
// pE-TODO(C7evQZDJ): Remove NullPointerException after fixing
|
||||
} catch (RemoteException e) {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
||||
@@ -111,24 +101,16 @@ class QuickstepInteractionHandler implements RemoteViews.InteractionHandler,
|
||||
} catch (Throwable t) {
|
||||
// ignore
|
||||
}
|
||||
// pE-TODO(C7evQZDJ): Remove activityOptions null check
|
||||
if (activityOptions != null) {
|
||||
options = Pair.create(options.first, activityOptions.options);
|
||||
}
|
||||
options = Pair.create(options.first, activityOptions.options);
|
||||
if (pendingIntent.isActivity()) {
|
||||
logAppLaunch(hostView.getTag());
|
||||
}
|
||||
if (activityOptions != null) {
|
||||
return RemoteViews.startPendingIntent(hostView, pendingIntent, options);
|
||||
} else {
|
||||
Log.d("pE(C7evQZDJ)", "activityOptions is null!");
|
||||
return RemoteViews.startPendingIntent(hostView, pendingIntent,
|
||||
remoteResponse.getLaunchOptions(view));
|
||||
}
|
||||
return RemoteViews.startPendingIntent(hostView, pendingIntent, options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs that the app was launched from the widget.
|
||||
*
|
||||
* @param itemInfo the widget info.
|
||||
*/
|
||||
private void logAppLaunch(Object itemInfo) {
|
||||
@@ -141,7 +123,8 @@ class QuickstepInteractionHandler implements RemoteViews.InteractionHandler,
|
||||
|
||||
private LauncherAppWidgetHostView findHostViewAncestor(View v) {
|
||||
while (v != null) {
|
||||
if (v instanceof LauncherAppWidgetHostView) return (LauncherAppWidgetHostView) v;
|
||||
if (v instanceof LauncherAppWidgetHostView)
|
||||
return (LauncherAppWidgetHostView) v;
|
||||
v = (View) v.getParent();
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -16,13 +16,11 @@
|
||||
package com.android.launcher3.uioverrides;
|
||||
|
||||
import static android.app.ActivityTaskManager.INVALID_TASK_ID;
|
||||
import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
|
||||
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_FOCUSED;
|
||||
import static android.window.DesktopModeFlags.ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY;
|
||||
|
||||
import static com.android.app.animation.Interpolators.EMPHASIZED;
|
||||
import static com.android.internal.jank.Cuj.CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE;
|
||||
import static com.android.launcher3.Flags.enableExpressiveDismissTaskMotion;
|
||||
import static com.android.launcher3.Flags.enablePredictiveBackGesture;
|
||||
import static com.android.launcher3.Flags.enableUnfoldStateAnimation;
|
||||
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.PENDING_SPLIT_SELECT_INFO;
|
||||
import static com.android.launcher3.LauncherConstants.SavedInstanceKeys.RUNTIME_STATE;
|
||||
@@ -37,17 +35,13 @@ import static com.android.launcher3.LauncherState.NO_OFFSET;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW_MODAL_TASK;
|
||||
import static com.android.launcher3.LauncherState.OVERVIEW_SPLIT_SELECT;
|
||||
import static com.android.launcher3.Utilities.ATLEAST_BAKLAVA;
|
||||
import static com.android.launcher3.Utilities.ATLEAST_S_V2;
|
||||
import static com.android.launcher3.Utilities.ATLEAST_T;
|
||||
import static com.android.launcher3.Utilities.isRtl;
|
||||
import static com.android.launcher3.compat.AccessibilityManagerCompat.sendCustomAccessibilityEvent;
|
||||
import static com.android.launcher3.config.FeatureFlags.enableSplitContextually;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_APP_LAUNCH_TAP;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_HOME;
|
||||
import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED;
|
||||
import static com.android.launcher3.popup.QuickstepSystemShortcut.getSplitSelectShortcutByPosition;
|
||||
import static com.android.launcher3.popup.SystemShortcut.APP_INFO;
|
||||
import static com.android.launcher3.popup.SystemShortcut.BUBBLE_SHORTCUT;
|
||||
import static com.android.launcher3.popup.SystemShortcut.DONT_SUGGEST_APP;
|
||||
import static com.android.launcher3.popup.SystemShortcut.INSTALL;
|
||||
import static com.android.launcher3.popup.SystemShortcut.PRIVATE_PROFILE_INSTALL;
|
||||
@@ -66,6 +60,9 @@ 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.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY;
|
||||
import static com.android.window.flags2.Flags.enableDesktopWindowingMode;
|
||||
import static com.android.window.flags2.Flags.enableDesktopWindowingWallpaperActivity;
|
||||
import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
|
||||
|
||||
import android.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
@@ -74,27 +71,23 @@ import android.app.ActivityOptions;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentSender;
|
||||
import android.content.pm.ShortcutInfo;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.hardware.display.DisplayManager;
|
||||
import android.media.permission.SafeCloseable;
|
||||
import android.os.Build;
|
||||
import android.os.Build.VERSION;
|
||||
import android.os.Build.VERSION_CODES;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.IRemoteCallback;
|
||||
import android.os.Looper;
|
||||
import android.os.SystemProperties;
|
||||
import android.os.UserHandle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.Display;
|
||||
import android.view.HapticFeedbackConstants;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.View;
|
||||
import android.widget.AnalogClock;
|
||||
import android.widget.TextClock;
|
||||
import android.window.BackEvent;
|
||||
import android.window.OnBackAnimationCallback;
|
||||
import android.window.OnBackInvokedDispatcher;
|
||||
@@ -104,7 +97,6 @@ import android.window.SplashScreen;
|
||||
import androidx.annotation.BinderThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
||||
import com.android.app.viewcapture.ViewCaptureFactory;
|
||||
import com.android.launcher3.AbstractFloatingView;
|
||||
@@ -114,13 +106,13 @@ import com.android.launcher3.InvariantDeviceProfile;
|
||||
import com.android.launcher3.Launcher;
|
||||
import com.android.launcher3.LauncherSettings.Favorites;
|
||||
import com.android.launcher3.LauncherState;
|
||||
import com.android.launcher3.OnBackPressedHandler;
|
||||
import com.android.launcher3.QuickstepAccessibilityDelegate;
|
||||
import com.android.launcher3.QuickstepTransitionManager;
|
||||
import com.android.launcher3.R;
|
||||
import com.android.launcher3.Utilities;
|
||||
import com.android.launcher3.Workspace;
|
||||
import com.android.launcher3.accessibility.LauncherAccessibilityDelegate;
|
||||
import com.android.launcher3.allapps.AllAppsRecyclerView;
|
||||
import com.android.launcher3.anim.AnimatorPlaybackController;
|
||||
import com.android.launcher3.anim.PendingAnimation;
|
||||
import com.android.launcher3.apppairs.AppPairIcon;
|
||||
@@ -142,9 +134,9 @@ import com.android.launcher3.statemanager.StateManager.AtomicAnimationFactory;
|
||||
import com.android.launcher3.statemanager.StateManager.StateHandler;
|
||||
import com.android.launcher3.taskbar.LauncherTaskbarUIController;
|
||||
import com.android.launcher3.taskbar.TaskbarManager;
|
||||
import com.android.launcher3.taskbar.TaskbarUIController;
|
||||
import com.android.launcher3.testing.TestLogging;
|
||||
import com.android.launcher3.testing.shared.TestProtocol;
|
||||
import com.android.launcher3.uioverrides.QuickstepWidgetHolder.QuickstepHolderFactory;
|
||||
import com.android.launcher3.uioverrides.states.QuickstepAtomicAnimationFactory;
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.NavBarToHomeTouchController;
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.NoButtonNavbarToOverviewTouchController;
|
||||
@@ -152,10 +144,7 @@ import com.android.launcher3.uioverrides.touchcontrollers.NoButtonQuickSwitchTou
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.PortraitStatesTouchController;
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.QuickSwitchTouchController;
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.StatusBarTouchController;
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.TaskViewDismissTouchController;
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.TaskViewLaunchTouchController;
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.TaskViewRecentsTouchContext;
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchControllerDeprecated;
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.TaskViewTouchController;
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.TransposedQuickSwitchTouchController;
|
||||
import com.android.launcher3.uioverrides.touchcontrollers.TwoButtonNavbarTouchController;
|
||||
import com.android.launcher3.util.ActivityOptionsWrapper;
|
||||
@@ -169,23 +158,20 @@ import com.android.launcher3.util.RunnableList;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.SplitPositionOption;
|
||||
import com.android.launcher3.util.SplitConfigurationOptions.SplitSelectSource;
|
||||
import com.android.launcher3.util.StableViewInfo;
|
||||
import com.android.launcher3.util.StartActivityParams;
|
||||
import com.android.launcher3.util.TouchController;
|
||||
import com.android.launcher3.views.FloatingIconView;
|
||||
import com.android.launcher3.widget.LauncherWidgetHolder;
|
||||
import com.android.quickstep.OverviewCommandHelper;
|
||||
import com.android.quickstep.OverviewComponentObserver;
|
||||
import com.android.quickstep.OverviewComponentObserver.OverviewChangeListener;
|
||||
import com.android.quickstep.RecentsAnimationDeviceState;
|
||||
import com.android.quickstep.RecentsModel;
|
||||
import com.android.quickstep.SystemUiProxy;
|
||||
import com.android.quickstep.TaskUtils;
|
||||
import com.android.quickstep.TouchInteractionService.TISBinder;
|
||||
import com.android.quickstep.util.ActiveGestureProtoLogProxy;
|
||||
import com.android.quickstep.util.AsyncClockEventDelegate;
|
||||
import com.android.quickstep.util.GroupTask;
|
||||
import com.android.quickstep.util.LauncherUnfoldAnimationController;
|
||||
import com.android.quickstep.util.QuickstepOnboardingPrefs;
|
||||
import com.android.quickstep.util.SplitSelectStateController;
|
||||
import com.android.quickstep.util.SplitTask;
|
||||
import com.android.quickstep.util.SplitToWorkspaceController;
|
||||
import com.android.quickstep.util.SplitWithKeyboardShortcutController;
|
||||
import com.android.quickstep.util.TISBindHelper;
|
||||
@@ -196,7 +182,6 @@ import com.android.quickstep.views.OverviewActionsView;
|
||||
import com.android.quickstep.views.RecentsView;
|
||||
import com.android.quickstep.views.RecentsViewContainer;
|
||||
import com.android.quickstep.views.TaskView;
|
||||
import com.android.systemui.animation.back.FlingOnBackAnimationCallback;
|
||||
import com.android.systemui.shared.recents.model.Task;
|
||||
import com.android.systemui.unfold.RemoteUnfoldSharedComponent;
|
||||
import com.android.systemui.unfold.UnfoldTransitionFactory;
|
||||
@@ -206,10 +191,9 @@ import com.android.systemui.unfold.config.UnfoldTransitionConfig;
|
||||
import com.android.systemui.unfold.dagger.UnfoldMain;
|
||||
import com.android.systemui.unfold.progress.RemoteUnfoldTransitionReceiver;
|
||||
import com.android.systemui.unfold.updates.RotationChangeProvider;
|
||||
import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper;
|
||||
import com.android.wm.shell.shared.bubbles.BubbleBarLocation;
|
||||
import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
|
||||
|
||||
import app.lawnchair.LawnchairApp;
|
||||
import app.lawnchair.compat.LawnchairQuickstepCompat;
|
||||
import kotlin.Unit;
|
||||
|
||||
import java.io.FileDescriptor;
|
||||
@@ -224,21 +208,19 @@ import java.util.function.BiConsumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import app.lawnchair.LawnchairApp;
|
||||
import app.lawnchair.compat.LawnchairQuickstepCompat;
|
||||
|
||||
public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
SystemShortcut.BubbleActivityStarter {
|
||||
public class QuickstepLauncher extends Launcher implements RecentsViewContainer {
|
||||
private static final boolean TRACE_LAYOUTS =
|
||||
SystemProperties.getBoolean("persist.debug.trace_layouts", false);
|
||||
private static final String TRACE_RELAYOUT_CLASS =
|
||||
SystemProperties.get("persist.debug.trace_request_layout_class", null);
|
||||
public static final boolean GO_LOW_RAM_RECENTS_ENABLED = false;
|
||||
|
||||
protected static final String RING_APPEAR_ANIMATION_PREFIX = "RingAppearAnimation\t";
|
||||
|
||||
private FixedContainerItems mAllAppsPredictions;
|
||||
private HotseatPredictionController mHotseatPredictionController;
|
||||
private DepthController mDepthController;
|
||||
private @Nullable DesktopVisibilityController mDesktopVisibilityController;
|
||||
private QuickstepTransitionManager mAppTransitionManager;
|
||||
|
||||
private OverviewActionsView<?> mActionsView;
|
||||
@@ -251,7 +233,6 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
private SplitSelectStateController mSplitSelectStateController;
|
||||
private SplitWithKeyboardShortcutController mSplitWithKeyboardShortcutController;
|
||||
private SplitToWorkspaceController mSplitToWorkspaceController;
|
||||
private BubbleBarLocation mBubbleBarLocation;
|
||||
|
||||
/**
|
||||
* If Launcher restarted while in the middle of an Overview split select, it needs this data to
|
||||
@@ -268,57 +249,35 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
|
||||
private boolean mIsPredictiveBackToHomeInProgress;
|
||||
|
||||
private boolean mCanShowAllAppsEducationView;
|
||||
|
||||
private boolean mIsOverlayVisible;
|
||||
|
||||
private final OverviewChangeListener mOverviewChangeListener = this::onOverviewTargetChanged;
|
||||
|
||||
private final TaskViewRecentsTouchContext mTaskViewRecentsTouchContext =
|
||||
new TaskViewRecentsTouchContext() {
|
||||
@Override
|
||||
public boolean isRecentsInteractive() {
|
||||
return isInState(OVERVIEW) || isInState(OVERVIEW_MODAL_TASK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRecentsModal() {
|
||||
return isInState(OVERVIEW_MODAL_TASK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUserControlledAnimationCreated(
|
||||
AnimatorPlaybackController animController) {
|
||||
getStateManager().setCurrentUserControlledAnimation(animController);
|
||||
}
|
||||
};
|
||||
|
||||
public static QuickstepLauncher getLauncher(Context context) {
|
||||
return fromContext(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void setupViews() {
|
||||
getAppWidgetHolder().setOnViewCreationCallback(new QuickstepInteractionHandler(this));
|
||||
super.setupViews();
|
||||
|
||||
mActionsView = findViewById(R.id.overview_actions_view);
|
||||
RecentsView<?,?> overviewPanel = getOverviewPanel();
|
||||
SystemUiProxy systemUiProxy = SystemUiProxy.INSTANCE.get(this);
|
||||
mSplitSelectStateController =
|
||||
new SplitSelectStateController(this, getStateManager(),
|
||||
new SplitSelectStateController(this, mHandler, getStateManager(),
|
||||
getDepthController(), getStatsLogManager(),
|
||||
systemUiProxy, RecentsModel.INSTANCE.get(this),
|
||||
() -> onStateBack());
|
||||
if (DesktopModeStatus.canEnterDesktopMode(this) && LawnchairApp.isRecentsEnabled()) {
|
||||
RecentsAnimationDeviceState deviceState = new RecentsAnimationDeviceState(asContext());
|
||||
// TODO(b/337863494): Explore use of the same OverviewComponentObserver across launcher
|
||||
OverviewComponentObserver overviewComponentObserver = new OverviewComponentObserver(
|
||||
asContext(), deviceState);
|
||||
if (enableDesktopWindowingMode() && LawnchairApp.isRecentsEnabled()) {
|
||||
mDesktopRecentsTransitionController = new DesktopRecentsTransitionController(
|
||||
getStateManager(), systemUiProxy, LawnchairApp.getInstance().getIApplicationThread(),
|
||||
getDepthController());
|
||||
}
|
||||
overviewPanel.init(mActionsView, mSplitSelectStateController,
|
||||
mDesktopRecentsTransitionController);
|
||||
mSplitWithKeyboardShortcutController = new SplitWithKeyboardShortcutController(
|
||||
this, mSplitSelectStateController);
|
||||
mSplitWithKeyboardShortcutController = new SplitWithKeyboardShortcutController(this,
|
||||
mSplitSelectStateController, overviewComponentObserver, deviceState);
|
||||
mSplitToWorkspaceController = new SplitToWorkspaceController(this,
|
||||
mSplitSelectStateController);
|
||||
mActionsView.updateDimension(getDeviceProfile(), overviewPanel.getLastComputedTaskSize());
|
||||
@@ -332,8 +291,11 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
|
||||
mTISBindHelper = new TISBindHelper(this, this::onTISConnected);
|
||||
mDepthController = new DepthController(this);
|
||||
if (DesktopModeStatus.canEnterDesktopModeOrShowAppHandle(this)) {
|
||||
mSplitSelectStateController.initSplitFromDesktopController(this);
|
||||
if (enableDesktopWindowingMode()) {
|
||||
mDesktopVisibilityController = new DesktopVisibilityController(this);
|
||||
mDesktopVisibilityController.registerSystemUiListener();
|
||||
mSplitSelectStateController.initSplitFromDesktopController(this,
|
||||
overviewComponentObserver);
|
||||
}
|
||||
mHotseatPredictionController = new HotseatPredictionController(this);
|
||||
|
||||
@@ -347,7 +309,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
|
||||
@Override
|
||||
public void logAppLaunch(StatsLogManager statsLogManager, ItemInfo info,
|
||||
InstanceId instanceId) {
|
||||
InstanceId instanceId) {
|
||||
// If the app launch is from any of the surfaces in AllApps then add the InstanceId from
|
||||
// LiveSearchManager to recreate the AllApps session on the server side.
|
||||
if (mAllAppsSessionLogId != null && ALL_APPS.equals(
|
||||
@@ -379,7 +341,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
|
||||
@Override
|
||||
protected void completeAddShortcut(Intent data, int container, int screenId, int cellX,
|
||||
int cellY, PendingRequestArgs args) {
|
||||
int cellY, PendingRequestArgs args) {
|
||||
if (container == CONTAINER_HOTSEAT) {
|
||||
mHotseatPredictionController.onDeferredDrop(cellX, cellY);
|
||||
}
|
||||
@@ -471,7 +433,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
|
||||
protected void onItemClicked(View view) {
|
||||
if (!mSplitToWorkspaceController.handleSecondAppSelectionForSplit(view)) {
|
||||
super.getItemOnClickListener().onClick(view);
|
||||
QuickstepLauncher.super.getItemOnClickListener().onClick(view);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,7 +447,6 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
// Order matters as it affects order of appearance in popup container
|
||||
List<SystemShortcut.Factory> shortcuts = new ArrayList(Arrays.asList(
|
||||
APP_INFO, WellbeingModel.SHORTCUT_FACTORY, mHotseatPredictionController));
|
||||
|
||||
shortcuts.addAll(getSplitShortcuts());
|
||||
shortcuts.add(WIDGETS);
|
||||
shortcuts.add(INSTALL);
|
||||
@@ -498,9 +459,6 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
if (Flags.enablePrivateSpace()) {
|
||||
shortcuts.add(UNINSTALL_APP);
|
||||
}
|
||||
if (BubbleAnythingFlagHelper.enableCreateAnyBubble()) {
|
||||
shortcuts.add(BUBBLE_SHORTCUT);
|
||||
}
|
||||
return shortcuts.stream();
|
||||
}
|
||||
|
||||
@@ -528,10 +486,11 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
boolean started = ((getActivityFlags() & ACTIVITY_STATE_STARTED)) != 0;
|
||||
if (started) {
|
||||
DeviceProfile profile = getDeviceProfile();
|
||||
boolean willUserBeActive =
|
||||
(getActivityFlags() & ACTIVITY_STATE_USER_WILL_BE_ACTIVE) != 0;
|
||||
boolean visible = (state == NORMAL || state == OVERVIEW)
|
||||
&& isUserActive()
|
||||
&& !profile.isVerticalBarLayout()
|
||||
&& !mIsOverlayVisible;
|
||||
&& (willUserBeActive || isUserActive())
|
||||
&& !profile.isVerticalBarLayout();
|
||||
SystemUiProxy.INSTANCE.get(this)
|
||||
.setLauncherKeepClearAreaHeight(visible, profile.hotseatBarSizePx);
|
||||
}
|
||||
@@ -540,12 +499,6 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOverlayVisibilityChanged(boolean visible) {
|
||||
super.onOverlayVisibilityChanged(visible);
|
||||
mIsOverlayVisible = visible;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindExtraContainerItems(FixedContainerItems item) {
|
||||
if (item.containerId == Favorites.CONTAINER_PREDICTION) {
|
||||
@@ -557,7 +510,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
} else if (item.containerId == Favorites.CONTAINER_HOTSEAT_PREDICTION) {
|
||||
mHotseatPredictionController.setPredictedItems(item);
|
||||
} else if (item.containerId == Favorites.CONTAINER_WIDGETS_PREDICTION) {
|
||||
getWidgetPickerDataProvider().setWidgetRecommendations(item.items);
|
||||
getPopupDataProvider().setRecommendedWidgets(item.items);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -580,25 +533,23 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
mUnfoldTransitionProgressProvider.destroy();
|
||||
}
|
||||
|
||||
OverviewComponentObserver.INSTANCE.get(this)
|
||||
.removeOverviewChangeListener(mOverviewChangeListener);
|
||||
mTISBindHelper.onDestroy();
|
||||
|
||||
if (mLauncherUnfoldAnimationController != null) {
|
||||
mLauncherUnfoldAnimationController.onDestroy();
|
||||
}
|
||||
|
||||
if (mDesktopVisibilityController != null) {
|
||||
mDesktopVisibilityController.unregisterSystemUiListener();
|
||||
}
|
||||
|
||||
if (mSplitSelectStateController != null) {
|
||||
mSplitSelectStateController.onDestroy();
|
||||
}
|
||||
|
||||
RecentsView recentsView = getOverviewPanel();
|
||||
if (recentsView != null) {
|
||||
recentsView.destroy();
|
||||
}
|
||||
|
||||
super.onDestroy();
|
||||
mHotseatPredictionController.destroy();
|
||||
mSplitWithKeyboardShortcutController.onDestroy();
|
||||
if (mViewCapture != null) mViewCapture.close();
|
||||
if (Utilities.ATLEAST_U) {
|
||||
removeBackAnimationCallback(mSplitSelectStateController.getSplitBackHandler());
|
||||
@@ -632,16 +583,9 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
}
|
||||
case QUICK_SWITCH_STATE_ORDINAL: {
|
||||
RecentsView rv = getOverviewPanel();
|
||||
TaskView currentPageTask = rv.getCurrentPageTaskView();
|
||||
TaskView fallbackTask = rv.getFirstTaskView();
|
||||
if (currentPageTask != null || fallbackTask != null) {
|
||||
TaskView taskToLaunch = currentPageTask;
|
||||
if (currentPageTask == null) {
|
||||
taskToLaunch = fallbackTask;
|
||||
ActiveGestureProtoLogProxy.logQuickSwitchFromHomeFallback(
|
||||
rv.getCurrentPage());
|
||||
}
|
||||
taskToLaunch.launchWithoutAnimation(success -> {
|
||||
TaskView tasktolaunch = rv.getCurrentPageTaskView();
|
||||
if (tasktolaunch != null) {
|
||||
tasktolaunch.launchTask(success -> {
|
||||
if (!success) {
|
||||
getStateManager().goToState(OVERVIEW);
|
||||
} else {
|
||||
@@ -650,7 +594,6 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
return Unit.INSTANCE;
|
||||
});
|
||||
} else {
|
||||
ActiveGestureProtoLogProxy.logQuickSwitchFromHomeFailed(rv.getCurrentPage());
|
||||
getStateManager().goToState(NORMAL);
|
||||
}
|
||||
break;
|
||||
@@ -697,12 +640,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
list.add(new StatusBarTouchController(this));
|
||||
}
|
||||
|
||||
if (enableExpressiveDismissTaskMotion()) {
|
||||
list.add(new TaskViewLaunchTouchController<>(this, mTaskViewRecentsTouchContext));
|
||||
list.add(new TaskViewDismissTouchController<>(this, mTaskViewRecentsTouchContext));
|
||||
} else {
|
||||
list.add(new TaskViewTouchControllerDeprecated<>(this, mTaskViewRecentsTouchContext));
|
||||
}
|
||||
list.add(new LauncherTaskViewController(this));
|
||||
return list.toArray(new TouchController[list.size()]);
|
||||
}
|
||||
|
||||
@@ -711,8 +649,28 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
return new QuickstepAtomicAnimationFactory(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LauncherWidgetHolder createAppWidgetHolder() {
|
||||
final QuickstepHolderFactory factory =
|
||||
(QuickstepHolderFactory) LauncherWidgetHolder.HolderFactory.newFactory(this);
|
||||
return factory.newInstance(this,
|
||||
appWidgetId -> getWorkspace().removeWidget(appWidgetId),
|
||||
new QuickstepInteractionHandler(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
// Back dispatcher is registered in {@link BaseActivity#onCreate}. For predictive back to
|
||||
// work, we must opt-in BEFORE registering back dispatcher. So we need to call
|
||||
// setEnableOnBackInvokedCallback() before super.onCreate()
|
||||
if (Utilities.ATLEAST_U && enablePredictiveBackGesture()) {
|
||||
try {
|
||||
getApplicationInfo().setEnableOnBackInvokedCallback(true);
|
||||
} catch (NoSuchMethodError e) {
|
||||
// Ignore
|
||||
}
|
||||
|
||||
}
|
||||
super.onCreate(savedInstanceState);
|
||||
if (savedInstanceState != null) {
|
||||
mPendingSplitSelectInfo = ObjectWrapper.unwrap(
|
||||
@@ -720,29 +678,21 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
}
|
||||
addMultiWindowModeChangedListener(mDepthController);
|
||||
initUnfoldTransitionProgressProvider();
|
||||
mViewCapture = ViewCaptureFactory.getInstance(this).startCapture(getWindow());
|
||||
if (FeatureFlags.CONTINUOUS_VIEW_TREE_CAPTURE.get()) {
|
||||
mViewCapture = ViewCaptureFactory.getInstance(this).startCapture(getWindow());
|
||||
}
|
||||
// getWindow().addPrivateFlags(PRIVATE_FLAG_OPTIMIZE_MEASURE);
|
||||
QuickstepOnboardingPrefs.setup(this);
|
||||
// View.setTraceLayoutSteps(TRACE_LAYOUTS);
|
||||
// View.setTracedRequestLayoutClassClass(TRACE_RELAYOUT_CLASS);
|
||||
OverviewComponentObserver.INSTANCE.get(this)
|
||||
.addOverviewChangeListener(mOverviewChangeListener);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean initDeviceProfile(InvariantDeviceProfile idp) {
|
||||
final boolean ret = super.initDeviceProfile(idp);
|
||||
try {
|
||||
mDeviceProfile.isPredictiveBackSwipe =
|
||||
getApplicationInfo().isOnBackInvokedCallbackEnabled();
|
||||
} catch (Throwable t) {
|
||||
mDeviceProfile.isPredictiveBackSwipe = false;
|
||||
}
|
||||
if (ATLEAST_S_V2) {
|
||||
if (ret) {
|
||||
SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx);
|
||||
}
|
||||
}
|
||||
// mDeviceProfile.isPredictiveBackSwipe =
|
||||
// getApplicationInfo().isOnBackInvokedCallbackEnabled();
|
||||
mDeviceProfile.isPredictiveBackSwipe = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -752,7 +702,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
// Check if there is already an instance of this app running, if so, initiate the split
|
||||
// using that.
|
||||
mSplitSelectStateController.findLastActiveTasksAndRunCallback(
|
||||
Collections.singletonList(splitSelectSource.getItemInfo().getComponentKey()),
|
||||
Collections.singletonList(splitSelectSource.itemInfo.getComponentKey()),
|
||||
false /* findExactPairMatch */,
|
||||
foundTasks -> {
|
||||
@Nullable Task foundTask = foundTasks[0];
|
||||
@@ -760,7 +710,11 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
splitSelectSource.alreadyRunningTaskId = taskWasFound
|
||||
? foundTask.key.id
|
||||
: INVALID_TASK_ID;
|
||||
startSplitToHome(splitSelectSource);
|
||||
if (enableSplitContextually()) {
|
||||
startSplitToHome(splitSelectSource);
|
||||
} else {
|
||||
recentsView.initiateSplitSelect(splitSelectSource);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -775,7 +729,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
Rect tempRect = new Rect();
|
||||
|
||||
mSplitSelectStateController.setInitialTaskSelect(source.intent,
|
||||
source.position.stagePosition, source.getItemInfo(), source.splitEvent,
|
||||
source.position.stagePosition, source.itemInfo, source.splitEvent,
|
||||
source.alreadyRunningTaskId);
|
||||
|
||||
RecentsView recentsView = getOverviewPanel();
|
||||
@@ -793,8 +747,6 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
floatingTaskView.setOnClickListener(view ->
|
||||
mSplitSelectStateController.getSplitAnimationController().
|
||||
playAnimPlaceholderToFullscreen(this, view, Optional.empty()));
|
||||
floatingTaskView.setContentDescription(source.getItemInfo().contentDescription);
|
||||
|
||||
mSplitSelectStateController.setFirstFloatingTaskView(floatingTaskView);
|
||||
anim.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
@@ -836,10 +788,6 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
if (mLauncherUnfoldAnimationController != null) {
|
||||
mLauncherUnfoldAnimationController.onResume();
|
||||
}
|
||||
|
||||
if (mTaskbarUIController != null && FeatureFlags.enableHomeTransitionListener()) {
|
||||
mTaskbarUIController.onLauncherResume();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -850,25 +798,15 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
|
||||
super.onPause();
|
||||
|
||||
// If Launcher pauses before both split apps are selected, exit split screen.
|
||||
if (!mSplitSelectStateController.isBothSplitAppsConfirmed() &&
|
||||
!mSplitSelectStateController.isLaunchingFirstAppFullscreen()) {
|
||||
mSplitSelectStateController
|
||||
.logExitReason(LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED);
|
||||
mSplitSelectStateController.getSplitAnimationController()
|
||||
.playPlaceholderDismissAnim(this, LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED);
|
||||
}
|
||||
|
||||
if (mTaskbarUIController != null && FeatureFlags.enableHomeTransitionListener()) {
|
||||
mTaskbarUIController.onLauncherPause();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
if (mTaskbarUIController != null && FeatureFlags.enableHomeTransitionListener()) {
|
||||
mTaskbarUIController.onLauncherStop();
|
||||
if (enableSplitContextually()) {
|
||||
// If Launcher pauses before both split apps are selected, exit split screen.
|
||||
if (!mSplitSelectStateController.isBothSplitAppsConfirmed() &&
|
||||
!mSplitSelectStateController.isLaunchingFirstAppFullscreen()) {
|
||||
mSplitSelectStateController
|
||||
.logExitReason(LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED);
|
||||
mSplitSelectStateController.getSplitAnimationController()
|
||||
.playPlaceholderDismissAnim(this, LAUNCHER_SPLIT_SELECTION_EXIT_INTERRUPTED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -943,67 +881,71 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
// event won't go through ViewRootImpl#InputStage#onProcess.
|
||||
// So when receive back key, try to do the same check thing in
|
||||
// ViewRootImpl#NativePreImeInputStage#onProcess
|
||||
if (event.getKeyCode() != KeyEvent.KEYCODE_BACK
|
||||
if (!Utilities.ATLEAST_U || !enablePredictiveBackGesture()
|
||||
|| event.getKeyCode() != KeyEvent.KEYCODE_BACK
|
||||
|| event.getAction() != KeyEvent.ACTION_UP || event.isCanceled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Lawnchair-TODO-Merge: LC disabled this, 16r2 enabled it.
|
||||
// getOnBackAnimationCallback().onBackInvoked();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void registerBackDispatcher() {
|
||||
if (Utilities.ATLEAST_U) {
|
||||
if (!enablePredictiveBackGesture()) {
|
||||
super.registerBackDispatcher();
|
||||
return;
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
|
||||
OnBackInvokedDispatcher.PRIORITY_DEFAULT,
|
||||
new FlingOnBackAnimationCallback() {
|
||||
|
||||
@Nullable OnBackAnimationCallback mActiveOnBackAnimationCallback;
|
||||
new OnBackAnimationCallback() {
|
||||
|
||||
@Nullable
|
||||
OnBackPressedHandler mActiveOnBackPressedHandler;
|
||||
|
||||
@Override
|
||||
public void onBackStartedCompat(@NonNull BackEvent backEvent) {
|
||||
if (mActiveOnBackAnimationCallback != null) {
|
||||
mActiveOnBackAnimationCallback.onBackCancelled();
|
||||
public void onBackStarted(@NonNull BackEvent backEvent) {
|
||||
if (mActiveOnBackPressedHandler != null) {
|
||||
mActiveOnBackPressedHandler.onBackCancelled();
|
||||
}
|
||||
mActiveOnBackAnimationCallback = getOnBackAnimationCallback();
|
||||
mActiveOnBackAnimationCallback.onBackStarted(backEvent);
|
||||
mActiveOnBackPressedHandler = getOnBackPressedHandler();
|
||||
mActiveOnBackPressedHandler.onBackStarted();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onBackInvokedCompat() {
|
||||
// Recreate mActiveOnBackAnimationCallback if necessary to avoid NPE
|
||||
public void onBackInvoked() {
|
||||
// Recreate mActiveOnBackPressedHandler if necessary to avoid NPE
|
||||
// because:
|
||||
// 1. b/260636433: In 3-button-navigation mode, onBackStarted() is not
|
||||
// called on ACTION_DOWN before onBackInvoked() is called in ACTION_UP.
|
||||
// 2. Launcher#onBackPressed() will call onBackInvoked() without calling
|
||||
// onBackInvoked() beforehand.
|
||||
if (mActiveOnBackAnimationCallback == null) {
|
||||
mActiveOnBackAnimationCallback = getOnBackAnimationCallback();
|
||||
if (mActiveOnBackPressedHandler == null) {
|
||||
mActiveOnBackPressedHandler = getOnBackPressedHandler();
|
||||
}
|
||||
mActiveOnBackAnimationCallback.onBackInvoked();
|
||||
mActiveOnBackAnimationCallback = null;
|
||||
mActiveOnBackPressedHandler.onBackInvoked();
|
||||
mActiveOnBackPressedHandler = null;
|
||||
TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onBackInvoked");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackProgressedCompat(@NonNull BackEvent backEvent) {
|
||||
public void onBackProgressed(@NonNull BackEvent backEvent) {
|
||||
if (!FeatureFlags.IS_STUDIO_BUILD
|
||||
&& mActiveOnBackAnimationCallback == null) {
|
||||
&& mActiveOnBackPressedHandler == null) {
|
||||
return;
|
||||
}
|
||||
mActiveOnBackAnimationCallback.onBackProgressed(backEvent);
|
||||
mActiveOnBackPressedHandler.onBackProgressed(backEvent.getProgress());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onBackCancelledCompat() {
|
||||
public void onBackCancelled() {
|
||||
if (!FeatureFlags.IS_STUDIO_BUILD
|
||||
&& mActiveOnBackAnimationCallback == null) {
|
||||
&& mActiveOnBackPressedHandler == null) {
|
||||
return;
|
||||
}
|
||||
mActiveOnBackAnimationCallback.onBackCancelled();
|
||||
mActiveOnBackAnimationCallback = null;
|
||||
mActiveOnBackPressedHandler.onBackCancelled();
|
||||
mActiveOnBackPressedHandler = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1021,7 +963,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
|
||||
@Override
|
||||
public void startIntentSenderForResult(IntentSender intent, int requestCode,
|
||||
Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
|
||||
Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options) {
|
||||
if (requestCode != -1) {
|
||||
mPendingActivityRequestCode = requestCode;
|
||||
StartActivityParams params = new StartActivityParams(this, requestCode);
|
||||
@@ -1053,16 +995,13 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
|
||||
@Override
|
||||
public void setResumed() {
|
||||
DesktopVisibilityController desktopVisibilityController =
|
||||
DesktopVisibilityController.INSTANCE.get(this);
|
||||
if (ATLEAST_BAKLAVA) {
|
||||
if (!ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY.isTrue()
|
||||
&& desktopVisibilityController.isInDesktopModeAndNotInOverview(getDisplayId())
|
||||
&& !desktopVisibilityController.isRecentsGestureInProgress()) {
|
||||
// Return early to skip setting activity to appear as resumed
|
||||
// TODO: b/333533253 - Remove after flag rollout
|
||||
return;
|
||||
}
|
||||
if (!enableDesktopWindowingWallpaperActivity()
|
||||
&& mDesktopVisibilityController != null
|
||||
&& mDesktopVisibilityController.areDesktopTasksVisible()
|
||||
&& !mDesktopVisibilityController.isRecentsGestureInProgress()) {
|
||||
// Return early to skip setting activity to appear as resumed
|
||||
// TODO: b/333533253 - Remove after flag rollout
|
||||
return;
|
||||
}
|
||||
super.setResumed();
|
||||
}
|
||||
@@ -1084,13 +1023,6 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
}
|
||||
}
|
||||
|
||||
private void onOverviewTargetChanged(boolean isHomeAndOverviewSame) {
|
||||
QuickstepTransitionManager transitionManager = getAppTransitionManager();
|
||||
if (transitionManager != null) {
|
||||
transitionManager.onOverviewTargetChange();
|
||||
}
|
||||
}
|
||||
|
||||
private void onTISConnected(TISBinder binder) {
|
||||
TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
|
||||
if (taskbarManager != null) {
|
||||
@@ -1121,6 +1053,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
|
||||
/** Receives animation progress from sysui process. */
|
||||
private void initRemotelyCalculatedUnfoldAnimation(UnfoldTransitionConfig config) {
|
||||
|
||||
RemoteUnfoldSharedComponent unfoldComponent =
|
||||
UnfoldTransitionFactory.createRemoteUnfoldSharedComponent(
|
||||
/* context= */ this,
|
||||
@@ -1148,7 +1081,7 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
}
|
||||
|
||||
private void initUnfoldAnimationController(UnfoldTransitionProgressProvider progressProvider,
|
||||
@UnfoldMain RotationChangeProvider rotationChangeProvider) {
|
||||
@UnfoldMain RotationChangeProvider rotationChangeProvider) {
|
||||
mLauncherUnfoldAnimationController = new LauncherUnfoldAnimationController(
|
||||
/* launcher= */ this,
|
||||
getWindowManager(),
|
||||
@@ -1157,32 +1090,14 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTaskbarUIController(@Nullable TaskbarUIController taskbarUIController) {
|
||||
mTaskbarUIController = (LauncherTaskbarUIController) taskbarUIController;
|
||||
public void setTaskbarUIController(LauncherTaskbarUIController taskbarUIController) {
|
||||
mTaskbarUIController = taskbarUIController;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable LauncherTaskbarUIController getTaskbarUIController() {
|
||||
return mTaskbarUIController;
|
||||
}
|
||||
|
||||
/** Provides the translation X for the hotseat item. */
|
||||
public int getHotseatItemTranslationX(ItemInfo itemInfo) {
|
||||
int translationX = 0;
|
||||
if (isBubbleBarEnabled() && mBubbleBarLocation != null) {
|
||||
boolean isBubblesOnLeft = mBubbleBarLocation.isOnLeft(isRtl(getResources()));
|
||||
translationX += mDeviceProfile
|
||||
.getHotseatTranslationXForNavBar(this, isBubblesOnLeft);
|
||||
}
|
||||
if (isBubbleBarEnabled()
|
||||
&& mDeviceProfile.shouldAdjustHotseatForBubbleBar(asContext(), hasBubbles())) {
|
||||
translationX += (int) mDeviceProfile
|
||||
.getHotseatAdjustedTranslation(asContext(), itemInfo.cellX);
|
||||
}
|
||||
return translationX;
|
||||
}
|
||||
|
||||
public SplitToWorkspaceController getSplitToWorkspaceController() {
|
||||
return mSplitToWorkspaceController;
|
||||
}
|
||||
@@ -1223,6 +1138,11 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
return mDepthController;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public DesktopVisibilityController getDesktopVisibilityController() {
|
||||
return mDesktopVisibilityController;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public UnfoldTransitionProgressProvider getUnfoldTransitionProgressProvider() {
|
||||
return mUnfoldTransitionProgressProvider;
|
||||
@@ -1253,23 +1173,19 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public ActivityOptionsWrapper getActivityLaunchOptions(View v, @Nullable ItemInfo item) {
|
||||
ActivityOptionsWrapper activityOptions = mAppTransitionManager.getActivityLaunchOptions(
|
||||
v, item != null ? item : (ItemInfo) v.getTag());
|
||||
ActivityOptionsWrapper activityOptions = mAppTransitionManager.getActivityLaunchOptions(v);
|
||||
if (mLastTouchUpTime > 0) {
|
||||
activityOptions.options.setSourceInfo(ActivityOptions.SourceInfo.TYPE_LAUNCHER,
|
||||
mLastTouchUpTime);
|
||||
}
|
||||
if (ATLEAST_T) {
|
||||
if (item != null && (item.animationType == DEFAULT_NO_ICON
|
||||
if (item != null && (item.animationType == DEFAULT_NO_ICON
|
||||
|| item.animationType == VIEW_BACKGROUND)) {
|
||||
activityOptions.options.setSplashScreenStyle(
|
||||
activityOptions.options.setSplashScreenStyle(
|
||||
SplashScreen.SPLASH_SCREEN_STYLE_SOLID_COLOR);
|
||||
} else {
|
||||
activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
|
||||
}
|
||||
} else {
|
||||
activityOptions.options.setSplashScreenStyle(SplashScreen.SPLASH_SCREEN_STYLE_ICON);
|
||||
}
|
||||
activityOptions.options.setLaunchDisplayId(
|
||||
(v != null && v.getDisplay() != null) ? v.getDisplay().getDisplayId()
|
||||
@@ -1282,11 +1198,9 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
public ActivityOptionsWrapper makeDefaultActivityOptions(int splashScreenStyle) {
|
||||
RunnableList callbacks = new RunnableList();
|
||||
ActivityOptions options = ActivityOptions.makeCustomAnimation(this, 0, 0);
|
||||
if (ATLEAST_T) {
|
||||
options.setSplashScreenStyle(splashScreenStyle);
|
||||
}
|
||||
options.setSplashScreenStyle(splashScreenStyle);
|
||||
Utilities.allowBGLaunch(options);
|
||||
IRemoteCallback endCallback = completeRunnableListCallback(callbacks, this);
|
||||
IRemoteCallback endCallback = completeRunnableListCallback(callbacks);
|
||||
options.setOnAnimationAbortListener(endCallback);
|
||||
options.setOnAnimationFinishedListener(endCallback);
|
||||
return new ActivityOptionsWrapper(options, callbacks);
|
||||
@@ -1374,14 +1288,18 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
mTISBindHelper.setPredictiveBackToHomeInProgress(isInProgress);
|
||||
}
|
||||
|
||||
public boolean getPredictiveBackToHomeInProgress() {
|
||||
return mIsPredictiveBackToHomeInProgress;
|
||||
@Override
|
||||
public boolean areDesktopTasksVisible() {
|
||||
if (mDesktopVisibilityController != null) {
|
||||
return mDesktopVisibilityController.areDesktopTasksVisible();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areDesktopTasksVisible() {
|
||||
return DesktopVisibilityController.INSTANCE.get(this)
|
||||
.isInDesktopModeAndNotInOverview(getDisplayId());
|
||||
protected void onDeviceProfileInitiated() {
|
||||
super.onDeviceProfileInitiated();
|
||||
SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1390,23 +1308,29 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
SystemUiProxy.INSTANCE.get(this).setLauncherAppIconSize(mDeviceProfile.iconSizePx);
|
||||
TaskbarManager taskbarManager = mTISBindHelper.getTaskbarManager();
|
||||
if (taskbarManager != null) {
|
||||
taskbarManager.debugPrimaryTaskbar("QuickstepLauncher#onDeviceProfileChanged",
|
||||
true);
|
||||
taskbarManager.debugWhyTaskbarNotDestroyed("QuickstepLauncher#onDeviceProfileChanged");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches the given {@link SplitTask} in splitscreen.
|
||||
* Launches the given {@link GroupTask} in splitscreen.
|
||||
*/
|
||||
public void launchSplitTasks(
|
||||
@NonNull SplitTask splitTask, @Nullable RemoteTransition remoteTransition) {
|
||||
mSplitSelectStateController.launchExistingSplitPair(null /* launchingTaskView */,
|
||||
splitTask.getTopLeftTask().key.id,
|
||||
splitTask.getBottomRightTask().key.id,
|
||||
@NonNull GroupTask groupTask, @Nullable RemoteTransition remoteTransition) {
|
||||
// Top/left and bottom/right tasks respectively.
|
||||
Task task1 = groupTask.task1;
|
||||
// task2 should never be null when calling this method. Allow a crash to catch invalid calls
|
||||
Task task2 = groupTask.task2;
|
||||
mSplitSelectStateController.launchExistingSplitPair(
|
||||
null /* launchingTaskView */,
|
||||
task1.key.id,
|
||||
task2.key.id,
|
||||
SplitConfigurationOptions.STAGE_POSITION_TOP_OR_LEFT,
|
||||
/* callback= */ success -> mSplitSelectStateController.resetState(),
|
||||
/* freezeTaskList= */ false,
|
||||
splitTask.getSplitBounds().snapPosition,
|
||||
groupTask.mSplitBounds == null
|
||||
? SNAP_TO_50_50
|
||||
: groupTask.mSplitBounds.snapPosition,
|
||||
remoteTransition);
|
||||
}
|
||||
|
||||
@@ -1414,14 +1338,8 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
* Launches two apps as an app pair.
|
||||
*/
|
||||
public void launchAppPair(AppPairIcon appPairIcon) {
|
||||
// Potentially show the Taskbar education once the app pair launch finishes
|
||||
mSplitSelectStateController.getAppPairsController().launchAppPair(appPairIcon,
|
||||
CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE,
|
||||
(success) -> {
|
||||
if (success && mTaskbarUIController != null) {
|
||||
mTaskbarUIController.showEduOnAppLaunch();
|
||||
}
|
||||
});
|
||||
CUJ_LAUNCHER_LAUNCH_APP_PAIR_FROM_WORKSPACE);
|
||||
}
|
||||
|
||||
public boolean canStartHomeSafely() {
|
||||
@@ -1439,69 +1357,41 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
return (mTaskbarUIController != null && mTaskbarUIController.hasBubbles());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public TISBindHelper getTISBindHelper() {
|
||||
return mTISBindHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handleIncorrectSplitTargetSelection() {
|
||||
if (!mSplitSelectStateController.isSplitSelectActive()) {
|
||||
if (!enableSplitContextually() || !mSplitSelectStateController.isSplitSelectActive()) {
|
||||
return false;
|
||||
}
|
||||
mSplitSelectStateController.getSplitInstructionsView().goBoing();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void showShortcutBubble(ShortcutInfo info) {
|
||||
if (info == null) return;
|
||||
SystemUiProxy.INSTANCE.get(this).showShortcutBubble(info);
|
||||
}
|
||||
private static final class LauncherTaskViewController extends
|
||||
TaskViewTouchController<QuickstepLauncher> {
|
||||
|
||||
@Override
|
||||
public void showAppBubble(Intent intent, UserHandle user) {
|
||||
if (intent == null || intent.getPackage() == null) return;
|
||||
SystemUiProxy.INSTANCE.get(this).showAppBubble(intent, user);
|
||||
}
|
||||
|
||||
/** Sets the location of the bubble bar */
|
||||
public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation) {
|
||||
mBubbleBarLocation = bubbleBarLocation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to {@link #getFirstHomeElementForAppClose} but also matches all apps if its visible
|
||||
*/
|
||||
@Nullable
|
||||
public View getFirstVisibleElementForAppClose(
|
||||
@Nullable StableViewInfo svi, String packageName, UserHandle user) {
|
||||
if (isInState(LauncherState.ALL_APPS)) {
|
||||
AllAppsRecyclerView activeRecyclerView = getAppsView().getActiveRecyclerView();
|
||||
View v = null;
|
||||
if (svi != null) {
|
||||
// Preferred item match
|
||||
v = activeRecyclerView.findViewByPredicate(view ->
|
||||
view.isAggregatedVisible()
|
||||
&& view.getTag() instanceof ItemInfo info && svi.matches(info));
|
||||
}
|
||||
if (v == null) {
|
||||
// Package user match
|
||||
v = activeRecyclerView.findViewByPredicate(view ->
|
||||
view.isAggregatedVisible() && view.getTag() instanceof ItemInfo info
|
||||
&& info.itemType == ITEM_TYPE_APPLICATION
|
||||
&& info.user.equals(user)
|
||||
&& TextUtils.equals(info.getTargetPackage(), packageName));
|
||||
}
|
||||
|
||||
if (v != null && activeRecyclerView.computeVerticalScrollOffset() > 0) {
|
||||
RectF locationBounds = new RectF();
|
||||
FloatingIconView.getLocationBoundsForView(this, v, false, locationBounds,
|
||||
new Rect());
|
||||
if (locationBounds.top < getAppsView().getHeaderBottom()) {
|
||||
// Icon is covered by scrim, return null to play fallback animation.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return v;
|
||||
LauncherTaskViewController(QuickstepLauncher activity) {
|
||||
super(activity);
|
||||
}
|
||||
|
||||
return getFirstHomeElementForAppClose(svi, packageName, user);
|
||||
@Override
|
||||
protected boolean isRecentsInteractive() {
|
||||
return mContainer.isInState(OVERVIEW) || mContainer.isInState(OVERVIEW_MODAL_TASK);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isRecentsModal() {
|
||||
return mContainer.isInState(OVERVIEW_MODAL_TASK);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onUserControlledAnimationCreated(AnimatorPlaybackController animController) {
|
||||
mContainer.getStateManager().setCurrentUserControlledAnimation(animController);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1531,18 +1421,6 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
|
||||
@Override
|
||||
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
|
||||
switch (name) {
|
||||
case "TextClock", "android.widget.TextClock" -> {
|
||||
TextClock tc = new TextClock(context, attrs);
|
||||
tc.setClockEventDelegate(AsyncClockEventDelegate.INSTANCE.get(this));
|
||||
return tc;
|
||||
}
|
||||
case "AnalogClock", "android.widget.AnalogClock" -> {
|
||||
AnalogClock ac = new AnalogClock(context, attrs);
|
||||
ac.setClockEventDelegate(AsyncClockEventDelegate.INSTANCE.get(this));
|
||||
return ac;
|
||||
}
|
||||
}
|
||||
return super.onCreateView(parent, name, context, attrs);
|
||||
}
|
||||
|
||||
@@ -1550,17 +1428,4 @@ public class QuickstepLauncher extends Launcher implements RecentsViewContainer,
|
||||
public boolean isRecentsViewVisible() {
|
||||
return getStateManager().getState().isRecentsViewVisible;
|
||||
}
|
||||
|
||||
public boolean isCanShowAllAppsEducationView() {
|
||||
return mCanShowAllAppsEducationView;
|
||||
}
|
||||
|
||||
public void setCanShowAllAppsEducationView(boolean canShowAllAppsEducationView) {
|
||||
mCanShowAllAppsEducationView = canShowAllAppsEducationView;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void returnToHomescreen() {
|
||||
getStateManager().goToState(LauncherState.NORMAL);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,40 +16,43 @@
|
||||
package com.android.launcher3.uioverrides;
|
||||
|
||||
import static com.android.launcher3.BuildConfigs.WIDGETS_ENABLED;
|
||||
import static com.android.launcher3.uioverrides.QuickstepAppWidgetHostProvider.getStaticQuickstepHost;
|
||||
import static com.android.launcher3.util.Executors.MAIN_EXECUTOR;
|
||||
import static com.android.launcher3.widget.ListenableAppWidgetHost.getWidgetHolderExecutor;
|
||||
import static com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR;
|
||||
|
||||
import android.appwidget.AppWidgetHost;
|
||||
import android.appwidget.AppWidgetHostView;
|
||||
import android.appwidget.AppWidgetProviderInfo;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.widget.RemoteViews;
|
||||
|
||||
import androidx.annotation.AnyThread;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
|
||||
import com.android.launcher3.config.FeatureFlags;
|
||||
import com.android.launcher3.util.IntSet;
|
||||
import com.android.launcher3.util.SafeCloseable;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetHostView;
|
||||
import com.android.launcher3.widget.LauncherAppWidgetProviderInfo;
|
||||
import com.android.launcher3.widget.LauncherWidgetHolder;
|
||||
|
||||
import dagger.assisted.Assisted;
|
||||
import dagger.assisted.AssistedFactory;
|
||||
import dagger.assisted.AssistedInject;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.IntConsumer;
|
||||
|
||||
/**
|
||||
* {@link LauncherWidgetHolder} that puts the app widget host in the background
|
||||
*/
|
||||
public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
|
||||
public class QuickstepWidgetHolder extends LauncherWidgetHolder {
|
||||
|
||||
private static final String TAG = "QuickstepWidgetHolder";
|
||||
|
||||
private static final UpdateKey<AppWidgetProviderInfo> KEY_PROVIDER_UPDATE =
|
||||
AppWidgetHostView::onUpdateProviderInfo;
|
||||
@@ -58,17 +61,51 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
|
||||
private static final UpdateKey<Integer> KEY_VIEW_DATA_CHANGED =
|
||||
AppWidgetHostView::onViewDataChanged;
|
||||
|
||||
private static final List<QuickstepWidgetHolder> sHolders = new ArrayList<>();
|
||||
private static final SparseArray<QuickstepWidgetHolderListener> sListeners =
|
||||
new SparseArray<>();
|
||||
|
||||
private static AppWidgetHost sWidgetHost = null;
|
||||
|
||||
private final UpdateHandler mUpdateHandler = this::onWidgetUpdate;
|
||||
private final @Nullable RemoteViews.InteractionHandler mInteractionHandler;
|
||||
|
||||
private final @NonNull IntConsumer mAppWidgetRemovedCallback;
|
||||
|
||||
// Map to all pending updated keyed with appWidgetId;
|
||||
private final SparseArray<PendingUpdate> mPendingUpdateMap = new SparseArray<>();
|
||||
|
||||
@AssistedInject
|
||||
public QuickstepWidgetHolder(@Assisted("UI_CONTEXT") @NonNull Context context) {
|
||||
super(context, getStaticQuickstepHost());
|
||||
public QuickstepWidgetHolder(@NonNull Context context,
|
||||
@Nullable IntConsumer appWidgetRemovedCallback,
|
||||
@Nullable RemoteViews.InteractionHandler interactionHandler) {
|
||||
super(context, appWidgetRemovedCallback);
|
||||
mAppWidgetRemovedCallback = appWidgetRemovedCallback != null ? appWidgetRemovedCallback
|
||||
: i -> {};
|
||||
mInteractionHandler = interactionHandler;
|
||||
MAIN_EXECUTOR.execute(() -> sHolders.add(this));
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
protected AppWidgetHost createHost(@NonNull Context context,
|
||||
@Nullable IntConsumer appWidgetRemovedCallback) {
|
||||
if (sWidgetHost == null) {
|
||||
sWidgetHost = new QuickstepAppWidgetHost(context.getApplicationContext(),
|
||||
i -> MAIN_EXECUTOR.execute(() ->
|
||||
sHolders.forEach(h -> h.mAppWidgetRemovedCallback.accept(i))),
|
||||
() -> MAIN_EXECUTOR.execute(() ->
|
||||
sHolders.forEach(h ->
|
||||
// Listeners might remove themselves from the list during the
|
||||
// iteration. Creating a copy of the list to avoid exceptions
|
||||
// for concurrent modification.
|
||||
new ArrayList<>(h.mProviderChangedListeners).forEach(
|
||||
ProviderChangedListener::notifyWidgetProvidersChanged))),
|
||||
UI_HELPER_EXECUTOR.getLooper());
|
||||
if (WIDGETS_ENABLED) {
|
||||
sWidgetHost.startListening();
|
||||
}
|
||||
}
|
||||
return sWidgetHost;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -132,6 +169,21 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
|
||||
sListeners.remove(appWidgetId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the launcher is destroyed
|
||||
*/
|
||||
@Override
|
||||
public void destroy() {
|
||||
try {
|
||||
MAIN_EXECUTOR.submit(() -> {
|
||||
clearViews();
|
||||
sHolders.remove(this);
|
||||
}).get();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to remove self from holder list", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean shouldListen(int flags) {
|
||||
return (flags & (FLAG_STATE_IS_NORMAL | FLAG_ACTIVITY_STARTED))
|
||||
@@ -147,10 +199,12 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
|
||||
return;
|
||||
}
|
||||
|
||||
getWidgetHolderExecutor().execute(() -> {
|
||||
mWidgetHost.setAppWidgetHidden();
|
||||
setListeningFlag(false);
|
||||
});
|
||||
try {
|
||||
sWidgetHost.setAppWidgetHidden();
|
||||
} catch (Throwable t) {
|
||||
// Ignore
|
||||
}
|
||||
setListeningFlag(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -166,7 +220,7 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
|
||||
};
|
||||
QuickstepWidgetHolderListener holderListener = getHolderListener(appWidgetId);
|
||||
holderListener.addHolder(handler);
|
||||
return () -> holderListener.removeHolder(handler);
|
||||
return () -> holderListener.mListeningHolders.remove(handler);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,6 +242,7 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
|
||||
protected LauncherAppWidgetHostView createViewInternal(
|
||||
int appWidgetId, @NonNull LauncherAppWidgetProviderInfo appWidget) {
|
||||
LauncherAppWidgetHostView widgetView = new LauncherAppWidgetHostView(mContext);
|
||||
widgetView.setInteractionHandler(mInteractionHandler);
|
||||
widgetView.setAppWidget(appWidgetId, appWidget);
|
||||
widgetView.updateAppWidget(getHolderListener(appWidgetId).addHolder(mUpdateHandler));
|
||||
return widgetView;
|
||||
@@ -197,7 +252,7 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
|
||||
QuickstepWidgetHolderListener listener = sListeners.get(appWidgetId);
|
||||
if (listener == null) {
|
||||
listener = new QuickstepWidgetHolderListener(appWidgetId);
|
||||
getStaticQuickstepHost().setListener(appWidgetId, listener);
|
||||
sWidgetHost.setListener(appWidgetId, listener);
|
||||
sListeners.put(appWidgetId, listener);
|
||||
}
|
||||
return listener;
|
||||
@@ -210,7 +265,7 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
|
||||
public void clearViews() {
|
||||
mViews.clear();
|
||||
for (int i = sListeners.size() - 1; i >= 0; i--) {
|
||||
sListeners.valueAt(i).removeHolder(mUpdateHandler);
|
||||
sListeners.valueAt(i).mListeningHolders.remove(mUpdateHandler);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,15 +292,13 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
|
||||
mWidgetId = widgetId;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@Nullable
|
||||
public RemoteViews addHolder(@NonNull UpdateHandler holder) {
|
||||
MAIN_EXECUTOR.execute(() -> mListeningHolders.add(holder));
|
||||
mListeningHolders.add(holder);
|
||||
return mRemoteViews;
|
||||
}
|
||||
|
||||
public void removeHolder(@NonNull UpdateHandler holder) {
|
||||
MAIN_EXECUTOR.execute(() -> mListeningHolders.remove(holder));
|
||||
}
|
||||
|
||||
@Override
|
||||
@AnyThread
|
||||
public void onUpdateProviderInfo(@Nullable AppWidgetProviderInfo info) {
|
||||
@@ -272,13 +325,44 @@ public final class QuickstepWidgetHolder extends LauncherWidgetHolder {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code HolderFactory} subclass that takes an interaction handler as one of the parameters
|
||||
* when creating a new instance.
|
||||
*/
|
||||
public static class QuickstepHolderFactory extends HolderFactory {
|
||||
|
||||
/** A factory that generates new instances of {@code LauncherWidgetHolder} */
|
||||
@AssistedFactory
|
||||
public interface QuickstepWidgetHolderFactory extends WidgetHolderFactory {
|
||||
@SuppressWarnings("unused")
|
||||
public QuickstepHolderFactory(Context context) { }
|
||||
|
||||
@Override
|
||||
QuickstepWidgetHolder newInstance(@Assisted("UI_CONTEXT") @NonNull Context context);
|
||||
public LauncherWidgetHolder newInstance(@NonNull Context context,
|
||||
@Nullable IntConsumer appWidgetRemovedCallback) {
|
||||
return newInstance(context, appWidgetRemovedCallback, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param context The context of the caller
|
||||
* @param appWidgetRemovedCallback The callback that is called when widgets are removed
|
||||
* @param interactionHandler The interaction handler when the widgets are clicked
|
||||
* @return A new {@link LauncherWidgetHolder} instance
|
||||
*/
|
||||
public LauncherWidgetHolder newInstance(@NonNull Context context,
|
||||
@Nullable IntConsumer appWidgetRemovedCallback,
|
||||
@Nullable RemoteViews.InteractionHandler interactionHandler) {
|
||||
|
||||
if (!FeatureFlags.ENABLE_WIDGET_HOST_IN_BACKGROUND.get()) {
|
||||
return new LauncherWidgetHolder(context, appWidgetRemovedCallback) {
|
||||
@Override
|
||||
protected AppWidgetHost createHost(Context context,
|
||||
@Nullable IntConsumer appWidgetRemovedCallback) {
|
||||
AppWidgetHost host = super.createHost(context, appWidgetRemovedCallback);
|
||||
host.setInteractionHandler(interactionHandler);
|
||||
return host;
|
||||
}
|
||||
};
|
||||
}
|
||||
return new QuickstepWidgetHolder(context, appWidgetRemovedCallback, interactionHandler);
|
||||
}
|
||||
}
|
||||
|
||||
private interface UpdateKey<T> extends BiConsumer<AppWidgetHostView, T> { }
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user