| /* |
| * Copyright (C) 2021 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.android.launcher3.taskbar; |
| |
| import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; |
| import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; |
| import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; |
| |
| import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_FOLDER_OPEN; |
| 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.WindowManagerWrapper.ITYPE_BOTTOM_TAPPABLE_ELEMENT; |
| import static com.android.systemui.shared.system.WindowManagerWrapper.ITYPE_EXTRA_NAVIGATION_BAR; |
| |
| import android.animation.AnimatorSet; |
| import android.app.ActivityOptions; |
| import android.content.ActivityNotFoundException; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.LauncherApps; |
| import android.graphics.Insets; |
| import android.graphics.PixelFormat; |
| import android.graphics.Rect; |
| import android.os.Process; |
| import android.os.SystemProperties; |
| import android.provider.Settings; |
| import android.util.Log; |
| import android.view.ContextThemeWrapper; |
| import android.view.Display; |
| import android.view.Gravity; |
| import android.view.LayoutInflater; |
| import android.view.RoundedCorner; |
| import android.view.View; |
| import android.view.WindowManager; |
| import android.widget.FrameLayout; |
| import android.widget.Toast; |
| |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| |
| import com.android.launcher3.AbstractFloatingView; |
| import com.android.launcher3.DeviceProfile; |
| import com.android.launcher3.LauncherSettings.Favorites; |
| import com.android.launcher3.R; |
| import com.android.launcher3.folder.Folder; |
| import com.android.launcher3.folder.FolderIcon; |
| import com.android.launcher3.logger.LauncherAtom; |
| import com.android.launcher3.model.data.FolderInfo; |
| import com.android.launcher3.model.data.WorkspaceItemInfo; |
| import com.android.launcher3.taskbar.contextual.RotationButtonController; |
| import com.android.launcher3.touch.ItemClickHandler; |
| import com.android.launcher3.util.PackageManagerHelper; |
| import com.android.launcher3.util.SettingsCache; |
| import com.android.launcher3.util.Themes; |
| import com.android.launcher3.util.TraceHelper; |
| import com.android.launcher3.util.ViewCache; |
| import com.android.launcher3.views.ActivityContext; |
| import com.android.quickstep.SysUINavigationMode; |
| import com.android.quickstep.SysUINavigationMode.Mode; |
| import com.android.quickstep.SystemUiProxy; |
| import com.android.systemui.shared.recents.model.Task; |
| import com.android.systemui.shared.system.ActivityManagerWrapper; |
| import com.android.systemui.shared.system.WindowManagerWrapper; |
| import com.android.systemui.unfold.util.ScopedUnfoldTransitionProgressProvider; |
| |
| /** |
| * The {@link ActivityContext} with which we inflate Taskbar-related Views. This allows UI elements |
| * that are used by both Launcher and Taskbar (such as Folder) to reference a generic |
| * ActivityContext and BaseDragLayer instead of the Launcher activity and its DragLayer. |
| */ |
| public class TaskbarActivityContext extends ContextThemeWrapper implements ActivityContext { |
| |
| private static final boolean ENABLE_THREE_BUTTON_TASKBAR = |
| SystemProperties.getBoolean("persist.debug.taskbar_three_button", false); |
| private static final String TAG = "TaskbarActivityContext"; |
| |
| private static final String WINDOW_TITLE = "Taskbar"; |
| |
| private final DeviceProfile mDeviceProfile; |
| private final LayoutInflater mLayoutInflater; |
| private final TaskbarDragLayer mDragLayer; |
| private final TaskbarControllers mControllers; |
| |
| private final WindowManager mWindowManager; |
| private final @Nullable RoundedCorner mLeftCorner, mRightCorner; |
| private WindowManager.LayoutParams mWindowLayoutParams; |
| private boolean mIsFullscreen; |
| // The size we should return to when we call setTaskbarWindowFullscreen(false) |
| private int mLastRequestedNonFullscreenHeight; |
| |
| private final SysUINavigationMode.Mode mNavMode; |
| private final ViewCache mViewCache = new ViewCache(); |
| |
| private final boolean mIsSafeModeEnabled; |
| private final boolean mIsUserSetupComplete; |
| private boolean mIsDestroyed = false; |
| |
| public TaskbarActivityContext(Context windowContext, DeviceProfile dp, |
| TaskbarNavButtonController buttonController, ScopedUnfoldTransitionProgressProvider |
| unfoldTransitionProgressProvider) { |
| super(windowContext, Themes.getActivityThemeRes(windowContext)); |
| mDeviceProfile = dp; |
| |
| mNavMode = SysUINavigationMode.getMode(windowContext); |
| mIsSafeModeEnabled = TraceHelper.allowIpcs("isSafeMode", |
| () -> getPackageManager().isSafeMode()); |
| mIsUserSetupComplete = SettingsCache.INSTANCE.get(this).getValue( |
| Settings.Secure.getUriFor(Settings.Secure.USER_SETUP_COMPLETE), 0); |
| |
| float taskbarIconSize = getResources().getDimension(R.dimen.taskbar_icon_size); |
| mDeviceProfile.updateIconSize(1, getResources()); |
| float iconScale = taskbarIconSize / mDeviceProfile.iconSizePx; |
| mDeviceProfile.updateIconSize(iconScale, getResources()); |
| |
| mLayoutInflater = LayoutInflater.from(this).cloneInContext(this); |
| |
| // Inflate views. |
| mDragLayer = (TaskbarDragLayer) mLayoutInflater.inflate( |
| R.layout.taskbar, null, false); |
| TaskbarView taskbarView = mDragLayer.findViewById(R.id.taskbar_view); |
| TaskbarScrimView taskbarScrimView = mDragLayer.findViewById(R.id.taskbar_scrim); |
| FrameLayout navButtonsView = mDragLayer.findViewById(R.id.navbuttons_view); |
| StashedHandleView stashedHandleView = mDragLayer.findViewById(R.id.stashed_handle); |
| |
| Display display = windowContext.getDisplay(); |
| Context c = display.getDisplayId() == Display.DEFAULT_DISPLAY |
| ? windowContext.getApplicationContext() |
| : windowContext.getApplicationContext().createDisplayContext(display); |
| mWindowManager = c.getSystemService(WindowManager.class); |
| mLeftCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT); |
| mRightCorner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_RIGHT); |
| |
| // Construct controllers. |
| mControllers = new TaskbarControllers(this, |
| new TaskbarDragController(this), |
| buttonController, |
| new NavbarButtonsViewController(this, navButtonsView), |
| new RotationButtonController(this, R.color.popup_color_primary_light, |
| R.color.popup_color_primary_light), |
| new TaskbarDragLayerController(this, mDragLayer), |
| new TaskbarViewController(this, taskbarView), |
| new TaskbarScrimViewController(this, taskbarScrimView), |
| new TaskbarUnfoldAnimationController(unfoldTransitionProgressProvider, |
| mWindowManager), |
| new TaskbarKeyguardController(this), |
| new StashedHandleViewController(this, stashedHandleView), |
| new TaskbarStashController(this), |
| new TaskbarEduController(this)); |
| } |
| |
| public void init(TaskbarSharedState sharedState) { |
| mLastRequestedNonFullscreenHeight = getDefaultTaskbarWindowHeight(); |
| mWindowLayoutParams = new WindowManager.LayoutParams( |
| MATCH_PARENT, |
| mLastRequestedNonFullscreenHeight, |
| TYPE_NAVIGATION_BAR_PANEL, |
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE |
| | WindowManager.LayoutParams.FLAG_SLIPPERY, |
| PixelFormat.TRANSLUCENT); |
| mWindowLayoutParams.setTitle(WINDOW_TITLE); |
| mWindowLayoutParams.packageName = getPackageName(); |
| mWindowLayoutParams.gravity = Gravity.BOTTOM; |
| mWindowLayoutParams.setFitInsetsTypes(0); |
| mWindowLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING; |
| mWindowLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; |
| mWindowLayoutParams.privateFlags = |
| WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; |
| |
| WindowManagerWrapper wmWrapper = WindowManagerWrapper.getInstance(); |
| wmWrapper.setProvidesInsetsTypes( |
| mWindowLayoutParams, |
| new int[] { ITYPE_EXTRA_NAVIGATION_BAR, ITYPE_BOTTOM_TAPPABLE_ELEMENT } |
| ); |
| // Adjust the frame by the rounded corners (ie. leaving just the bar as the inset) when |
| // the IME is showing |
| mWindowLayoutParams.providedInternalImeInsets = Insets.of(0, |
| getDefaultTaskbarWindowHeight() - mDeviceProfile.taskbarSize, 0, 0); |
| |
| // Initialize controllers after all are constructed. |
| mControllers.init(sharedState); |
| |
| mWindowManager.addView(mDragLayer, mWindowLayoutParams); |
| } |
| |
| public boolean isThreeButtonNav() { |
| return mNavMode == Mode.THREE_BUTTONS; |
| } |
| |
| public int getLeftCornerRadius() { |
| return mLeftCorner == null ? 0 : mLeftCorner.getRadius(); |
| } |
| |
| public int getRightCornerRadius() { |
| return mRightCorner == null ? 0 : mRightCorner.getRadius(); |
| } |
| |
| @Override |
| public LayoutInflater getLayoutInflater() { |
| return mLayoutInflater; |
| } |
| |
| @Override |
| public TaskbarDragLayer getDragLayer() { |
| return mDragLayer; |
| } |
| |
| @Override |
| public DeviceProfile getDeviceProfile() { |
| return mDeviceProfile; |
| } |
| |
| @Override |
| public Rect getFolderBoundingBox() { |
| return mControllers.taskbarDragLayerController.getFolderBoundingBox(); |
| } |
| |
| @Override |
| public TaskbarDragController getDragController() { |
| return mControllers.taskbarDragController; |
| } |
| |
| @Override |
| public ViewCache getViewCache() { |
| return mViewCache; |
| } |
| |
| @Override |
| public boolean supportsIme() { |
| // Currently we don't support IME because we have FLAG_NOT_FOCUSABLE. We can remove that |
| // flag when opening a floating view that needs IME (such as Folder), but then that means |
| // Taskbar will be below IME and thus users can't click the back button. |
| return false; |
| } |
| |
| /** |
| * Change from hotseat/predicted hotseat to taskbar container. |
| */ |
| @Override |
| public void applyOverwritesToLogItem(LauncherAtom.ItemInfo.Builder itemInfoBuilder) { |
| if (!itemInfoBuilder.hasContainerInfo()) { |
| return; |
| } |
| LauncherAtom.ContainerInfo oldContainer = itemInfoBuilder.getContainerInfo(); |
| |
| if (oldContainer.hasPredictedHotseatContainer()) { |
| LauncherAtom.PredictedHotseatContainer predictedHotseat = |
| oldContainer.getPredictedHotseatContainer(); |
| LauncherAtom.TaskBarContainer.Builder taskbarBuilder = |
| LauncherAtom.TaskBarContainer.newBuilder(); |
| |
| if (predictedHotseat.hasIndex()) { |
| taskbarBuilder.setIndex(predictedHotseat.getIndex()); |
| } |
| if (predictedHotseat.hasCardinality()) { |
| taskbarBuilder.setCardinality(predictedHotseat.getCardinality()); |
| } |
| |
| itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder() |
| .setTaskBarContainer(taskbarBuilder)); |
| } else if (oldContainer.hasHotseat()) { |
| LauncherAtom.HotseatContainer hotseat = oldContainer.getHotseat(); |
| LauncherAtom.TaskBarContainer.Builder taskbarBuilder = |
| LauncherAtom.TaskBarContainer.newBuilder(); |
| |
| if (hotseat.hasIndex()) { |
| taskbarBuilder.setIndex(hotseat.getIndex()); |
| } |
| |
| itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder() |
| .setTaskBarContainer(taskbarBuilder)); |
| } else if (oldContainer.hasFolder() && oldContainer.getFolder().hasHotseat()) { |
| LauncherAtom.FolderContainer.Builder folderBuilder = oldContainer.getFolder() |
| .toBuilder(); |
| LauncherAtom.HotseatContainer hotseat = folderBuilder.getHotseat(); |
| LauncherAtom.TaskBarContainer.Builder taskbarBuilder = |
| LauncherAtom.TaskBarContainer.newBuilder(); |
| |
| if (hotseat.hasIndex()) { |
| taskbarBuilder.setIndex(hotseat.getIndex()); |
| } |
| |
| folderBuilder.setTaskbar(taskbarBuilder); |
| folderBuilder.clearHotseat(); |
| itemInfoBuilder.setContainerInfo(LauncherAtom.ContainerInfo.newBuilder() |
| .setFolder(folderBuilder)); |
| } |
| } |
| |
| /** |
| * Sets a new data-source for this taskbar instance |
| */ |
| public void setUIController(@NonNull TaskbarUIController uiController) { |
| mControllers.uiController.onDestroy(); |
| mControllers.uiController = uiController; |
| mControllers.uiController.init(mControllers); |
| } |
| |
| /** |
| * Sets the flag indicating setup UI is visible |
| */ |
| public void setSetupUIVisible(boolean isVisible) { |
| mControllers.taskbarStashController.setSetupUIVisible(isVisible); |
| } |
| |
| /** |
| * Called when this instance of taskbar is no longer needed |
| */ |
| public void onDestroy() { |
| mIsDestroyed = true; |
| setUIController(TaskbarUIController.DEFAULT); |
| mControllers.onDestroy(); |
| mWindowManager.removeViewImmediate(mDragLayer); |
| } |
| |
| public void updateSysuiStateFlags(int systemUiStateFlags) { |
| mControllers.navbarButtonsViewController.updateStateForSysuiFlags(systemUiStateFlags); |
| mControllers.taskbarViewController.setImeIsVisible( |
| mControllers.navbarButtonsViewController.isImeVisible()); |
| int shadeExpandedFlags = SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED |
| | SYSUI_STATE_QUICK_SETTINGS_EXPANDED; |
| onNotificationShadeExpandChanged((systemUiStateFlags & shadeExpandedFlags) != 0); |
| mControllers.taskbarViewController.setRecentsButtonDisabled( |
| mControllers.navbarButtonsViewController.isRecentsDisabled()); |
| mControllers.stashedHandleViewController.setIsHomeButtonDisabled( |
| mControllers.navbarButtonsViewController.isHomeDisabled()); |
| mControllers.taskbarKeyguardController.updateStateForSysuiFlags(systemUiStateFlags); |
| mControllers.taskbarStashController.updateStateForSysuiFlags(systemUiStateFlags); |
| mControllers.taskbarScrimViewController.updateStateForSysuiFlags(systemUiStateFlags); |
| } |
| |
| /** |
| * Hides the taskbar icons and background when the notication shade is expanded. |
| */ |
| private void onNotificationShadeExpandChanged(boolean isExpanded) { |
| float alpha = isExpanded ? 0 : 1; |
| AnimatorSet anim = new AnimatorSet(); |
| anim.play(mControllers.taskbarViewController.getTaskbarIconAlpha().getProperty( |
| TaskbarViewController.ALPHA_INDEX_NOTIFICATION_EXPANDED).animateToValue(alpha)); |
| if (!isThreeButtonNav()) { |
| anim.play(mControllers.taskbarDragLayerController.getNotificationShadeBgTaskbar() |
| .animateToValue(alpha)); |
| } |
| anim.start(); |
| } |
| |
| public void onRotationProposal(int rotation, boolean isValid) { |
| mControllers.rotationButtonController.onRotationProposal(rotation, isValid); |
| } |
| |
| public void disableNavBarElements(int displayId, int state1, int state2, boolean animate) { |
| if (displayId != getDisplayId()) { |
| return; |
| } |
| mControllers.rotationButtonController.onDisable2FlagChanged(state2); |
| } |
| |
| public void onSystemBarAttributesChanged(int displayId, int behavior) { |
| mControllers.rotationButtonController.onBehaviorChanged(displayId, behavior); |
| } |
| |
| /** |
| * Updates the TaskbarContainer to MATCH_PARENT vs original Taskbar size. |
| */ |
| public void setTaskbarWindowFullscreen(boolean fullscreen) { |
| SystemUiProxy.INSTANCE.getNoCreate().notifyTaskbarAutohideSuspend(fullscreen); |
| mIsFullscreen = fullscreen; |
| setTaskbarWindowHeight(fullscreen ? MATCH_PARENT : mLastRequestedNonFullscreenHeight); |
| } |
| |
| public boolean isTaskbarWindowFullscreen() { |
| return mIsFullscreen; |
| } |
| |
| /** |
| * Updates the TaskbarContainer height (pass {@link #getDefaultTaskbarWindowHeight()} to reset). |
| */ |
| public void setTaskbarWindowHeight(int height) { |
| if (mWindowLayoutParams.height == height || mIsDestroyed) { |
| return; |
| } |
| if (height != MATCH_PARENT) { |
| mLastRequestedNonFullscreenHeight = height; |
| if (mIsFullscreen) { |
| // We still need to be fullscreen, so defer any change to our height until we call |
| // setTaskbarWindowFullscreen(false). For example, this could happen when dragging |
| // from the gesture region, as the drag will cancel the gesture and reset launcher's |
| // state, which in turn normally would reset the taskbar window height as well. |
| return; |
| } |
| } |
| mWindowLayoutParams.height = height; |
| mWindowManager.updateViewLayout(mDragLayer, mWindowLayoutParams); |
| } |
| |
| /** |
| * Returns the default height of the window, including the static corner radii above taskbar. |
| */ |
| public int getDefaultTaskbarWindowHeight() { |
| return mDeviceProfile.taskbarSize + Math.max(getLeftCornerRadius(), getRightCornerRadius()); |
| } |
| |
| protected void onTaskbarIconClicked(View view) { |
| Object tag = view.getTag(); |
| if (tag instanceof Task) { |
| Task task = (Task) tag; |
| ActivityManagerWrapper.getInstance().startActivityFromRecents(task.key, |
| ActivityOptions.makeBasic()); |
| } else if (tag instanceof FolderInfo) { |
| FolderIcon folderIcon = (FolderIcon) view; |
| Folder folder = folderIcon.getFolder(); |
| setTaskbarWindowFullscreen(true); |
| |
| getDragLayer().post(() -> { |
| folder.animateOpen(); |
| getStatsLogManager().logger().withItemInfo(folder.mInfo).log(LAUNCHER_FOLDER_OPEN); |
| |
| folder.iterateOverItems((itemInfo, itemView) -> { |
| mControllers.taskbarViewController |
| .setClickAndLongClickListenersForIcon(itemView); |
| // To play haptic when dragging, like other Taskbar items do. |
| itemView.setHapticFeedbackEnabled(true); |
| return false; |
| }); |
| }); |
| } else if (tag instanceof WorkspaceItemInfo) { |
| WorkspaceItemInfo info = (WorkspaceItemInfo) tag; |
| if (info.isDisabled()) { |
| ItemClickHandler.handleDisabledItemClicked(info, this); |
| } else { |
| Intent intent = new Intent(info.getIntent()) |
| .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| try { |
| if (mIsSafeModeEnabled && !PackageManagerHelper.isSystemApp(this, intent)) { |
| Toast.makeText(this, R.string.safemode_shortcut_error, |
| Toast.LENGTH_SHORT).show(); |
| } else if (info.isPromise()) { |
| intent = new PackageManagerHelper(this) |
| .getMarketIntent(info.getTargetPackage()) |
| .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| startActivity(intent); |
| |
| } else if (info.itemType == Favorites.ITEM_TYPE_DEEP_SHORTCUT) { |
| String id = info.getDeepShortcutId(); |
| String packageName = intent.getPackage(); |
| getSystemService(LauncherApps.class) |
| .startShortcut(packageName, id, null, null, info.user); |
| } else if (info.user.equals(Process.myUserHandle())) { |
| startActivity(intent); |
| } else { |
| getSystemService(LauncherApps.class).startMainActivity( |
| intent.getComponent(), info.user, intent.getSourceBounds(), null); |
| } |
| |
| mControllers.uiController.onTaskbarIconLaunched(info); |
| } catch (NullPointerException | ActivityNotFoundException | SecurityException e) { |
| Toast.makeText(this, R.string.activity_not_found, Toast.LENGTH_SHORT) |
| .show(); |
| Log.e(TAG, "Unable to launch. tag=" + info + " intent=" + intent, e); |
| } |
| } |
| } else { |
| Log.e(TAG, "Unknown type clicked: " + tag); |
| } |
| |
| AbstractFloatingView.closeAllOpenViews(this); |
| } |
| |
| /** |
| * Called when we detect a long press in the nav region before passing the gesture slop. |
| * @return Whether taskbar handled the long press, and thus should cancel the gesture. |
| */ |
| public boolean onLongPressToUnstashTaskbar() { |
| return mControllers.taskbarStashController.onLongPressToUnstashTaskbar(); |
| } |
| |
| /** |
| * Called when we detect a motion down or up/cancel in the nav region while stashed. |
| * @param animateForward Whether to animate towards the unstashed hint state or back to stashed. |
| */ |
| public void startTaskbarUnstashHint(boolean animateForward) { |
| mControllers.taskbarStashController.startUnstashHint(animateForward); |
| } |
| |
| protected boolean isUserSetupComplete() { |
| return mIsUserSetupComplete; |
| } |
| } |