1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.launcher3.taskbar.customization 18 19 import android.annotation.SuppressLint 20 import android.content.Context 21 import android.content.res.ColorStateList 22 import android.graphics.Color.TRANSPARENT 23 import android.util.AttributeSet 24 import android.view.MotionEvent 25 import android.view.View 26 import android.view.ViewConfiguration 27 import androidx.annotation.DimenRes 28 import androidx.annotation.DrawableRes 29 import androidx.core.view.setPadding 30 import com.android.launcher3.R 31 import com.android.launcher3.Utilities.dpToPx 32 import com.android.launcher3.config.FeatureFlags.enableTaskbarPinning 33 import com.android.launcher3.taskbar.TaskbarActivityContext 34 import com.android.launcher3.taskbar.TaskbarViewCallbacks 35 import com.android.launcher3.util.Executors.MAIN_EXECUTOR 36 import com.android.launcher3.views.ActivityContext 37 import com.android.launcher3.views.IconButtonView 38 import com.android.quickstep.DeviceConfigWrapper 39 import com.android.quickstep.util.ContextualSearchStateManager 40 import com.android.wm.shell.Flags 41 42 /** Taskbar all apps button container for customizable taskbar. */ 43 class TaskbarAllAppsButtonContainer 44 @JvmOverloads 45 constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : 46 IconButtonView(context, attrs), TaskbarContainer { 47 48 private val activityContext: TaskbarActivityContext = ActivityContext.lookupContext(context) 49 private var allAppsTouchTriggered = false 50 private var allAppsTouchRunnable: Runnable? = null 51 private var allAppsButtonTouchDelayMs: Long = ViewConfiguration.getLongPressTimeout().toLong() 52 private lateinit var taskbarViewCallbacks: TaskbarViewCallbacks 53 54 override val spaceNeeded: Int 55 get() { 56 return dpToPx(activityContext.taskbarSpecsEvaluator.taskbarIconSize.size.toFloat()) 57 } 58 59 init { 60 contentDescription = context.getString(R.string.all_apps_button_label) 61 setUpIcon() 62 } 63 64 @SuppressLint("UseCompatLoadingForDrawables", "ResourceAsColor") setUpIconnull65 private fun setUpIcon() { 66 val drawable = 67 resources.getDrawable( 68 getAllAppsButton(activityContext.taskbarFeatureEvaluator.isTransient) 69 ) 70 backgroundTintList = ColorStateList.valueOf(TRANSPARENT) 71 setIconDrawable(drawable) 72 if (!activityContext.isTransientTaskbar) { 73 setPadding(dpToPx(activityContext.taskbarSpecsEvaluator.taskbarIconPadding.toFloat())) 74 } 75 setForegroundTint(activityContext.getColor(R.color.all_apps_button_color)) 76 } 77 78 @SuppressLint("ClickableViewAccessibility") setUpCallbacksnull79 fun setUpCallbacks(callbacks: TaskbarViewCallbacks) { 80 taskbarViewCallbacks = callbacks 81 setOnClickListener(this::onAllAppsButtonClick) 82 setOnLongClickListener(this::onAllAppsButtonLongClick) 83 setOnTouchListener(this::onAllAppsButtonTouch) 84 isHapticFeedbackEnabled = 85 taskbarViewCallbacks.isAllAppsButtonHapticFeedbackEnabled(mContext) 86 allAppsTouchRunnable = Runnable { 87 taskbarViewCallbacks.triggerAllAppsButtonLongClick() 88 allAppsTouchTriggered = true 89 } 90 val contextualSearchStateManager = ContextualSearchStateManager.INSTANCE[mContext] 91 if ( 92 DeviceConfigWrapper.get().customLpaaThresholds && 93 contextualSearchStateManager.lpnhDurationMillis.isPresent 94 ) { 95 allAppsButtonTouchDelayMs = contextualSearchStateManager.lpnhDurationMillis.get() 96 } 97 } 98 99 @DrawableRes getAllAppsButtonnull100 private fun getAllAppsButton(isTransientTaskbar: Boolean): Int { 101 if (Flags.enableGsf()) { 102 return getAllAppsButtonForExpressiveTheme() 103 } 104 val shouldSelectTransientIcon = 105 isTransientTaskbar || (enableTaskbarPinning() && !activityContext.isThreeButtonNav) 106 return if (shouldSelectTransientIcon) R.drawable.ic_transient_taskbar_all_apps_search_button 107 else R.drawable.ic_taskbar_all_apps_search_button 108 } 109 110 @DrawableRes getAllAppsButtonForExpressiveThemenull111 private fun getAllAppsButtonForExpressiveTheme(): Int { 112 return R.drawable.ic_taskbar_all_apps_search_button_expressive_theme 113 } 114 115 @DimenRes getAllAppsButtonTranslationXOffsetForExpressiveThemenull116 fun getAllAppsButtonTranslationXOffsetForExpressiveTheme(isTransientTaskbar: Boolean): Int { 117 return if (isTransientTaskbar) { 118 R.dimen.transient_taskbar_all_apps_button_translation_x_offset_for_expressive_theme 119 } else { 120 R.dimen.taskbar_all_apps_search_button_translation_x_offset_for_expressive_theme 121 } 122 } 123 124 @DimenRes getAllAppsButtonTranslationXOffsetnull125 fun getAllAppsButtonTranslationXOffset(isTransientTaskbar: Boolean): Int { 126 if (Flags.enableGsf()) { 127 return getAllAppsButtonTranslationXOffsetForExpressiveTheme(isTransientTaskbar) 128 } 129 return if (isTransientTaskbar) { 130 R.dimen.transient_taskbar_all_apps_button_translation_x_offset 131 } else { 132 R.dimen.taskbar_all_apps_search_button_translation_x_offset 133 } 134 } 135 onAllAppsButtonTouchnull136 private fun onAllAppsButtonTouch(view: View, ev: MotionEvent): Boolean { 137 when (ev.action) { 138 MotionEvent.ACTION_DOWN -> { 139 allAppsTouchTriggered = false 140 MAIN_EXECUTOR.handler.postDelayed(allAppsTouchRunnable!!, allAppsButtonTouchDelayMs) 141 } 142 MotionEvent.ACTION_UP, 143 MotionEvent.ACTION_CANCEL -> cancelAllAppsButtonTouch() 144 } 145 return false 146 } 147 cancelAllAppsButtonTouchnull148 private fun cancelAllAppsButtonTouch() { 149 MAIN_EXECUTOR.handler.removeCallbacks(allAppsTouchRunnable!!) 150 // ACTION_UP is first triggered, then click listener / long-click listener is triggered on 151 // the next frame, so we need to post twice and delay the reset. 152 this.post { this.post { allAppsTouchTriggered = false } } 153 } 154 onAllAppsButtonClicknull155 private fun onAllAppsButtonClick(view: View) { 156 if (!allAppsTouchTriggered) { 157 taskbarViewCallbacks.triggerAllAppsButtonClick(view) 158 } 159 } 160 161 // Handle long click from Switch Access and Voice Access onAllAppsButtonLongClicknull162 private fun onAllAppsButtonLongClick(view: View): Boolean { 163 if (!MAIN_EXECUTOR.handler.hasCallbacks(allAppsTouchRunnable!!) && !allAppsTouchTriggered) { 164 taskbarViewCallbacks.triggerAllAppsButtonLongClick() 165 } 166 return true 167 } 168 } 169