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.AlertDialog
20 import android.app.Dialog
21 import android.content.ComponentName
22 import android.content.Context
23 import android.content.DialogInterface
24 import android.content.Intent
25 import android.os.Bundle
26 import android.os.UserHandle
27 import android.service.controls.Control
28 import android.service.controls.ControlsProviderService
29 import android.util.Log
30 import android.view.LayoutInflater
31 import android.view.View
32 import android.widget.ImageView
33 import android.widget.TextView
34 import androidx.activity.ComponentActivity
35 import com.android.systemui.res.R
36 import com.android.systemui.controls.ControlsServiceInfo
37 import com.android.systemui.controls.controller.ControlInfo
38 import com.android.systemui.controls.controller.ControlsController
39 import com.android.systemui.controls.ui.RenderInfo
40 import com.android.systemui.dagger.qualifiers.Main
41 import com.android.systemui.settings.UserTracker
42 import com.android.systemui.statusbar.phone.SystemUIDialog
43 import java.util.concurrent.Executor
44 import javax.inject.Inject
45 
46 open class ControlsRequestDialog @Inject constructor(
47     @Main private val mainExecutor: Executor,
48     private val controller: ControlsController,
49     private val userTracker: UserTracker,
50     private val controlsListingController: ControlsListingController
51 ) : ComponentActivity(), DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
52 
53     companion object {
54         private const val TAG = "ControlsRequestDialog"
55     }
56 
57     private lateinit var controlComponent: ComponentName
58     private lateinit var control: Control
59     private var dialog: Dialog? = null
60     private val callback = object : ControlsListingController.ControlsListingCallback {
onServicesUpdatednull61         override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {}
62     }
63 
64     private val userTrackerCallback: UserTracker.Callback = object : UserTracker.Callback {
65         private val startingUser = controller.currentUserId
66 
onUserChangednull67         override fun onUserChanged(newUser: Int, userContext: Context) {
68             if (newUser != startingUser) {
69                 userTracker.removeCallback(this)
70                 finish()
71             }
72         }
73     }
74 
onCreatenull75     override fun onCreate(savedInstanceState: Bundle?) {
76         super.onCreate(savedInstanceState)
77 
78         userTracker.addCallback(userTrackerCallback, mainExecutor)
79         controlsListingController.addCallback(callback)
80 
81         val requestUser = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL)
82         val currentUser = controller.currentUserId
83 
84         if (requestUser != currentUser) {
85             Log.w(TAG, "Current user ($currentUser) different from request user ($requestUser)")
86             finish()
87         }
88 
89         controlComponent = intent.getParcelableExtra(Intent.EXTRA_COMPONENT_NAME) ?: run {
90             Log.e(TAG, "Request did not contain componentName")
91             finish()
92             return
93         }
94 
95         control = intent.getParcelableExtra(ControlsProviderService.EXTRA_CONTROL) ?: run {
96             Log.e(TAG, "Request did not contain control")
97             finish()
98             return
99         }
100     }
101 
onResumenull102     override fun onResume() {
103         super.onResume()
104         val label = verifyComponentAndGetLabel()
105         if (label == null) {
106             Log.e(TAG, "The component specified (${controlComponent.flattenToString()} " +
107                     "is not a valid ControlsProviderService")
108             finish()
109             return
110         }
111 
112         if (isCurrentFavorite()) {
113             Log.w(TAG, "The control ${control.title} is already a favorite")
114             finish()
115         }
116 
117         dialog = createDialog(label)
118 
119         dialog?.show()
120     }
121 
onDestroynull122     override fun onDestroy() {
123         dialog?.dismiss()
124         userTracker.removeCallback(userTrackerCallback)
125         controlsListingController.removeCallback(callback)
126         super.onDestroy()
127     }
128 
verifyComponentAndGetLabelnull129     private fun verifyComponentAndGetLabel(): CharSequence? {
130         return controlsListingController.getAppLabel(controlComponent)
131     }
132 
isCurrentFavoritenull133     private fun isCurrentFavorite(): Boolean {
134         val favorites = controller.getFavoritesForComponent(controlComponent)
135         return favorites.any { it.controls.any { it.controlId == control.controlId } }
136     }
137 
createDialognull138     fun createDialog(label: CharSequence): Dialog {
139         val renderInfo = RenderInfo.lookup(this, controlComponent, control.deviceType)
140         val frame = LayoutInflater.from(this).inflate(R.layout.controls_dialog, null).apply {
141             requireViewById<ImageView>(R.id.icon).apply {
142                 setImageDrawable(renderInfo.icon)
143                 setImageTintList(
144                         context.resources.getColorStateList(renderInfo.foreground, context.theme))
145             }
146             requireViewById<TextView>(R.id.title).text = control.title
147             requireViewById<TextView>(R.id.subtitle).text = control.subtitle
148             requireViewById<View>(R.id.control).elevation =
149                     resources.getFloat(R.dimen.control_card_elevation)
150         }
151 
152         val dialog = AlertDialog.Builder(this)
153                 .setTitle(getString(R.string.controls_dialog_title))
154                 .setMessage(getString(R.string.controls_dialog_message, label))
155                 .setPositiveButton(R.string.controls_dialog_ok, this)
156                 .setNegativeButton(android.R.string.cancel, this)
157                 .setOnCancelListener(this)
158                 .setView(frame)
159                 .create()
160 
161         SystemUIDialog.registerDismissListener(dialog)
162         dialog.setCanceledOnTouchOutside(true)
163         return dialog
164     }
165 
onCancelnull166     override fun onCancel(dialog: DialogInterface?) {
167         finish()
168     }
169 
onClicknull170     override fun onClick(dialog: DialogInterface?, which: Int) {
171         if (which == Dialog.BUTTON_POSITIVE) {
172             controller.addFavorite(
173                 controlComponent,
174                 control.structure ?: "",
175                 ControlInfo(control.controlId, control.title, control.subtitle, control.deviceType)
176             )
177         }
178         finish()
179     }
180 }
181