1 /*
2  * Copyright (C) 2020 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package com.android.systemui.privacy
16 
17 import android.content.Context
18 import android.content.pm.ActivityInfo
19 import android.content.res.Configuration
20 import android.util.AttributeSet
21 import android.view.Gravity.CENTER_VERTICAL
22 import android.view.Gravity.END
23 import android.view.ViewGroup
24 import android.view.ViewGroup.LayoutParams.MATCH_PARENT
25 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
26 import android.widget.ImageView
27 import android.widget.LinearLayout
28 import com.android.settingslib.Utils
29 import com.android.systemui.R
30 import com.android.systemui.animation.view.LaunchableFrameLayout
31 import com.android.systemui.statusbar.events.BackgroundAnimatableView
32 
33 class OngoingPrivacyChip @JvmOverloads constructor(
34     context: Context,
35     attrs: AttributeSet? = null,
36     defStyleAttrs: Int = 0,
37     defStyleRes: Int = 0
38 ) : LaunchableFrameLayout(context, attrs, defStyleAttrs, defStyleRes), BackgroundAnimatableView {
39 
40     private var configuration: Configuration
41     private var iconMargin = 0
42     private var iconSize = 0
43     private var iconColor = 0
44 
45     private val iconsContainer: LinearLayout
46 
47     var privacyList = emptyList<PrivacyItem>()
48         set(value) {
49             field = value
50             updateView(PrivacyChipBuilder(context, field))
51         }
52 
53     init {
54         inflate(context, R.layout.ongoing_privacy_chip, this)
55         id = R.id.privacy_chip
56         layoutParams = LayoutParams(WRAP_CONTENT, MATCH_PARENT, CENTER_VERTICAL or END)
57         clipChildren = true
58         clipToPadding = true
59         iconsContainer = requireViewById(R.id.icons_container)
60         configuration = Configuration(context.resources.configuration)
61         updateResources()
62     }
63 
64     /**
65      * When animating as a chip in the status bar, we want to animate the width for the container
66      * of the privacy items. We have to subtract our own top and left offset because the bounds
67      * come to us as absolute on-screen bounds, and `iconsContainer` is laid out relative to the
68      * frame layout's bounds.
69      */
70     override fun setBoundsForAnimation(l: Int, t: Int, r: Int, b: Int) {
71         iconsContainer.setLeftTopRightBottom(l - left, t - top, r - left, b - top)
72     }
73 
74     // Should only be called if the builder icons or app changed
75     private fun updateView(builder: PrivacyChipBuilder) {
76         fun setIcons(chipBuilder: PrivacyChipBuilder, iconsContainer: ViewGroup) {
77             iconsContainer.removeAllViews()
78             chipBuilder.generateIcons().forEachIndexed { i, it ->
79                 it.mutate()
80                 it.setTint(iconColor)
81                 val image = ImageView(context).apply {
82                     setImageDrawable(it)
83                     scaleType = ImageView.ScaleType.CENTER_INSIDE
84                 }
85                 iconsContainer.addView(image, iconSize, iconSize)
86                 if (i != 0) {
87                     val lp = image.layoutParams as MarginLayoutParams
88                     lp.marginStart = iconMargin
89                     image.layoutParams = lp
90                 }
91             }
92         }
93 
94         if (!privacyList.isEmpty()) {
95             generateContentDescription(builder)
96             setIcons(builder, iconsContainer)
97         } else {
98             iconsContainer.removeAllViews()
99         }
100         requestLayout()
101     }
102 
103     private fun generateContentDescription(builder: PrivacyChipBuilder) {
104         val typesText = builder.joinTypes()
105         contentDescription = context.getString(
106                 R.string.ongoing_privacy_chip_content_multiple_apps, typesText)
107     }
108 
109     override fun onConfigurationChanged(newConfig: Configuration?) {
110         super.onConfigurationChanged(newConfig)
111         if (newConfig != null) {
112             val diff = newConfig.diff(configuration)
113             configuration.setTo(newConfig)
114             if (diff.and(ActivityInfo.CONFIG_DENSITY.or(ActivityInfo.CONFIG_FONT_SCALE)) != 0) {
115                 updateResources()
116             }
117         }
118     }
119 
120     private fun updateResources() {
121         iconMargin = context.resources
122                 .getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_margin)
123         iconSize = context.resources
124                 .getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_size)
125         iconColor =
126                 Utils.getColorAttrDefaultColor(context, com.android.internal.R.attr.colorPrimary)
127 
128         val height = context.resources
129                 .getDimensionPixelSize(R.dimen.ongoing_appops_chip_height)
130         val padding = context.resources
131                 .getDimensionPixelSize(R.dimen.ongoing_appops_chip_side_padding)
132         iconsContainer.layoutParams.height = height
133         iconsContainer.setPaddingRelative(padding, 0, padding, 0)
134         iconsContainer.background = context.getDrawable(R.drawable.statusbar_privacy_chip_bg)
135     }
136 }