• 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.Activity
20 import android.app.ActivityOptions
21 import android.content.ComponentName
22 import android.content.Context
23 import android.content.Intent
24 import android.os.Bundle
25 import android.os.Process
26 import android.util.Log
27 import android.view.View
28 import android.view.ViewGroup
29 import android.view.ViewStub
30 import android.widget.Button
31 import android.widget.TextView
32 import android.widget.Toast
33 import android.window.OnBackInvokedCallback
34 import android.window.OnBackInvokedDispatcher
35 import androidx.activity.ComponentActivity
36 import androidx.recyclerview.widget.GridLayoutManager
37 import androidx.recyclerview.widget.ItemTouchHelper
38 import androidx.recyclerview.widget.RecyclerView
39 import com.android.systemui.controls.CustomIconCache
40 import com.android.systemui.controls.controller.ControlsControllerImpl
41 import com.android.systemui.controls.controller.StructureInfo
42 import com.android.systemui.controls.ui.ControlsActivity
43 import com.android.systemui.dagger.qualifiers.Main
44 import com.android.systemui.res.R
45 import com.android.systemui.settings.UserTracker
46 import com.android.systemui.utils.SafeIconLoader
47 import java.util.concurrent.Executor
48 import javax.inject.Inject
49 
50 /** Activity for rearranging and removing controls for a given structure */
51 open class ControlsEditingActivity
52 @Inject
53 constructor(
54     @Main private val mainExecutor: Executor,
55     private val controller: ControlsControllerImpl,
56     private val userTracker: UserTracker,
57     private val customIconCache: CustomIconCache,
58     private val controlsListingController: ControlsListingController,
59     private val safeIconLoaderFactory: SafeIconLoader.Factory,
60 ) : ComponentActivity(), ControlsManagementActivity {
61 
62     companion object {
63         private const val DEBUG = false
64         private const val TAG = "ControlsEditingActivity"
65         const val EXTRA_STRUCTURE = ControlsFavoritingActivity.EXTRA_STRUCTURE
66         const val EXTRA_APP = ControlsFavoritingActivity.EXTRA_APP
67         const val EXTRA_FROM_FAVORITING = "extra_from_favoriting"
68         private val SUBTITLE_ID = R.string.controls_favorite_rearrange
69         private val EMPTY_TEXT_ID = R.string.controls_favorite_removed
70     }
71 
72     override val activity: Activity
73         get() = this
74 
75     private lateinit var component: ComponentName
76     private lateinit var structure: CharSequence
77     private lateinit var model: FavoritesModel
78     private lateinit var subtitle: TextView
79     private lateinit var saveButton: View
80     private lateinit var addControls: View
81 
82     private var isFromFavoriting: Boolean = false
83 
84     private val userTrackerCallback: UserTracker.Callback =
85         object : UserTracker.Callback {
86             private val startingUser = controller.currentUserId
87 
onUserChangednull88             override fun onUserChanged(newUser: Int, userContext: Context) {
89                 if (newUser != startingUser) {
90                     userTracker.removeCallback(this)
91                     finish()
92                 }
93             }
94         }
95 
<lambda>null96     private val mOnBackInvokedCallback = OnBackInvokedCallback {
97         if (DEBUG) {
98             Log.d(TAG, "Predictive Back dispatcher called mOnBackInvokedCallback")
99         }
100         onBackPressed()
101     }
102 
onCreatenull103     override fun onCreate(savedInstanceState: Bundle?) {
104         super.onCreate(savedInstanceState)
105 
106         intent.getParcelableExtra<ComponentName>(Intent.EXTRA_COMPONENT_NAME)?.let {
107             component = it
108         } ?: run(this::finish)
109         isFromFavoriting = intent.getBooleanExtra(EXTRA_FROM_FAVORITING, false)
110         intent.getCharSequenceExtra(EXTRA_STRUCTURE)?.let { structure = it } ?: run(this::finish)
111 
112         bindViews()
113 
114         bindButtons()
115     }
116 
onStartnull117     override fun onStart() {
118         super.onStart()
119         setUpList()
120 
121         userTracker.addCallback(userTrackerCallback, mainExecutor)
122 
123         if (DEBUG) {
124             Log.d(TAG, "Registered onBackInvokedCallback")
125         }
126         onBackInvokedDispatcher.registerOnBackInvokedCallback(
127             OnBackInvokedDispatcher.PRIORITY_DEFAULT,
128             mOnBackInvokedCallback,
129         )
130     }
131 
onStopnull132     override fun onStop() {
133         super.onStop()
134         userTracker.removeCallback(userTrackerCallback)
135 
136         if (DEBUG) {
137             Log.d(TAG, "Unregistered onBackInvokedCallback")
138         }
139         onBackInvokedDispatcher.unregisterOnBackInvokedCallback(mOnBackInvokedCallback)
140     }
141 
onBackPressednull142     override fun onBackPressed() {
143         animateExitAndFinish()
144     }
145 
animateExitAndFinishnull146     private fun animateExitAndFinish() {
147         val rootView = requireViewById<ViewGroup>(R.id.controls_management_root)
148         ControlsAnimations.exitAnimation(
149                 rootView,
150                 object : Runnable {
151                     override fun run() {
152                         finish()
153                     }
154                 },
155             )
156             .start()
157     }
158 
bindViewsnull159     private fun bindViews() {
160         setContentView(R.layout.controls_management)
161 
162         applyInsets(R.id.controls_management_root)
163 
164         lifecycle.addObserver(
165             ControlsAnimations.observerForAnimations(
166                 requireViewById<ViewGroup>(R.id.controls_management_root),
167                 window,
168                 intent,
169             )
170         )
171 
172         requireViewById<ViewStub>(R.id.stub).apply {
173             layoutResource = R.layout.controls_management_editing
174             inflate()
175         }
176         requireViewById<TextView>(R.id.title).text = structure
177         setTitle(structure)
178         subtitle = requireViewById<TextView>(R.id.subtitle).apply { setText(SUBTITLE_ID) }
179     }
180 
bindButtonsnull181     private fun bindButtons() {
182         addControls =
183             requireViewById<Button>(R.id.addControls).apply {
184                 isEnabled = true
185                 visibility = View.VISIBLE
186                 setOnClickListener {
187                     if (saveButton.isEnabled) {
188                         // The user has made changes
189                         Toast.makeText(
190                                 applicationContext,
191                                 R.string.controls_favorite_toast_no_changes,
192                                 Toast.LENGTH_SHORT,
193                             )
194                             .show()
195                     }
196                     if (isFromFavoriting) {
197                         animateExitAndFinish()
198                     } else {
199                         startActivity(
200                             Intent(context, ControlsFavoritingActivity::class.java).also {
201                                 it.putExtra(ControlsFavoritingActivity.EXTRA_STRUCTURE, structure)
202                                 it.putExtra(Intent.EXTRA_COMPONENT_NAME, component)
203                                 it.putExtra(
204                                     ControlsFavoritingActivity.EXTRA_APP,
205                                     intent.getCharSequenceExtra(EXTRA_APP),
206                                 )
207                                 it.putExtra(
208                                     ControlsFavoritingActivity.EXTRA_SOURCE,
209                                     ControlsFavoritingActivity.EXTRA_SOURCE_VALUE_FROM_EDITING,
210                                 )
211                             },
212                             ActivityOptions.makeSceneTransitionAnimation(
213                                     this@ControlsEditingActivity
214                                 )
215                                 .toBundle(),
216                         )
217                     }
218                 }
219             }
220         saveButton =
221             requireViewById<Button>(R.id.done).apply {
222                 isEnabled = isFromFavoriting
223                 setText(R.string.save)
224                 setOnClickListener {
225                     saveFavorites()
226                     startActivity(
227                         Intent(applicationContext, ControlsActivity::class.java),
228                         ActivityOptions.makeSceneTransitionAnimation(this@ControlsEditingActivity)
229                             .toBundle(),
230                     )
231                     animateExitAndFinish()
232                 }
233             }
234     }
235 
saveFavoritesnull236     private fun saveFavorites() {
237         controller.replaceFavoritesForStructure(
238             StructureInfo(component, structure, model.favorites)
239         )
240     }
241 
242     private val favoritesModelCallback =
243         object : FavoritesModel.FavoritesModelCallback {
onNoneChangednull244             override fun onNoneChanged(showNoFavorites: Boolean) {
245                 if (showNoFavorites) {
246                     subtitle.setText(EMPTY_TEXT_ID)
247                 } else {
248                     subtitle.setText(SUBTITLE_ID)
249                 }
250             }
251 
onChangenull252             override fun onChange() = Unit
253 
254             override fun onFirstChange() {
255                 saveButton.isEnabled = true
256             }
257         }
258 
setUpListnull259     private fun setUpList() {
260         val controls = controller.getFavoritesForStructure(component, structure)
261         model = FavoritesModel(customIconCache, component, controls, favoritesModelCallback)
262         val elevation = resources.getFloat(R.dimen.control_card_elevation)
263         val recyclerView = requireViewById<RecyclerView>(R.id.list)
264         recyclerView.alpha = 0.0f
265         val uid =
266             controlsListingController
267                 .getCurrentServices()
268                 .firstOrNull { it.componentName == component }
269                 ?.serviceInfo
270                 ?.applicationInfo
271                 ?.uid ?: Process.INVALID_UID
272         val packageName = component.packageName
273         val safeIconLoader = safeIconLoaderFactory.create(uid, packageName, userTracker.userId)
274 
275         val adapter =
276             ControlAdapter(elevation, userTracker.userId, safeIconLoader).apply {
277                 registerAdapterDataObserver(
278                     object : RecyclerView.AdapterDataObserver() {
279                         var hasAnimated = false
280 
281                         override fun onChanged() {
282                             if (!hasAnimated) {
283                                 hasAnimated = true
284                                 ControlsAnimations.enterAnimation(recyclerView).start()
285                             }
286                         }
287                     }
288                 )
289             }
290 
291         val margin = resources.getDimensionPixelSize(R.dimen.controls_card_margin)
292         val itemDecorator = MarginItemDecorator(margin, margin)
293         val spanCount = ControlAdapter.findMaxColumns(resources)
294 
295         recyclerView.apply {
296             this.adapter = adapter
297             layoutManager =
298                 object : GridLayoutManager(recyclerView.context, spanCount) {
299 
300                         // This will remove from the announcement the row corresponding to the
301                         // divider,
302                         // as it's not something that should be announced.
303                         override fun getRowCountForAccessibility(
304                             recycler: RecyclerView.Recycler,
305                             state: RecyclerView.State,
306                         ): Int {
307                             val initial = super.getRowCountForAccessibility(recycler, state)
308                             return if (initial > 0) initial - 1 else initial
309                         }
310                     }
311                     .apply {
312                         spanSizeLookup =
313                             object : GridLayoutManager.SpanSizeLookup() {
314                                 override fun getSpanSize(position: Int): Int {
315                                     return if (
316                                         adapter?.getItemViewType(position) !=
317                                             ControlAdapter.TYPE_CONTROL
318                                     )
319                                         spanCount
320                                     else 1
321                                 }
322                             }
323                     }
324             addItemDecoration(itemDecorator)
325         }
326         adapter.changeModel(model)
327         model.attachAdapter(adapter)
328         ItemTouchHelper(model.itemTouchHelperCallback).attachToRecyclerView(recyclerView)
329     }
330 
onDestroynull331     override fun onDestroy() {
332         userTracker.removeCallback(userTrackerCallback)
333         super.onDestroy()
334     }
335 }
336