1 /* <lambda>null2 * Copyright (C) 2019 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.app.Dialog 21 import android.content.ComponentName 22 import android.content.Context 23 import android.content.Intent 24 import android.os.Bundle 25 import android.util.Log 26 import android.view.LayoutInflater 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.window.OnBackInvokedCallback 33 import android.window.OnBackInvokedDispatcher 34 import androidx.activity.ComponentActivity 35 import androidx.annotation.VisibleForTesting 36 import androidx.recyclerview.widget.LinearLayoutManager 37 import androidx.recyclerview.widget.RecyclerView 38 import com.android.systemui.res.R 39 import com.android.systemui.controls.ControlsServiceInfo 40 import com.android.systemui.controls.controller.ControlsController 41 import com.android.systemui.controls.panels.AuthorizedPanelsRepository 42 import com.android.systemui.controls.ui.ControlsActivity 43 import com.android.systemui.controls.ui.SelectedItem 44 import com.android.systemui.dagger.qualifiers.Background 45 import com.android.systemui.dagger.qualifiers.Main 46 import com.android.systemui.settings.UserTracker 47 import java.util.concurrent.Executor 48 import javax.inject.Inject 49 50 /** 51 * Activity to select an application to favorite the [Control] provided by them. 52 */ 53 open class ControlsProviderSelectorActivity @Inject constructor( 54 @Main private val executor: Executor, 55 @Background private val backExecutor: Executor, 56 private val listingController: ControlsListingController, 57 private val controlsController: ControlsController, 58 private val userTracker: UserTracker, 59 private val authorizedPanelsRepository: AuthorizedPanelsRepository, 60 private val panelConfirmationDialogFactory: PanelConfirmationDialogFactory 61 ) : ComponentActivity() { 62 63 companion object { 64 private const val DEBUG = false 65 private const val TAG = "ControlsProviderSelectorActivity" 66 const val BACK_SHOULD_EXIT = "back_should_exit" 67 } 68 private var backShouldExit = false 69 private lateinit var recyclerView: RecyclerView 70 private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback { 71 private val startingUser = listingController.currentUserId 72 73 override fun onUserChanged(newUser: Int, userContext: Context) { 74 if (newUser != startingUser) { 75 userTracker.removeCallback(this) 76 finish() 77 } 78 } 79 } 80 private var dialog: Dialog? = null 81 82 private val mOnBackInvokedCallback = OnBackInvokedCallback { 83 if (DEBUG) { 84 Log.d(TAG, "Predictive Back dispatcher called mOnBackInvokedCallback") 85 } 86 onBackPressed() 87 } 88 89 override fun onCreate(savedInstanceState: Bundle?) { 90 super.onCreate(savedInstanceState) 91 92 setContentView(R.layout.controls_management) 93 94 lifecycle.addObserver( 95 ControlsAnimations.observerForAnimations( 96 requireViewById<ViewGroup>(R.id.controls_management_root), 97 window, 98 intent 99 ) 100 ) 101 102 requireViewById<ViewStub>(R.id.stub).apply { 103 layoutResource = R.layout.controls_management_apps 104 inflate() 105 } 106 107 recyclerView = requireViewById(R.id.list) 108 recyclerView.layoutManager = LinearLayoutManager(applicationContext) 109 110 requireViewById<TextView>(R.id.title).apply { 111 text = resources.getText(R.string.controls_providers_title) 112 } 113 114 requireViewById<Button>(R.id.other_apps).apply { 115 visibility = View.VISIBLE 116 setText(com.android.internal.R.string.cancel) 117 setOnClickListener { 118 onBackPressed() 119 } 120 } 121 requireViewById<View>(R.id.done).visibility = View.GONE 122 123 backShouldExit = intent.getBooleanExtra(BACK_SHOULD_EXIT, false) 124 } 125 126 override fun onBackPressed() { 127 if (!backShouldExit) { 128 val i = Intent().apply { 129 component = ComponentName(applicationContext, ControlsActivity::class.java) 130 } 131 startActivity(i, ActivityOptions.makeSceneTransitionAnimation(this).toBundle()) 132 } 133 animateExitAndFinish() 134 } 135 136 override fun onStart() { 137 super.onStart() 138 userTracker.addCallback(userTrackerCallback, executor) 139 140 recyclerView.alpha = 0.0f 141 recyclerView.adapter = AppAdapter( 142 backExecutor, 143 executor, 144 lifecycle, 145 listingController, 146 LayoutInflater.from(this), 147 ::onAppSelected, 148 FavoritesRenderer(resources, controlsController::countFavoritesForComponent), 149 resources, 150 authorizedPanelsRepository.getAuthorizedPanels() 151 ).apply { 152 registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { 153 var hasAnimated = false 154 override fun onChanged() { 155 if (!hasAnimated) { 156 hasAnimated = true 157 ControlsAnimations.enterAnimation(recyclerView).start() 158 } 159 } 160 }) 161 } 162 163 if (DEBUG) { 164 Log.d(TAG, "Registered onBackInvokedCallback") 165 } 166 onBackInvokedDispatcher.registerOnBackInvokedCallback( 167 OnBackInvokedDispatcher.PRIORITY_DEFAULT, mOnBackInvokedCallback) 168 } 169 170 override fun onStop() { 171 super.onStop() 172 userTracker.removeCallback(userTrackerCallback) 173 174 if (DEBUG) { 175 Log.d(TAG, "Unregistered onBackInvokedCallback") 176 } 177 onBackInvokedDispatcher.unregisterOnBackInvokedCallback(mOnBackInvokedCallback) 178 dialog?.cancel() 179 } 180 181 fun onAppSelected(serviceInfo: ControlsServiceInfo) { 182 dialog?.cancel() 183 if (serviceInfo.panelActivity == null) { 184 launchFavoritingActivity(serviceInfo.componentName) 185 } else { 186 val appName = serviceInfo.loadLabel() ?: "" 187 dialog = panelConfirmationDialogFactory.createConfirmationDialog(this, appName) { ok -> 188 if (ok) { 189 authorizedPanelsRepository.addAuthorizedPanels( 190 setOf(serviceInfo.componentName.packageName) 191 ) 192 val selected = SelectedItem.PanelItem(appName, serviceInfo.componentName) 193 controlsController.setPreferredSelection(selected) 194 animateExitAndFinish() 195 openControlsOrigin() 196 } 197 dialog = null 198 }.also { it.show() } 199 } 200 } 201 202 /** 203 * Launch the [ControlsFavoritingActivity] for the specified component. 204 * @param component a component name for a [ControlsProviderService] 205 */ 206 private fun launchFavoritingActivity(component: ComponentName?) { 207 executor.execute { 208 component?.let { 209 val intent = Intent(applicationContext, ControlsFavoritingActivity::class.java) 210 .apply { 211 putExtra(ControlsFavoritingActivity.EXTRA_APP, 212 listingController.getAppLabel(it)) 213 putExtra(Intent.EXTRA_COMPONENT_NAME, it) 214 putExtra( 215 ControlsFavoritingActivity.EXTRA_SOURCE, 216 ControlsFavoritingActivity.EXTRA_SOURCE_VALUE_FROM_PROVIDER_SELECTOR, 217 ) 218 } 219 startActivity(intent, ActivityOptions.makeSceneTransitionAnimation(this).toBundle()) 220 } 221 } 222 } 223 224 override fun onDestroy() { 225 userTracker.removeCallback(userTrackerCallback) 226 super.onDestroy() 227 } 228 229 private fun openControlsOrigin() { 230 startActivity( 231 Intent(applicationContext, ControlsActivity::class.java), 232 ActivityOptions.makeSceneTransitionAnimation(this).toBundle() 233 ) 234 } 235 236 @VisibleForTesting 237 internal open fun animateExitAndFinish() { 238 val rootView = requireViewById<ViewGroup>(R.id.controls_management_root) 239 ControlsAnimations.exitAnimation( 240 rootView, 241 object : Runnable { 242 override fun run() { 243 finish() 244 } 245 } 246 ).start() 247 } 248 } 249