blob: 6fd98db19a5bb149a8d476006721e356b9c79653 [file] [log] [blame]
/*
* 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 static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
import static com.android.launcher3.AbstractFloatingView.TYPE_ALL;
import static com.android.launcher3.AbstractFloatingView.TYPE_REBIND_SAFE;
import android.content.Context;
import android.graphics.PixelFormat;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import androidx.annotation.Nullable;
import com.android.launcher3.AbstractFloatingView;
import com.android.launcher3.DeviceProfile;
import com.android.launcher3.appprediction.PredictionRowView;
import com.android.launcher3.config.FeatureFlags;
import com.android.launcher3.model.data.AppInfo;
import com.android.launcher3.model.data.ItemInfo;
import com.android.launcher3.taskbar.TaskbarActivityContext;
import com.android.launcher3.taskbar.TaskbarControllers;
import com.android.systemui.shared.system.TaskStackChangeListener;
import com.android.systemui.shared.system.TaskStackChangeListeners;
import java.util.List;
import java.util.Optional;
/**
* 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
* 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 models for the next all apps session.
*/
public final class TaskbarAllAppsController {
private static final String WINDOW_TITLE = "Taskbar All Apps";
private final TaskbarActivityContext mTaskbarContext;
private final TaskbarAllAppsProxyView mProxyView;
private final LayoutParams mLayoutParams;
private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
@Override
public void onTaskStackChanged() {
mProxyView.close(false);
}
};
private DeviceProfile mDeviceProfile;
private TaskbarControllers mControllers;
/** Window context for all apps if it is open. */
private @Nullable TaskbarAllAppsContext mAllAppsContext;
// Application data models.
private AppInfo[] mApps;
private int mAppsModelFlags;
private List<ItemInfo> mPredictedApps;
public TaskbarAllAppsController(TaskbarActivityContext context, DeviceProfile dp) {
mDeviceProfile = dp;
mTaskbarContext = context;
mProxyView = new TaskbarAllAppsProxyView(mTaskbarContext);
mLayoutParams = createLayoutParams();
}
/** Initialize the controller. */
public void init(TaskbarControllers controllers, boolean allAppsVisible) {
if (!FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get()) {
return;
}
mControllers = controllers;
/*
* Recreate All Apps if it was open in the previous Taskbar instance (e.g. the configuration
* changed).
*/
if (allAppsVisible) {
show(false);
}
}
/** Updates the current {@link AppInfo} instances. */
public void setApps(AppInfo[] apps, int flags) {
if (!FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get()) {
return;
}
mApps = apps;
mAppsModelFlags = flags;
if (mAllAppsContext != null) {
mAllAppsContext.getAppsView().getAppsStore().setApps(mApps, mAppsModelFlags);
}
}
/** Updates the current predictions. */
public void setPredictedApps(List<ItemInfo> predictedApps) {
if (!FeatureFlags.ENABLE_ALL_APPS_IN_TASKBAR.get()) {
return;
}
mPredictedApps = predictedApps;
if (mAllAppsContext != null) {
mAllAppsContext.getAppsView().getFloatingHeaderView()
.findFixedRowByType(PredictionRowView.class)
.setPredictedApps(mPredictedApps);
}
}
/** Opens the {@link TaskbarAllAppsContainerView} in a new window. */
public void show() {
show(true);
}
private void show(boolean animate) {
if (mProxyView.isOpen()) {
return;
}
mProxyView.show();
// mControllers and getSharedState should never be null here. Do not handle null-pointer
// to catch invalid states.
mControllers.getSharedState().allAppsVisible = true;
mAllAppsContext = new TaskbarAllAppsContext(mTaskbarContext,
this,
mControllers.taskbarStashController);
mAllAppsContext.getDragController().init(mControllers);
TaskStackChangeListeners.getInstance().registerTaskStackListener(mTaskStackListener);
Optional.ofNullable(mAllAppsContext.getSystemService(WindowManager.class))
.ifPresent(m -> m.addView(mAllAppsContext.getDragLayer(), mLayoutParams));
mAllAppsContext.getAppsView().getAppsStore().setApps(mApps, mAppsModelFlags);
mAllAppsContext.getAppsView().getFloatingHeaderView()
.findFixedRowByType(PredictionRowView.class)
.setPredictedApps(mPredictedApps);
mAllAppsContext.getAllAppsViewController().show(animate);
}
/** Closes the {@link TaskbarAllAppsContainerView}. */
public void hide() {
mProxyView.close(true);
}
/**
* Removes the all apps window from the hierarchy, if all floating views are closed and there is
* no system drag operation in progress.
* <p>
* This method should be called after an exit animation finishes, if applicable.
*/
void maybeCloseWindow() {
if (AbstractFloatingView.getOpenView(mAllAppsContext, TYPE_ALL) != null
|| mAllAppsContext.getDragController().isSystemDragInProgress()) {
return;
}
mProxyView.close(false);
// mControllers and getSharedState should never be null here. Do not handle null-pointer
// to catch invalid states.
mControllers.getSharedState().allAppsVisible = false;
onDestroy();
}
/** Destroys the controller and any All Apps window if present. */
public void onDestroy() {
TaskStackChangeListeners.getInstance().unregisterTaskStackListener(mTaskStackListener);
Optional.ofNullable(mAllAppsContext)
.map(c -> c.getSystemService(WindowManager.class))
.ifPresent(m -> m.removeView(mAllAppsContext.getDragLayer()));
mAllAppsContext = null;
}
/** Updates {@link DeviceProfile} instance for Taskbar's All Apps window. */
public void updateDeviceProfile(DeviceProfile dp) {
mDeviceProfile = dp;
Optional.ofNullable(mAllAppsContext).ifPresent(c -> {
AbstractFloatingView.closeAllOpenViewsExcept(c, false, TYPE_REBIND_SAFE);
c.dispatchDeviceProfileChanged();
});
}
DeviceProfile getDeviceProfile() {
return mDeviceProfile;
}
private LayoutParams createLayoutParams() {
LayoutParams layoutParams = new LayoutParams(
TYPE_APPLICATION_OVERLAY,
0,
PixelFormat.TRANSLUCENT);
layoutParams.setTitle(WINDOW_TITLE);
layoutParams.gravity = Gravity.BOTTOM;
layoutParams.packageName = mTaskbarContext.getPackageName();
layoutParams.setFitInsetsTypes(0); // Handled by container view.
layoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
layoutParams.setSystemApplicationOverlay(true);
return layoutParams;
}
/**
* Proxy view connecting taskbar drag layer to the all apps window.
* <p>
* The all apps view is in a separate window and has its own drag layer, but this proxy lets it
* behave as though its in the taskbar drag layer. For instance, when the taskbar closes all
* {@link AbstractFloatingView} instances, the all apps window will also close.
*/
private class TaskbarAllAppsProxyView extends AbstractFloatingView {
private TaskbarAllAppsProxyView(Context context) {
super(context, null);
}
private void show() {
mIsOpen = true;
mTaskbarContext.getDragLayer().addView(this);
}
@Override
protected void handleClose(boolean animate) {
mTaskbarContext.getDragLayer().removeView(this);
Optional.ofNullable(mAllAppsContext)
.map(TaskbarAllAppsContext::getAllAppsViewController)
.ifPresent(v -> v.close(animate));
}
@Override
protected boolean isOfType(int type) {
return (type & TYPE_TASKBAR_ALL_APPS) != 0;
}
@Override
public boolean onControllerInterceptTouchEvent(MotionEvent ev) {
return false;
}
}
}