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