/* * Copyright (C) 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.launcher3.taskbar.customization import android.annotation.SuppressLint import android.content.Context import android.content.res.ColorStateList import android.graphics.Color.TRANSPARENT import android.util.AttributeSet import android.view.MotionEvent import android.view.View import android.view.ViewConfiguration import androidx.annotation.DimenRes import androidx.annotation.DrawableRes import androidx.core.view.setPadding import com.android.launcher3.R import com.android.launcher3.Utilities.dpToPx import com.android.launcher3.config.FeatureFlags.enableTaskbarPinning import com.android.launcher3.taskbar.TaskbarActivityContext import com.android.launcher3.taskbar.TaskbarViewCallbacks import com.android.launcher3.util.Executors.MAIN_EXECUTOR import com.android.launcher3.views.ActivityContext import com.android.launcher3.views.IconButtonView import com.android.quickstep.DeviceConfigWrapper import com.android.quickstep.util.ContextualSearchStateManager import com.android.wm.shell.Flags /** Taskbar all apps button container for customizable taskbar. */ class TaskbarAllAppsButtonContainer @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : IconButtonView(context, attrs), TaskbarContainer { private val activityContext: TaskbarActivityContext = ActivityContext.lookupContext(context) private var allAppsTouchTriggered = false private var allAppsTouchRunnable: Runnable? = null private var allAppsButtonTouchDelayMs: Long = ViewConfiguration.getLongPressTimeout().toLong() private lateinit var taskbarViewCallbacks: TaskbarViewCallbacks override val spaceNeeded: Int get() { return dpToPx(activityContext.taskbarSpecsEvaluator.taskbarIconSize.size.toFloat()) } init { contentDescription = context.getString(R.string.all_apps_button_label) setUpIcon() } @SuppressLint("UseCompatLoadingForDrawables", "ResourceAsColor") private fun setUpIcon() { val drawable = resources.getDrawable( getAllAppsButton(activityContext.taskbarFeatureEvaluator.isTransient) ) backgroundTintList = ColorStateList.valueOf(TRANSPARENT) setIconDrawable(drawable) if (!activityContext.isTransientTaskbar) { setPadding(dpToPx(activityContext.taskbarSpecsEvaluator.taskbarIconPadding.toFloat())) } setForegroundTint(activityContext.getColor(R.color.all_apps_button_color)) } @SuppressLint("ClickableViewAccessibility") fun setUpCallbacks(callbacks: TaskbarViewCallbacks) { taskbarViewCallbacks = callbacks setOnClickListener(this::onAllAppsButtonClick) setOnLongClickListener(this::onAllAppsButtonLongClick) setOnTouchListener(this::onAllAppsButtonTouch) isHapticFeedbackEnabled = taskbarViewCallbacks.isAllAppsButtonHapticFeedbackEnabled(mContext) allAppsTouchRunnable = Runnable { taskbarViewCallbacks.triggerAllAppsButtonLongClick() allAppsTouchTriggered = true } val contextualSearchStateManager = ContextualSearchStateManager.INSTANCE[mContext] if ( DeviceConfigWrapper.get().customLpaaThresholds && contextualSearchStateManager.lpnhDurationMillis.isPresent ) { allAppsButtonTouchDelayMs = contextualSearchStateManager.lpnhDurationMillis.get() } } @DrawableRes private fun getAllAppsButton(isTransientTaskbar: Boolean): Int { if (Flags.enableGsf()) { return getAllAppsButtonForExpressiveTheme() } val shouldSelectTransientIcon = isTransientTaskbar || (enableTaskbarPinning() && !activityContext.isThreeButtonNav) return if (shouldSelectTransientIcon) R.drawable.ic_transient_taskbar_all_apps_search_button else R.drawable.ic_taskbar_all_apps_search_button } @DrawableRes private fun getAllAppsButtonForExpressiveTheme(): Int { return R.drawable.ic_taskbar_all_apps_search_button_expressive_theme } @DimenRes fun getAllAppsButtonTranslationXOffsetForExpressiveTheme(isTransientTaskbar: Boolean): Int { return if (isTransientTaskbar) { R.dimen.transient_taskbar_all_apps_button_translation_x_offset_for_expressive_theme } else { R.dimen.taskbar_all_apps_search_button_translation_x_offset_for_expressive_theme } } @DimenRes fun getAllAppsButtonTranslationXOffset(isTransientTaskbar: Boolean): Int { if (Flags.enableGsf()) { return getAllAppsButtonTranslationXOffsetForExpressiveTheme(isTransientTaskbar) } return if (isTransientTaskbar) { R.dimen.transient_taskbar_all_apps_button_translation_x_offset } else { R.dimen.taskbar_all_apps_search_button_translation_x_offset } } private fun onAllAppsButtonTouch(view: View, ev: MotionEvent): Boolean { when (ev.action) { MotionEvent.ACTION_DOWN -> { allAppsTouchTriggered = false MAIN_EXECUTOR.handler.postDelayed(allAppsTouchRunnable!!, allAppsButtonTouchDelayMs) } MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> cancelAllAppsButtonTouch() } return false } private fun cancelAllAppsButtonTouch() { MAIN_EXECUTOR.handler.removeCallbacks(allAppsTouchRunnable!!) // ACTION_UP is first triggered, then click listener / long-click listener is triggered on // the next frame, so we need to post twice and delay the reset. this.post { this.post { allAppsTouchTriggered = false } } } private fun onAllAppsButtonClick(view: View) { if (!allAppsTouchTriggered) { taskbarViewCallbacks.triggerAllAppsButtonClick(view) } } // Handle long click from Switch Access and Voice Access private fun onAllAppsButtonLongClick(view: View): Boolean { if (!MAIN_EXECUTOR.handler.hasCallbacks(allAppsTouchRunnable!!) && !allAppsTouchTriggered) { taskbarViewCallbacks.triggerAllAppsButtonLongClick() } return true } }