• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Context
22 import android.content.Intent
23 import android.os.Bundle
24 import android.util.Log
25 import android.view.View
26 import android.view.ViewGroup
27 import android.view.ViewStub
28 import android.widget.Button
29 import android.widget.TextView
30 import android.window.OnBackInvokedCallback
31 import android.window.OnBackInvokedDispatcher
32 import androidx.activity.ComponentActivity
33 import androidx.recyclerview.widget.GridLayoutManager
34 import androidx.recyclerview.widget.ItemTouchHelper
35 import androidx.recyclerview.widget.RecyclerView
36 import com.android.systemui.R
37 import com.android.systemui.controls.CustomIconCache
38 import com.android.systemui.controls.controller.ControlsControllerImpl
39 import com.android.systemui.controls.controller.StructureInfo
40 import com.android.systemui.controls.ui.ControlsActivity
41 import com.android.systemui.controls.ui.ControlsUiController
42 import com.android.systemui.dagger.qualifiers.Main
43 import com.android.systemui.settings.UserTracker
44 import java.util.concurrent.Executor
45 import javax.inject.Inject
46 
47 /**
48  * Activity for rearranging and removing controls for a given structure
49  */
50 open class ControlsEditingActivity @Inject constructor(
51     @Main private val mainExecutor: Executor,
52     private val controller: ControlsControllerImpl,
53     private val userTracker: UserTracker,
54     private val customIconCache: CustomIconCache,
55     private val uiController: ControlsUiController
56 ) : ComponentActivity() {
57 
58     companion object {
59         private const val DEBUG = false
60         private const val TAG = "ControlsEditingActivity"
61         const val EXTRA_STRUCTURE = ControlsFavoritingActivity.EXTRA_STRUCTURE
62         private val SUBTITLE_ID = R.string.controls_favorite_rearrange
63         private val EMPTY_TEXT_ID = R.string.controls_favorite_removed
64     }
65 
66     private lateinit var component: ComponentName
67     private lateinit var structure: CharSequence
68     private lateinit var model: FavoritesModel
69     private lateinit var subtitle: TextView
70     private lateinit var saveButton: View
71 
72     private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
73         private val startingUser = controller.currentUserId
74 
onUserChangednull75         override fun onUserChanged(newUser: Int, userContext: Context) {
76             if (newUser != startingUser) {
77                 userTracker.removeCallback(this)
78                 finish()
79             }
80         }
81     }
82 
<lambda>null83     private val mOnBackInvokedCallback = OnBackInvokedCallback {
84         if (DEBUG) {
85             Log.d(TAG, "Predictive Back dispatcher called mOnBackInvokedCallback")
86         }
87         onBackPressed()
88     }
89 
onCreatenull90     override fun onCreate(savedInstanceState: Bundle?) {
91         super.onCreate(savedInstanceState)
92 
93         intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)?.let {
94             component = it
95         } ?: run(this::finish)
96 
97         intent.getCharSequenceExtra(EXTRA_STRUCTURE)?.let {
98             structure = it
99         } ?: run(this::finish)
100 
101         bindViews()
102 
103         bindButtons()
104     }
105 
onStartnull106     override fun onStart() {
107         super.onStart()
108         setUpList()
109 
110         userTracker.addCallback(userTrackerCallback, mainExecutor)
111 
112         if (DEBUG) {
113             Log.d(TAG, "Registered onBackInvokedCallback")
114         }
115         onBackInvokedDispatcher.registerOnBackInvokedCallback(
116                 OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback)
117     }
118 
onStopnull119     override fun onStop() {
120         super.onStop()
121         userTracker.removeCallback(userTrackerCallback)
122 
123         if (DEBUG) {
124             Log.d(TAG, "Unregistered onBackInvokedCallback")
125         }
126         onBackInvokedDispatcher.unregisterOnBackInvokedCallback(mOnBackInvokedCallback)
127     }
128 
onBackPressednull129     override fun onBackPressed() {
130         animateExitAndFinish()
131     }
132 
animateExitAndFinishnull133     private fun animateExitAndFinish() {
134         val rootView = requireViewById<ViewGroup>(R.id.controls_management_root)
135         ControlsAnimations.exitAnimation(
136                 rootView,
137                 object : Runnable {
138                     override fun run() {
139                         finish()
140                     }
141                 }
142         ).start()
143     }
144 
bindViewsnull145     private fun bindViews() {
146         setContentView(R.layout.controls_management)
147 
148         getLifecycle().addObserver(
149             ControlsAnimations.observerForAnimations(
150                 requireViewById<ViewGroup>(R.id.controls_management_root),
151                 window,
152                 intent
153             )
154         )
155 
156         requireViewById<ViewStub>(R.id.stub).apply {
157             layoutResource = R.layout.controls_management_editing
158             inflate()
159         }
160         requireViewById<TextView>(R.id.title).text = structure
161         setTitle(structure)
162         subtitle = requireViewById<TextView>(R.id.subtitle).apply {
163             setText(SUBTITLE_ID)
164         }
165     }
166 
bindButtonsnull167     private fun bindButtons() {
168         saveButton = requireViewById<Button>(R.id.done).apply {
169             isEnabled = false
170             setText(R.string.save)
171             setOnClickListener {
172                 saveFavorites()
173                 startActivity(
174                     Intent(applicationContext, ControlsActivity::class.java),
175                     ActivityOptions
176                         .makeSceneTransitionAnimation(this@ControlsEditingActivity).toBundle()
177                 )
178                 animateExitAndFinish()
179             }
180         }
181     }
182 
saveFavoritesnull183     private fun saveFavorites() {
184         controller.replaceFavoritesForStructure(
185                 StructureInfo(component, structure, model.favorites))
186     }
187 
188     private val favoritesModelCallback = object : FavoritesModel.FavoritesModelCallback {
onNoneChangednull189         override fun onNoneChanged(showNoFavorites: Boolean) {
190             if (showNoFavorites) {
191                 subtitle.setText(EMPTY_TEXT_ID)
192             } else {
193                 subtitle.setText(SUBTITLE_ID)
194             }
195         }
196 
onFirstChangenull197         override fun onFirstChange() {
198             saveButton.isEnabled = true
199         }
200     }
201 
setUpListnull202     private fun setUpList() {
203         val controls = controller.getFavoritesForStructure(component, structure)
204         model = FavoritesModel(customIconCache, component, controls, favoritesModelCallback)
205         val elevation = resources.getFloat(R.dimen.control_card_elevation)
206         val recyclerView = requireViewById<RecyclerView>(R.id.list)
207         recyclerView.alpha = 0.0f
208         val adapter = ControlAdapter(elevation).apply {
209             registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {
210                 var hasAnimated = false
211                 override fun onChanged() {
212                     if (!hasAnimated) {
213                         hasAnimated = true
214                         ControlsAnimations.enterAnimation(recyclerView).start()
215                     }
216                 }
217             })
218         }
219 
220         val margin = resources
221                 .getDimensionPixelSize(R.dimen.controls_card_margin)
222         val itemDecorator = MarginItemDecorator(margin, margin)
223         val spanCount = ControlAdapter.findMaxColumns(resources)
224 
225         recyclerView.apply {
226             this.adapter = adapter
227             layoutManager = object : GridLayoutManager(recyclerView.context, spanCount) {
228 
229                 // This will remove from the announcement the row corresponding to the divider,
230                 // as it's not something that should be announced.
231                 override fun getRowCountForAccessibility(
232                     recycler: RecyclerView.Recycler,
233                     state: RecyclerView.State
234                 ): Int {
235                     val initial = super.getRowCountForAccessibility(recycler, state)
236                     return if (initial > 0) initial - 1 else initial
237                 }
238             }.apply {
239                 spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
240                     override fun getSpanSize(position: Int): Int {
241                         return if (adapter?.getItemViewType(position)
242                                 != ControlAdapter.TYPE_CONTROL) spanCount else 1
243                     }
244                 }
245             }
246             addItemDecoration(itemDecorator)
247         }
248         adapter.changeModel(model)
249         model.attachAdapter(adapter)
250         ItemTouchHelper(model.itemTouchHelperCallback).attachToRecyclerView(recyclerView)
251     }
252 
onDestroynull253     override fun onDestroy() {
254         userTracker.removeCallback(userTrackerCallback)
255         super.onDestroy()
256     }
257 }
258