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