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