• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright (C) 2021 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.privacy
18 
19 import android.content.Context
20 import android.content.Intent
21 import android.graphics.drawable.LayerDrawable
22 import android.os.Bundle
23 import android.text.TextUtils
24 import android.view.Gravity
25 import android.view.LayoutInflater
26 import android.view.View
27 import android.view.ViewGroup
28 import android.view.WindowInsets
29 import android.widget.ImageView
30 import android.widget.TextView
31 import com.android.settingslib.Utils
32 import com.android.systemui.R
33 import com.android.systemui.statusbar.phone.SystemUIDialog
34 import java.lang.ref.WeakReference
35 import java.util.concurrent.atomic.AtomicBoolean
36 
37 /**
38  * Dialog to show ongoing and recent app ops usage.
39  *
40  * @see PrivacyDialogController
41  * @param context A context to create the dialog
42  * @param list list of elements to show in the dialog. The elements will show in the same order they
43  *             appear in the list
44  * @param activityStarter a callback to start an activity for a given package name, user id, attributionTag and intent
45  */
46 class PrivacyDialog(
47     context: Context,
48     private val list: List<PrivacyElement>,
49     activityStarter: (String, Int, CharSequence?, Intent?) -> Unit
50 ) : SystemUIDialog(context, R.style.PrivacyDialog) {
51 
52     private val dismissListeners = mutableListOf<WeakReference<OnDialogDismissed>>()
53     private val dismissed = AtomicBoolean(false)
54 
55     private val iconColorSolid = Utils.getColorAttrDefaultColor(
56             this.context, com.android.internal.R.attr.colorPrimary
57     )
58     private val enterpriseText = " ${context.getString(R.string.ongoing_privacy_dialog_enterprise)}"
59     private val phonecall = context.getString(R.string.ongoing_privacy_dialog_phonecall)
60 
61     private lateinit var rootView: ViewGroup
62 
63     override fun onCreate(savedInstanceState: Bundle?) {
64         super.onCreate(savedInstanceState)
65         window?.apply {
66             attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars()
67             attributes.receiveInsetsIgnoringZOrder = true
68             setGravity(Gravity.TOP or Gravity.CENTER_HORIZONTAL)
69         }
70         setTitle(R.string.ongoing_privacy_dialog_a11y_title)
71         setContentView(R.layout.privacy_dialog)
72         rootView = requireViewById<ViewGroup>(R.id.root)
73 
74         list.forEach {
75             rootView.addView(createView(it))
76         }
77     }
78 
79     /**
80      * Add a listener that will be called when the dialog is dismissed.
81      *
82      * If the dialog has already been dismissed, the listener will be called immediately, in the
83      * same thread.
84      */
85     fun addOnDismissListener(listener: OnDialogDismissed) {
86         if (dismissed.get()) {
87             listener.onDialogDismissed()
88         } else {
89             dismissListeners.add(WeakReference(listener))
90         }
91     }
92 
93     override fun onStop() {
94         super.onStop()
95         dismissed.set(true)
96         val iterator = dismissListeners.iterator()
97         while (iterator.hasNext()) {
98             val el = iterator.next()
99             iterator.remove()
100             el.get()?.onDialogDismissed()
101         }
102     }
103 
104     private fun createView(element: PrivacyElement): View {
105         val newView = LayoutInflater.from(context).inflate(
106                 R.layout.privacy_dialog_item, rootView, false
107         ) as ViewGroup
108         val d = getDrawableForType(element.type)
109         d.findDrawableByLayerId(R.id.icon).setTint(iconColorSolid)
110         newView.requireViewById<ImageView>(R.id.icon).apply {
111             setImageDrawable(d)
112             contentDescription = element.type.getName(context)
113         }
114         val stringId = getStringIdForState(element.active)
115         val app = if (element.phoneCall) phonecall else element.applicationName
116         val appName = if (element.enterprise) {
117             TextUtils.concat(app, enterpriseText)
118         } else {
119             app
120         }
121         val firstLine = context.getString(stringId, appName)
122         val finalText = getFinalText(firstLine, element.attributionLabel, element.proxyLabel)
123         newView.requireViewById<TextView>(R.id.text).text = finalText
124         if (element.phoneCall) {
125             newView.requireViewById<View>(R.id.chevron).visibility = View.GONE
126         }
127         newView.apply {
128             setTag(element)
129             if (!element.phoneCall) {
130                 setOnClickListener(clickListener)
131             }
132         }
133         return newView
134     }
135 
136     private fun getFinalText(
137         firstLine: CharSequence,
138         attributionLabel: CharSequence?,
139         proxyLabel: CharSequence?
140     ): CharSequence {
141         var dialogText: CharSequence? = null
142         if (attributionLabel != null && proxyLabel != null) {
143             dialogText = context.getString(R.string.ongoing_privacy_dialog_attribution_proxy_label,
144                 attributionLabel, proxyLabel)
145         } else if (attributionLabel != null) {
146             dialogText = context.getString(R.string.ongoing_privacy_dialog_attribution_label,
147                 attributionLabel)
148         } else if (proxyLabel != null) {
149             dialogText = context.getString(R.string.ongoing_privacy_dialog_attribution_text,
150                 proxyLabel)
151         }
152         return if (dialogText != null) TextUtils.concat(firstLine, " ", dialogText) else firstLine
153     }
154 
155     private fun getStringIdForState(active: Boolean): Int {
156         return if (active) {
157             R.string.ongoing_privacy_dialog_using_op
158         } else {
159             R.string.ongoing_privacy_dialog_recent_op
160         }
161     }
162 
163     private fun getDrawableForType(type: PrivacyType): LayerDrawable {
164         return context.getDrawable(when (type) {
165             PrivacyType.TYPE_LOCATION -> R.drawable.privacy_item_circle_location
166             PrivacyType.TYPE_CAMERA -> R.drawable.privacy_item_circle_camera
167             PrivacyType.TYPE_MICROPHONE -> R.drawable.privacy_item_circle_microphone
168             PrivacyType.TYPE_MEDIA_PROJECTION -> R.drawable.privacy_item_circle_media_projection
169         }) as LayerDrawable
170     }
171 
172     private val clickListener = View.OnClickListener { v ->
173         v.tag?.let {
174             val element = it as PrivacyElement
175             activityStarter(element.packageName, element.userId,
176                 element.attributionTag, element.navigationIntent)
177         }
178     }
179 
180     /** */
181     data class PrivacyElement(
182         val type: PrivacyType,
183         val packageName: String,
184         val userId: Int,
185         val applicationName: CharSequence,
186         val attributionTag: CharSequence?,
187         val attributionLabel: CharSequence?,
188         val proxyLabel: CharSequence?,
189         val lastActiveTimestamp: Long,
190         val active: Boolean,
191         val enterprise: Boolean,
192         val phoneCall: Boolean,
193         val permGroupName: CharSequence,
194         val navigationIntent: Intent?
195     ) {
196         private val builder = StringBuilder("PrivacyElement(")
197 
198         init {
199             builder.append("type=${type.logName}")
200             builder.append(", packageName=$packageName")
201             builder.append(", userId=$userId")
202             builder.append(", appName=$applicationName")
203             if (attributionTag != null) {
204                 builder.append(", attributionTag=$attributionTag")
205             }
206             if (attributionLabel != null) {
207                 builder.append(", attributionLabel=$attributionLabel")
208             }
209             if (proxyLabel != null) {
210                 builder.append(", proxyLabel=$proxyLabel")
211             }
212             builder.append(", lastActive=$lastActiveTimestamp")
213             if (active) {
214                 builder.append(", active")
215             }
216             if (enterprise) {
217                 builder.append(", enterprise")
218             }
219             if (phoneCall) {
220                 builder.append(", phoneCall")
221             }
222             builder.append(", permGroupName=$permGroupName)")
223             if (navigationIntent != null) {
224                 builder.append(", navigationIntent=$navigationIntent")
225             }
226         }
227 
228         override fun toString(): String = builder.toString()
229     }
230 
231     /** */
232     interface OnDialogDismissed {
233         fun onDialogDismissed()
234     }
235 }