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