• 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.graphics.drawable.LayerDrawable
21 import android.os.Bundle
22 import android.text.TextUtils
23 import android.view.Gravity
24 import android.view.LayoutInflater
25 import android.view.View
26 import android.view.ViewGroup
27 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
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 and user id
45  */
46 class PrivacyDialog(
47     context: Context,
48     private val list: List<PrivacyElement>,
49     activityStarter: (String, Int) -> 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             setLayout(context.resources.getDimensionPixelSize(R.dimen.qs_panel_width), WRAP_CONTENT)
69             setGravity(Gravity.TOP or Gravity.CENTER_HORIZONTAL)
70         }
71 
72         setContentView(R.layout.privacy_dialog)
73         rootView = requireViewById<ViewGroup>(R.id.root)
74 
75         list.forEach {
76             rootView.addView(createView(it))
77         }
78     }
79 
80     /**
81      * Add a listener that will be called when the dialog is dismissed.
82      *
83      * If the dialog has already been dismissed, the listener will be called immediately, in the
84      * same thread.
85      */
86     fun addOnDismissListener(listener: OnDialogDismissed) {
87         if (dismissed.get()) {
88             listener.onDialogDismissed()
89         } else {
90             dismissListeners.add(WeakReference(listener))
91         }
92     }
93 
94     override fun onStop() {
95         super.onStop()
96         dismissed.set(true)
97         val iterator = dismissListeners.iterator()
98         while (iterator.hasNext()) {
99             val el = iterator.next()
100             iterator.remove()
101             el.get()?.onDialogDismissed()
102         }
103     }
104 
105     private fun createView(element: PrivacyElement): View {
106         val newView = LayoutInflater.from(context).inflate(
107                 R.layout.privacy_dialog_item, rootView, false
108         ) as ViewGroup
109         val d = getDrawableForType(element.type)
110         d.findDrawableByLayerId(R.id.icon).setTint(iconColorSolid)
111         newView.requireViewById<ImageView>(R.id.icon).apply {
112             setImageDrawable(d)
113             contentDescription = element.type.getName(context)
114         }
115         val stringId = getStringIdForState(element.active)
116         val app = if (element.phoneCall) phonecall else element.applicationName
117         val appName = if (element.enterprise) {
118             TextUtils.concat(app, enterpriseText)
119         } else {
120             app
121         }
122         val firstLine = context.getString(stringId, appName)
123         val finalText = element.attribution?.let {
124             TextUtils.concat(
125                     firstLine,
126                     " ",
127                     context.getString(R.string.ongoing_privacy_dialog_attribution_text, it)
128             )
129         } ?: firstLine
130         newView.requireViewById<TextView>(R.id.text).text = finalText
131         if (element.phoneCall) {
132             newView.requireViewById<View>(R.id.chevron).visibility = View.GONE
133         }
134         newView.apply {
135             setTag(element)
136             if (!element.phoneCall) {
137                 setOnClickListener(clickListener)
138             }
139         }
140         return newView
141     }
142 
143     private fun getStringIdForState(active: Boolean): Int {
144         return if (active) {
145             R.string.ongoing_privacy_dialog_using_op
146         } else {
147             R.string.ongoing_privacy_dialog_recent_op
148         }
149     }
150 
151     private fun getDrawableForType(type: PrivacyType): LayerDrawable {
152         return context.getDrawable(when (type) {
153             PrivacyType.TYPE_LOCATION -> R.drawable.privacy_item_circle_location
154             PrivacyType.TYPE_CAMERA -> R.drawable.privacy_item_circle_camera
155             PrivacyType.TYPE_MICROPHONE -> R.drawable.privacy_item_circle_microphone
156         }) as LayerDrawable
157     }
158 
159     private val clickListener = View.OnClickListener { v ->
160         v.tag?.let {
161             val element = it as PrivacyElement
162             activityStarter(element.packageName, element.userId)
163         }
164     }
165 
166     /** */
167     data class PrivacyElement(
168         val type: PrivacyType,
169         val packageName: String,
170         val userId: Int,
171         val applicationName: CharSequence,
172         val attribution: CharSequence?,
173         val lastActiveTimestamp: Long,
174         val active: Boolean,
175         val enterprise: Boolean,
176         val phoneCall: Boolean
177     ) {
178         private val builder = StringBuilder("PrivacyElement(")
179 
180         init {
181             builder.append("type=${type.logName}")
182             builder.append(", packageName=$packageName")
183             builder.append(", userId=$userId")
184             builder.append(", appName=$applicationName")
185             if (attribution != null) {
186                 builder.append(", attribution=$attribution")
187             }
188             builder.append(", lastActive=$lastActiveTimestamp")
189             if (active) {
190                 builder.append(", active")
191             }
192             if (enterprise) {
193                 builder.append(", enterprise")
194             }
195             if (phoneCall) {
196                 builder.append(", phoneCall")
197             }
198             builder.append(")")
199         }
200 
201         override fun toString(): String = builder.toString()
202     }
203 
204     /** */
205     interface OnDialogDismissed {
206         fun onDialogDismissed()
207     }
208 }