1 /* <lambda>null2 * Copyright (C) 2020 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.systemui.controls.management 18 19 import android.animation.Animator 20 import android.animation.AnimatorListenerAdapter 21 import android.app.ActivityOptions 22 import android.content.ComponentName 23 import android.content.Intent 24 import android.content.res.Configuration 25 import android.os.Bundle 26 import android.text.TextUtils 27 import android.view.Gravity 28 import android.view.View 29 import android.view.ViewGroup 30 import android.view.ViewStub 31 import android.widget.Button 32 import android.widget.FrameLayout 33 import android.widget.TextView 34 import android.widget.Toast 35 import androidx.viewpager2.widget.ViewPager2 36 import com.android.systemui.Prefs 37 import com.android.systemui.R 38 import com.android.systemui.broadcast.BroadcastDispatcher 39 import com.android.systemui.controls.ControlsServiceInfo 40 import com.android.systemui.controls.TooltipManager 41 import com.android.systemui.controls.controller.ControlsControllerImpl 42 import com.android.systemui.controls.controller.StructureInfo 43 import com.android.systemui.dagger.qualifiers.Main 44 import com.android.systemui.globalactions.GlobalActionsComponent 45 import com.android.systemui.settings.CurrentUserTracker 46 import com.android.systemui.util.LifecycleActivity 47 import java.text.Collator 48 import java.util.concurrent.Executor 49 import java.util.function.Consumer 50 import javax.inject.Inject 51 52 class ControlsFavoritingActivity @Inject constructor( 53 @Main private val executor: Executor, 54 private val controller: ControlsControllerImpl, 55 private val listingController: ControlsListingController, 56 broadcastDispatcher: BroadcastDispatcher, 57 private val globalActionsComponent: GlobalActionsComponent 58 ) : LifecycleActivity() { 59 60 companion object { 61 private const val TAG = "ControlsFavoritingActivity" 62 63 // If provided and no structure is available, use as the title 64 const val EXTRA_APP = "extra_app_label" 65 66 // If provided, show this structure page first 67 const val EXTRA_STRUCTURE = "extra_structure" 68 const val EXTRA_SINGLE_STRUCTURE = "extra_single_structure" 69 internal const val EXTRA_FROM_PROVIDER_SELECTOR = "extra_from_provider_selector" 70 private const val TOOLTIP_PREFS_KEY = Prefs.Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT 71 private const val TOOLTIP_MAX_SHOWN = 2 72 } 73 74 private var component: ComponentName? = null 75 private var appName: CharSequence? = null 76 private var structureExtra: CharSequence? = null 77 private var fromProviderSelector = false 78 79 private lateinit var structurePager: ViewPager2 80 private lateinit var statusText: TextView 81 private lateinit var titleView: TextView 82 private lateinit var subtitleView: TextView 83 private lateinit var pageIndicator: ManagementPageIndicator 84 private var mTooltipManager: TooltipManager? = null 85 private lateinit var doneButton: View 86 private lateinit var otherAppsButton: View 87 private var listOfStructures = emptyList<StructureContainer>() 88 89 private lateinit var comparator: Comparator<StructureContainer> 90 private var cancelLoadRunnable: Runnable? = null 91 private var isPagerLoaded = false 92 93 private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) { 94 private val startingUser = controller.currentUserId 95 96 override fun onUserSwitched(newUserId: Int) { 97 if (newUserId != startingUser) { 98 stopTracking() 99 finish() 100 } 101 } 102 } 103 104 private val listingCallback = object : ControlsListingController.ControlsListingCallback { 105 106 override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) { 107 if (serviceInfos.size > 1) { 108 otherAppsButton.post { 109 otherAppsButton.visibility = View.VISIBLE 110 } 111 } 112 } 113 } 114 115 override fun onBackPressed() { 116 if (!fromProviderSelector) { 117 globalActionsComponent.handleShowGlobalActionsMenu() 118 } 119 animateExitAndFinish() 120 } 121 122 override fun onCreate(savedInstanceState: Bundle?) { 123 super.onCreate(savedInstanceState) 124 125 val collator = Collator.getInstance(resources.configuration.locales[0]) 126 comparator = compareBy(collator) { it.structureName } 127 appName = intent.getCharSequenceExtra(EXTRA_APP) 128 structureExtra = intent.getCharSequenceExtra(EXTRA_STRUCTURE) 129 component = intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME) 130 fromProviderSelector = intent.getBooleanExtra(EXTRA_FROM_PROVIDER_SELECTOR, false) 131 132 bindViews() 133 } 134 135 private val controlsModelCallback = object : ControlsModel.ControlsModelCallback { 136 override fun onFirstChange() { 137 doneButton.isEnabled = true 138 } 139 } 140 141 private fun loadControls() { 142 component?.let { 143 statusText.text = resources.getText(com.android.internal.R.string.loading) 144 val emptyZoneString = resources.getText( 145 R.string.controls_favorite_other_zone_header) 146 controller.loadForComponent(it, Consumer { data -> 147 val allControls = data.allControls 148 val favoriteKeys = data.favoritesIds 149 val error = data.errorOnLoad 150 val controlsByStructure = allControls.groupBy { it.control.structure ?: "" } 151 listOfStructures = controlsByStructure.map { 152 StructureContainer(it.key, AllModel( 153 it.value, favoriteKeys, emptyZoneString, controlsModelCallback)) 154 }.sortedWith(comparator) 155 156 val structureIndex = listOfStructures.indexOfFirst { 157 sc -> sc.structureName == structureExtra 158 }.let { if (it == -1) 0 else it } 159 160 // If we were requested to show a single structure, set the list to just that one 161 if (intent.getBooleanExtra(EXTRA_SINGLE_STRUCTURE, false)) { 162 listOfStructures = listOf(listOfStructures[structureIndex]) 163 } 164 165 executor.execute { 166 structurePager.adapter = StructureAdapter(listOfStructures) 167 structurePager.setCurrentItem(structureIndex) 168 if (error) { 169 statusText.text = resources.getString(R.string.controls_favorite_load_error, 170 appName ?: "") 171 subtitleView.visibility = View.GONE 172 } else if (listOfStructures.isEmpty()) { 173 statusText.text = resources.getString(R.string.controls_favorite_load_none) 174 subtitleView.visibility = View.GONE 175 } else { 176 statusText.visibility = View.GONE 177 178 pageIndicator.setNumPages(listOfStructures.size) 179 pageIndicator.setLocation(0f) 180 pageIndicator.visibility = 181 if (listOfStructures.size > 1) View.VISIBLE else View.INVISIBLE 182 183 ControlsAnimations.enterAnimation(pageIndicator).apply { 184 addListener(object : AnimatorListenerAdapter() { 185 override fun onAnimationEnd(animation: Animator?) { 186 // Position the tooltip if necessary after animations are complete 187 // so we can get the position on screen. The tooltip is not 188 // rooted in the layout root. 189 if (pageIndicator.visibility == View.VISIBLE && 190 mTooltipManager != null) { 191 val p = IntArray(2) 192 pageIndicator.getLocationOnScreen(p) 193 val x = p[0] + pageIndicator.width / 2 194 val y = p[1] + pageIndicator.height 195 mTooltipManager?.show( 196 R.string.controls_structure_tooltip, x, y) 197 } 198 } 199 }) 200 }.start() 201 ControlsAnimations.enterAnimation(structurePager).start() 202 } 203 } 204 }, Consumer { runnable -> cancelLoadRunnable = runnable }) 205 } 206 } 207 208 private fun setUpPager() { 209 structurePager.alpha = 0.0f 210 pageIndicator.alpha = 0.0f 211 structurePager.apply { 212 adapter = StructureAdapter(emptyList()) 213 registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { 214 override fun onPageSelected(position: Int) { 215 super.onPageSelected(position) 216 val name = listOfStructures[position].structureName 217 val title = if (!TextUtils.isEmpty(name)) name else appName 218 titleView.text = title 219 titleView.requestFocus() 220 } 221 222 override fun onPageScrolled( 223 position: Int, 224 positionOffset: Float, 225 positionOffsetPixels: Int 226 ) { 227 super.onPageScrolled(position, positionOffset, positionOffsetPixels) 228 pageIndicator.setLocation(position + positionOffset) 229 } 230 }) 231 } 232 } 233 234 private fun bindViews() { 235 setContentView(R.layout.controls_management) 236 237 getLifecycle().addObserver( 238 ControlsAnimations.observerForAnimations( 239 requireViewById<ViewGroup>(R.id.controls_management_root), 240 window, 241 intent 242 ) 243 ) 244 245 requireViewById<ViewStub>(R.id.stub).apply { 246 layoutResource = R.layout.controls_management_favorites 247 inflate() 248 } 249 250 statusText = requireViewById(R.id.status_message) 251 if (shouldShowTooltip()) { 252 mTooltipManager = TooltipManager(statusText.context, 253 TOOLTIP_PREFS_KEY, TOOLTIP_MAX_SHOWN) 254 addContentView( 255 mTooltipManager?.layout, 256 FrameLayout.LayoutParams( 257 ViewGroup.LayoutParams.WRAP_CONTENT, 258 ViewGroup.LayoutParams.WRAP_CONTENT, 259 Gravity.TOP or Gravity.LEFT 260 ) 261 ) 262 } 263 pageIndicator = requireViewById<ManagementPageIndicator>( 264 R.id.structure_page_indicator).apply { 265 visibilityListener = { 266 if (it != View.VISIBLE) { 267 mTooltipManager?.hide(true) 268 } 269 } 270 } 271 272 val title = structureExtra 273 ?: (appName ?: resources.getText(R.string.controls_favorite_default_title)) 274 titleView = requireViewById<TextView>(R.id.title).apply { 275 text = title 276 } 277 subtitleView = requireViewById<TextView>(R.id.subtitle).apply { 278 text = resources.getText(R.string.controls_favorite_subtitle) 279 } 280 structurePager = requireViewById<ViewPager2>(R.id.structure_pager) 281 structurePager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { 282 override fun onPageSelected(position: Int) { 283 super.onPageSelected(position) 284 mTooltipManager?.hide(true) 285 } 286 }) 287 bindButtons() 288 } 289 290 private fun animateExitAndFinish() { 291 val rootView = requireViewById<ViewGroup>(R.id.controls_management_root) 292 ControlsAnimations.exitAnimation( 293 rootView, 294 object : Runnable { 295 override fun run() { 296 finish() 297 } 298 } 299 ).start() 300 } 301 302 private fun bindButtons() { 303 otherAppsButton = requireViewById<Button>(R.id.other_apps).apply { 304 setOnClickListener { 305 val i = Intent().apply { 306 component = ComponentName(context, ControlsProviderSelectorActivity::class.java) 307 } 308 if (doneButton.isEnabled) { 309 // The user has made changes 310 Toast.makeText( 311 applicationContext, 312 R.string.controls_favorite_toast_no_changes, 313 Toast.LENGTH_SHORT 314 ).show() 315 } 316 startActivity(i, ActivityOptions 317 .makeSceneTransitionAnimation(this@ControlsFavoritingActivity).toBundle()) 318 animateExitAndFinish() 319 } 320 } 321 322 doneButton = requireViewById<Button>(R.id.done).apply { 323 isEnabled = false 324 setOnClickListener { 325 if (component == null) return@setOnClickListener 326 listOfStructures.forEach { 327 val favoritesForStorage = it.model.favorites 328 controller.replaceFavoritesForStructure( 329 StructureInfo(component!!, it.structureName, favoritesForStorage) 330 ) 331 } 332 animateExitAndFinish() 333 globalActionsComponent.handleShowGlobalActionsMenu() 334 } 335 } 336 } 337 338 override fun onPause() { 339 super.onPause() 340 mTooltipManager?.hide(false) 341 } 342 343 override fun onStart() { 344 super.onStart() 345 346 listingController.addCallback(listingCallback) 347 currentUserTracker.startTracking() 348 } 349 350 override fun onResume() { 351 super.onResume() 352 353 // only do once, to make sure that any user changes do not get replaces if resume is called 354 // more than once 355 if (!isPagerLoaded) { 356 setUpPager() 357 loadControls() 358 isPagerLoaded = true 359 } 360 } 361 362 override fun onStop() { 363 super.onStop() 364 365 listingController.removeCallback(listingCallback) 366 currentUserTracker.stopTracking() 367 } 368 369 override fun onConfigurationChanged(newConfig: Configuration) { 370 super.onConfigurationChanged(newConfig) 371 mTooltipManager?.hide(false) 372 } 373 374 override fun onDestroy() { 375 cancelLoadRunnable?.run() 376 super.onDestroy() 377 } 378 379 private fun shouldShowTooltip(): Boolean { 380 return Prefs.getInt(applicationContext, TOOLTIP_PREFS_KEY, 0) < TOOLTIP_MAX_SHOWN 381 } 382 } 383 384 data class StructureContainer(val structureName: CharSequence, val model: ControlsModel) 385