1 /* 2 * 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.app.ActivityOptions 20 import android.content.ComponentName 21 import android.content.Intent 22 import android.os.Bundle 23 import android.view.View 24 import android.view.ViewGroup 25 import android.view.ViewStub 26 import android.widget.Button 27 import android.widget.TextView 28 import androidx.recyclerview.widget.GridLayoutManager 29 import androidx.recyclerview.widget.ItemTouchHelper 30 import androidx.recyclerview.widget.RecyclerView 31 import com.android.systemui.R 32 import com.android.systemui.broadcast.BroadcastDispatcher 33 import com.android.systemui.controls.CustomIconCache 34 import com.android.systemui.controls.controller.ControlsControllerImpl 35 import com.android.systemui.controls.controller.StructureInfo 36 import com.android.systemui.controls.ui.ControlsActivity 37 import com.android.systemui.controls.ui.ControlsUiController 38 import com.android.systemui.settings.CurrentUserTracker 39 import com.android.systemui.util.LifecycleActivity 40 import javax.inject.Inject 41 42 /** 43 * Activity for rearranging and removing controls for a given structure 44 */ 45 class ControlsEditingActivity @Inject constructor( 46 private val controller: ControlsControllerImpl, 47 private val broadcastDispatcher: BroadcastDispatcher, 48 private val customIconCache: CustomIconCache, 49 private val uiController: ControlsUiController 50 ) : LifecycleActivity() { 51 52 companion object { 53 private const val TAG = "ControlsEditingActivity" 54 private const val EXTRA_STRUCTURE = ControlsFavoritingActivity.EXTRA_STRUCTURE 55 private val SUBTITLE_ID = R.string.controls_favorite_rearrange 56 private val EMPTY_TEXT_ID = R.string.controls_favorite_removed 57 } 58 59 private lateinit var component: ComponentName 60 private lateinit var structure: CharSequence 61 private lateinit var model: FavoritesModel 62 private lateinit var subtitle: TextView 63 private lateinit var saveButton: View 64 65 private val currentUserTracker = object : CurrentUserTracker(broadcastDispatcher) { 66 private val startingUser = controller.currentUserId 67 68 override fun onUserSwitched(newUserId: Int) { 69 if (newUserId != startingUser) { 70 stopTracking() 71 finish() 72 } 73 } 74 } 75 76 override fun onCreate(savedInstanceState: Bundle?) { 77 super.onCreate(savedInstanceState) 78 79 intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)?.let { 80 component = it 81 } ?: run(this::finish) 82 83 intent.getCharSequenceExtra(EXTRA_STRUCTURE)?.let { 84 structure = it 85 } ?: run(this::finish) 86 87 bindViews() 88 89 bindButtons() 90 } 91 92 override fun onStart() { 93 super.onStart() 94 setUpList() 95 96 currentUserTracker.startTracking() 97 } 98 99 override fun onStop() { 100 super.onStop() 101 currentUserTracker.stopTracking() 102 } 103 104 override fun onBackPressed() { 105 animateExitAndFinish() 106 } 107 108 private fun animateExitAndFinish() { 109 val rootView = requireViewById<ViewGroup>(R.id.controls_management_root) 110 ControlsAnimations.exitAnimation( 111 rootView, 112 object : Runnable { 113 override fun run() { 114 finish() 115 } 116 } 117 ).start() 118 } 119 120 private fun bindViews() { 121 setContentView(R.layout.controls_management) 122 123 getLifecycle().addObserver( 124 ControlsAnimations.observerForAnimations( 125 requireViewById<ViewGroup>(R.id.controls_management_root), 126 window, 127 intent 128 ) 129 ) 130 131 requireViewById<ViewStub>(R.id.stub).apply { 132 layoutResource = R.layout.controls_management_editing 133 inflate() 134 } 135 requireViewById<TextView>(R.id.title).text = structure 136 setTitle(structure) 137 subtitle = requireViewById<TextView>(R.id.subtitle).apply { 138 setText(SUBTITLE_ID) 139 } 140 } 141 142 private fun bindButtons() { 143 saveButton = requireViewById<Button>(R.id.done).apply { 144 isEnabled = false 145 setText(R.string.save) 146 setOnClickListener { 147 saveFavorites() 148 startActivity( 149 Intent(applicationContext, ControlsActivity::class.java), 150 ActivityOptions 151 .makeSceneTransitionAnimation(this@ControlsEditingActivity).toBundle() 152 ) 153 animateExitAndFinish() 154 } 155 } 156 } 157 158 private fun saveFavorites() { 159 controller.replaceFavoritesForStructure( 160 StructureInfo(component, structure, model.favorites)) 161 } 162 163 private val favoritesModelCallback = object : FavoritesModel.FavoritesModelCallback { 164 override fun onNoneChanged(showNoFavorites: Boolean) { 165 if (showNoFavorites) { 166 subtitle.setText(EMPTY_TEXT_ID) 167 } else { 168 subtitle.setText(SUBTITLE_ID) 169 } 170 } 171 172 override fun onFirstChange() { 173 saveButton.isEnabled = true 174 } 175 } 176 177 private fun setUpList() { 178 val controls = controller.getFavoritesForStructure(component, structure) 179 model = FavoritesModel(customIconCache, component, controls, favoritesModelCallback) 180 val elevation = resources.getFloat(R.dimen.control_card_elevation) 181 val recyclerView = requireViewById<RecyclerView>(R.id.list) 182 recyclerView.alpha = 0.0f 183 val adapter = ControlAdapter(elevation).apply { 184 registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { 185 var hasAnimated = false 186 override fun onChanged() { 187 if (!hasAnimated) { 188 hasAnimated = true 189 ControlsAnimations.enterAnimation(recyclerView).start() 190 } 191 } 192 }) 193 } 194 195 val margin = resources 196 .getDimensionPixelSize(R.dimen.controls_card_margin) 197 val itemDecorator = MarginItemDecorator(margin, margin) 198 199 recyclerView.apply { 200 this.adapter = adapter 201 layoutManager = object : GridLayoutManager(recyclerView.context, 2) { 202 203 // This will remove from the announcement the row corresponding to the divider, 204 // as it's not something that should be announced. 205 override fun getRowCountForAccessibility( 206 recycler: RecyclerView.Recycler, 207 state: RecyclerView.State 208 ): Int { 209 val initial = super.getRowCountForAccessibility(recycler, state) 210 return if (initial > 0) initial - 1 else initial 211 } 212 }.apply { 213 spanSizeLookup = adapter.spanSizeLookup 214 } 215 addItemDecoration(itemDecorator) 216 } 217 adapter.changeModel(model) 218 model.attachAdapter(adapter) 219 ItemTouchHelper(model.itemTouchHelperCallback).attachToRecyclerView(recyclerView) 220 } 221 222 override fun onDestroy() { 223 currentUserTracker.stopTracking() 224 super.onDestroy() 225 } 226 } 227