diff options
| author | 2018-11-02 11:02:11 -0400 | |
|---|---|---|
| committer | 2018-11-16 09:16:25 -0500 | |
| commit | ef12449cf8be03a23d6acd295827fd358a1dc0d2 (patch) | |
| tree | b306f88b1821ec6c96bb8f7adb5aee859b8a6685 | |
| parent | 22edbb5e7e6169d5dd09f3f883502ae643b06292 (diff) | |
Version 2 of Ongoing Privacy Dialog
Minor changes to colors and layout of chip.
Redesign of dialog using new mocks.
Dialog launches Permission Hub
Test: visual & atest PrivacyDialogBuilderTest
Fixes: 117646163
Bug: 112331475
Change-Id: Ic8008f05fcb139c2581794abbb47c00819c20d7f
16 files changed, 309 insertions, 187 deletions
diff --git a/packages/SystemUI/res/drawable/privacy_chip_bg.xml b/packages/SystemUI/res/drawable/privacy_chip_bg.xml index 8247c27ff850..36d06591460b 100644 --- a/packages/SystemUI/res/drawable/privacy_chip_bg.xml +++ b/packages/SystemUI/res/drawable/privacy_chip_bg.xml @@ -16,7 +16,7 @@ --> <shape xmlns:android="http://schemas.android.com/apk/res/android"> - <solid android:color="#bbbbbb" /> + <solid android:color="#4a4a4a" /> <padding android:padding="@dimen/ongoing_appops_chip_bg_padding" /> <corners android:radius="@dimen/ongoing_appops_chip_bg_corner_radius" /> </shape>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml index ddefb6a43a6f..cbdd51b24388 100644 --- a/packages/SystemUI/res/layout/ongoing_privacy_chip.xml +++ b/packages/SystemUI/res/layout/ongoing_privacy_chip.xml @@ -18,11 +18,14 @@ <com.android.systemui.privacy.OngoingPrivacyChip xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/privacy_chip" - android:layout_width="wrap_content" android:layout_height="match_parent" - android:layout_margin="@dimen/ongoing_appops_chip_margin" + android:layout_width="wrap_content" + android:layout_marginLeft="@dimen/ongoing_appops_chip_margin" + android:layout_marginRight="@dimen/ongoing_appops_chip_margin" + android:layout_marginTop="@dimen/ongoing_appops_top_chip_margin" + android:layout_marginBottom="@dimen/ongoing_appops_top_chip_margin" android:gravity="center_vertical|center_horizontal" - android:layout_gravity="center_vertical|end" + android:layout_gravity="center_vertical|start" android:orientation="horizontal" android:paddingStart="@dimen/ongoing_appops_chip_side_padding" android:paddingEnd="@dimen/ongoing_appops_chip_side_padding" @@ -38,12 +41,17 @@ /> <TextView - android:id="@+id/app_name" + android:id="@+id/text_container" android:layout_height="match_parent" android:layout_width="wrap_content" android:singleLine="true" android:ellipsize="end" + android:lines="1" android:layout_gravity="center_vertical|end" android:gravity="center_vertical" + android:textAppearance="@style/TextAppearance.StatusBar.Clock" + android:textColor="@color/status_bar_clock_color" + android:layout_marginStart="@dimen/ongoing_appops_chip_icon_margin" + android:layout_marginEnd="@dimen/ongoing_appops_chip_icon_margin" /> </com.android.systemui.privacy.OngoingPrivacyChip>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/ongoing_privacy_dialog_content.xml b/packages/SystemUI/res/layout/ongoing_privacy_dialog_content.xml index b5e24a04f85e..2f7d486a1372 100644 --- a/packages/SystemUI/res/layout/ongoing_privacy_dialog_content.xml +++ b/packages/SystemUI/res/layout/ongoing_privacy_dialog_content.xml @@ -29,22 +29,30 @@ android:orientation="vertical" android:padding="@dimen/ongoing_appops_dialog_content_padding"> - <LinearLayout - android:id="@+id/icons_container" + <TextView + android:id="@+id/title" android:layout_width="match_parent" - android:layout_height="@dimen/ongoing_appops_dialog_icon_height" - android:orientation="horizontal" + android:layout_height="wrap_content" android:gravity="center" - android:importantForAccessibility="no" + android:textDirection="locale" + android:textAppearance="@style/TextAppearance.AppOpsDialog.Title" + android:textColor="@*android:color/text_color_primary" + android:paddingStart="@dimen/ongoing_appops_dialog_title_padding" + android:paddingEnd="@dimen/ongoing_appops_dialog_title_padding" + android:paddingBottom="@dimen/ongoing_appops_dialog_sep" /> <LinearLayout - android:id="@+id/text_container" + android:id="@+id/items_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:gravity="start" /> + + <include android:id="@+id/overflow" layout="@layout/ongoing_privacy_dialog_item" + android:visibility="gone" /> + </LinearLayout> </ScrollView>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/ongoing_privacy_dialog_item.xml b/packages/SystemUI/res/layout/ongoing_privacy_dialog_item.xml new file mode 100644 index 000000000000..f05f7bad36ba --- /dev/null +++ b/packages/SystemUI/res/layout/ongoing_privacy_dialog_item.xml @@ -0,0 +1,53 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + Copyright (C) 2018 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:fillViewport="true" + android:orientation="horizontal" + android:layout_marginTop="@dimen/ongoing_appops_dialog_text_margin" + android:focusable="true" > + + <ImageView + android:id="@+id/app_icon" + android:layout_height="@dimen/ongoing_appops_dialog_icon_height" + android:layout_width="@dimen/ongoing_appops_dialog_icon_height" + /> + + <TextView + android:id="@+id/app_name" + android:layout_height="@dimen/ongoing_appops_dialog_icon_height" + android:layout_width="0dp" + android:layout_weight="1" + android:gravity="bottom|start" + android:textDirection="locale" + android:textAppearance="@style/TextAppearance.AppOpsDialog.Item" + android:textColor="@*android:color/text_color_primary" + android:paddingStart="@dimen/ongoing_appops_dialog_text_padding" + android:paddingEnd="@dimen/ongoing_appops_dialog_text_padding" + + /> + + <LinearLayout + android:id="@+id/icons" + android:layout_height="@dimen/ongoing_appops_dialog_icon_height" + android:layout_width="wrap_content" + android:gravity="end" + android:visibility="gone" + /> +</LinearLayout>
\ No newline at end of file diff --git a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml index e7f2c51d124b..22b8d2ff4db0 100644 --- a/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml +++ b/packages/SystemUI/res/layout/quick_status_bar_header_system_icons.xml @@ -59,7 +59,7 @@ android:layout_height="match_parent" android:layout_weight="1" android:orientation="horizontal" - android:gravity="center_vertical|end"> + android:gravity="center_vertical|end" > <include layout="@layout/ongoing_privacy_chip" /> @@ -67,6 +67,7 @@ android:id="@+id/battery" android:layout_height="match_parent" android:layout_width="wrap_content" - android:gravity="center_vertical|end" /> + android:gravity="center_vertical|end" + android:layout_gravity="center_vertical|end" /> </LinearLayout> </LinearLayout> diff --git a/packages/SystemUI/res/values-land/dimens.xml b/packages/SystemUI/res/values-land/dimens.xml index bb0c6f6acb06..df858f0c54e2 100644 --- a/packages/SystemUI/res/values-land/dimens.xml +++ b/packages/SystemUI/res/values-land/dimens.xml @@ -34,4 +34,5 @@ <bool name="quick_settings_wide">true</bool> <dimen name="qs_detail_margin_top">0dp</dimen> <dimen name="qs_paged_tile_layout_padding_bottom">0dp</dimen> + <dimen name="ongoing_appops_top_chip_margin">2dp</dimen> </resources> diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index d4e698722662..97f5f8672b7d 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -940,18 +940,34 @@ that just start below the notch. --> <dimen name="display_cutout_touchable_region_size">12dp</dimen> + <!-- Padding below Ongoing App Ops dialog title --> + <dimen name="ongoing_appops_dialog_sep">16dp</dimen> + <!--Padding around text items in Ongoing App Ops dialog --> + <dimen name="ongoing_appops_dialog_text_padding">16dp</dimen> <!-- Height of icons in Ongoing App Ops dialog. Both App Op icon and application icon --> - <dimen name="ongoing_appops_dialog_icon_height">48dp</dimen> + <dimen name="ongoing_appops_dialog_icon_height">28dp</dimen> <!-- Margin between text lines in Ongoing App Ops dialog --> <dimen name="ongoing_appops_dialog_text_margin">15dp</dimen> + <!-- Side padding of title in Ongoing App Ops dialog --> + <dimen name="ongoing_appops_dialog_title_padding">10dp</dimen> <!-- Padding around Ongoing App Ops dialog content --> <dimen name="ongoing_appops_dialog_content_padding">24dp</dimen> - <!-- Margins around the Ongoing App Ops chip. In landscape, the side margins are 0 --> + <!-- Side margins around the Ongoing App Ops chip--> <dimen name="ongoing_appops_chip_margin">12dp</dimen> + <!-- Top and bottom margins around the Ongoing App Ops chip --> + <dimen name="ongoing_appops_top_chip_margin">12dp</dimen> <!-- Start and End padding for Ongoing App Ops chip --> <dimen name="ongoing_appops_chip_side_padding">6dp</dimen> <!-- Padding between background of Ongoing App Ops chip and content --> - <dimen name="ongoing_appops_chip_bg_padding">4dp</dimen> + <dimen name="ongoing_appops_chip_bg_padding">0dp</dimen> + <!-- Margin between icons of Ongoing App Ops chip --> + <dimen name="ongoing_appops_chip_icon_margin">4dp</dimen> + <!-- Icon size of Ongoing App Ops chip --> + <dimen name="ongoing_appops_chip_icon_size">18dp</dimen> <!-- Radius of Ongoing App Ops chip corners --> <dimen name="ongoing_appops_chip_bg_corner_radius">12dp</dimen> + <!-- Text size for Ongoing App Ops dialog title --> + <dimen name="ongoing_appops_dialog_title_size">24sp</dimen> + <!-- Text size for Ongoing App Ops dialog items --> + <dimen name="ongoing_appops_dialog_item_size">20sp</dimen> </resources> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index 50454fc9bcf2..4a0bc9b81378 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -2250,39 +2250,48 @@ app for debugging. Will not be seen by users. [CHAR LIMIT=20] --> <string name="heap_dump_tile_name">Dump SysUI Heap</string> + <!-- Text on chip for multiple apps using a single app op [CHAR LIMIT=10] --> + <string name="ongoing_privacy_chip_multiple_apps"><xliff:g id="num_apps" example="3">%d</xliff:g> apps</string> + <!-- Content description for ongoing privacy chip. Use with a single app [CHAR LIMIT=NONE]--> <string name="ongoing_privacy_chip_content_single_app"><xliff:g id="app" example="Example App">%1$s</xliff:g> is using your <xliff:g id="types_list" example="camera, location">%2$s</xliff:g>.</string> <!-- Content description for ongoing privacy chip. Use with multiple apps [CHAR LIMIT=NONE]--> <string name="ongoing_privacy_chip_content_multiple_apps">Applications are using your <xliff:g id="types_list" example="camera, location">%s</xliff:g>.</string> - <!-- Action on Ongoing Privacy Dialog to open application [CHAR LIMIT=10]--> - <string name="ongoing_privacy_dialog_open_app">Open app</string> + <!-- Content description for ongoing privacy chip. Use with multiple apps using same app op[CHAR LIMIT=NONE]--> + <string name="ongoing_privacy_chip_content_multiple_apps_single_op"><xliff:g id="num_apps" example="3">%1$d</xliff:g> applications are using your <xliff:g id="type" example="camera">%2$s</xliff:g>.</string> <!-- Action on Ongoing Privacy Dialog to dismiss [CHAR LIMIT=10]--> <string name="ongoing_privacy_dialog_cancel">Cancel</string> - <!-- Action on Ongoing Privacy Dialog to dismiss [CHAR LIMIT=10]--> - <string name="ongoing_privacy_dialog_okay">Okay</string> + <!-- Action on Ongoing Privacy Dialog to open privacy hub [CHAR LIMIT=15]--> + <string name="ongoing_privacy_dialog_open_settings">View details</string> - <!-- Action on Ongoing Privacy Dialog to open privacy hub [CHAR LIMIT=10]--> - <string name="ongoing_privacy_dialog_open_settings">Settings</string> + <!-- Text for item in Ongoing Privacy Dialog title when only one app is using app ops [CHAR LIMIT=NONE] --> + <string name="ongoing_privacy_dialog_single_app_title">App using your <xliff:g id="types_list" example="camera( and location)">%s</xliff:g></string> - <!-- Text for item in Ongoing Privacy Dialog when only one app is using a particular type of app op [CHAR LIMIT=NONE] --> - <string name="ongoing_privacy_dialog_app_item"><xliff:g id="app" example="Example App">%1$s</xliff:g> is using your <xliff:g id="type" example="camera">%2$s</xliff:g> for the last <xliff:g id="time" example="3">%3$d</xliff:g> min</string> + <!-- Text for item in Ongoing Privacy Dialog title when multiple apps is using app ops [CHAR LIMIT=NONE] --> + <string name="ongoing_privacy_dialog_multiple_apps_title">Apps using your <xliff:g id="types_list" example="camera( and location)">%s</xliff:g></string> - <!-- Text for item in Ongoing Privacy Dialog when only multiple apps are using a particular type of app op [CHAR LIMIT=NONE] --> - <string name="ongoing_privacy_dialog_apps_item"><xliff:g id="apps" example="Camera, Phone">%1$s</xliff:g> are using your <xliff:g id="type" example="camera">%2$s</xliff:g></string> + <!-- Separator for types. Include spaces before and after if needed [CHAR LIMIT=10] --> + <string name="ongoing_privacy_dialog_separator">,\u0020</string> - <!-- Text for Ongoing Privacy Dialog when a single app is using app ops [CHAR LIMIT=NONE] --> - <string name="ongoing_privacy_dialog_single_app"><xliff:g id="app" example="Example App">%1$s</xliff:g> is using your <xliff:g id="types_list" example="camera, location">%2$s</xliff:g></string> + <!-- Separator for types, before last type. Include spaces before and after if needed [CHAR LIMIT=10] --> + <string name="ongoing_privacy_dialog_last_separator">\u0020and\u0020</string> - <!-- Text for camera app op [CHAR LIMIT=12]--> + <!-- Text for camera app op [CHAR LIMIT=20]--> <string name="privacy_type_camera">camera</string> - <!-- Text for location app op [CHAR LIMIT=12]--> + <!-- Text for location app op [CHAR LIMIT=20]--> <string name="privacy_type_location">location</string> - <!-- Text for microphone app op [CHAR LIMIT=12]--> + <!-- Text for microphone app op [CHAR LIMIT=20]--> <string name="privacy_type_microphone">microphone</string> + + <!-- Text for indicating extra apps using app ops [CHAR LIMIT=NONE] --> + <plurals name="ongoing_privacy_dialog_overflow_text"> + <item quantity="one"><xliff:g id="num_apps" example="1">%d</xliff:g> other app</item> + <item quantity="other"><xliff:g id="num_apps" example="3">%d</xliff:g> other app</item> + </plurals> </resources> diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 95fd86fd2e95..e9aa1b65f48a 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -253,6 +253,18 @@ <item name="android:textSize">@dimen/qs_carrier_info_text_size</item> </style> + <style name="TextAppearance.AppOpsDialog" /> + + <style name="TextAppearance.AppOpsDialog.Title"> + <item name="android:textSize">@dimen/ongoing_appops_dialog_title_size</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamilyMedium</item> + </style> + + <style name="TextAppearance.AppOpsDialog.Item"> + <item name="android:textSize">@dimen/ongoing_appops_dialog_item_size</item> + <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item> + </style> + <style name="BaseBrightnessDialogContainer" parent="@style/Theme.SystemUI"> <item name="android:layout_width">match_parent</item> <item name="android:layout_height">wrap_content</item> diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt index fc1baeff706e..d3715d04b7bc 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyChip.kt @@ -15,7 +15,6 @@ package com.android.systemui.privacy import android.content.Context -import android.graphics.Color import android.util.AttributeSet import android.view.ViewGroup import android.widget.ImageView @@ -30,7 +29,13 @@ class OngoingPrivacyChip @JvmOverloads constructor( defStyleRes: Int = 0 ) : LinearLayout(context, attrs, defStyleAttrs, defStyleRes) { - private lateinit var appName: TextView + private val iconMargin = + context.resources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_margin) + private val iconSize = + context.resources.getDimensionPixelSize(R.dimen.ongoing_appops_chip_icon_size) + val iconColor = context.resources.getColor( + R.color.status_bar_clock_color, context.theme) + private lateinit var text: TextView private lateinit var iconsContainer: LinearLayout var builder = PrivacyDialogBuilder(context, emptyList<PrivacyItem>()) var privacyList = emptyList<PrivacyItem>() @@ -43,7 +48,7 @@ class OngoingPrivacyChip @JvmOverloads constructor( override fun onFinishInflate() { super.onFinishInflate() - appName = findViewById(R.id.app_name) + text = findViewById(R.id.text_container) iconsContainer = findViewById(R.id.icons_container) } @@ -53,39 +58,52 @@ class OngoingPrivacyChip @JvmOverloads constructor( iconsContainer.removeAllViews() dialogBuilder.generateIcons().forEach { it.mutate() - it.setTint(Color.WHITE) - iconsContainer.addView(ImageView(context).apply { + it.setTint(iconColor) + val image = ImageView(context).apply { setImageDrawable(it) - maxHeight = this@OngoingPrivacyChip.height - }) + scaleType = ImageView.ScaleType.CENTER_INSIDE + } + iconsContainer.addView(image, iconSize, iconSize) + val lp = image.layoutParams as MarginLayoutParams + lp.marginStart = iconMargin + image.layoutParams = lp } } - if (privacyList.isEmpty()) { - return - } else { + if (!privacyList.isEmpty()) { generateContentDescription() setIcons(builder, iconsContainer) - appName.visibility = GONE - builder.app?.let { - appName.apply { - setText(it.applicationName) - setTextColor(Color.WHITE) - visibility = VISIBLE + text.visibility = if (builder.types.size == 1) VISIBLE else GONE + if (builder.types.size == 1) { + if (builder.app != null) { + text.setText(builder.app?.applicationName) + } else { + text.text = context.getString(R.string.ongoing_privacy_chip_multiple_apps, + builder.appsAndTypes.size) } } + } else { + text.visibility = GONE + iconsContainer.removeAllViews() } requestLayout() } private fun generateContentDescription() { - val typesText = builder.generateTypesText() - if (builder.app != null) { - contentDescription = context.getString(R.string.ongoing_privacy_chip_content_single_app, - builder.app?.applicationName, typesText) - } else { + val typesText = builder.joinTypes() + if (builder.types.size > 1) { contentDescription = context.getString( R.string.ongoing_privacy_chip_content_multiple_apps, typesText) + } else { + if (builder.app != null) { + contentDescription = + context.getString(R.string.ongoing_privacy_chip_content_single_app, + builder.app?.applicationName, typesText) + } else { + contentDescription = context.getString( + R.string.ongoing_privacy_chip_content_multiple_apps_single_op, + builder.appsAndTypes.size, typesText) + } } } }
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt index 1d0e16ed3334..f6a95af4a075 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/OngoingPrivacyDialog.kt @@ -18,10 +18,10 @@ import android.app.AlertDialog import android.app.Dialog import android.content.Context import android.content.DialogInterface -import android.graphics.drawable.Drawable +import android.content.Intent +import android.content.res.ColorStateList import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView @@ -34,29 +34,25 @@ class OngoingPrivacyDialog constructor( val dialogBuilder: PrivacyDialogBuilder ) { - val iconHeight = context.resources.getDimensionPixelSize( + val iconSize = context.resources.getDimensionPixelSize( R.dimen.ongoing_appops_dialog_icon_height) - val textMargin = context.resources.getDimensionPixelSize( - R.dimen.ongoing_appops_dialog_text_margin) val iconColor = context.resources.getColor( com.android.internal.R.color.text_color_primary, context.theme) + companion object { + private const val MAX_ITEMS = 10 + } fun createDialog(): Dialog { - val builder = AlertDialog.Builder(context) - .setNeutralButton(R.string.ongoing_privacy_dialog_open_settings, null) - if (dialogBuilder.app != null) { - builder.setPositiveButton(R.string.ongoing_privacy_dialog_open_app, + val builder = AlertDialog.Builder(context).apply { + setNegativeButton(R.string.ongoing_privacy_dialog_cancel, null) + setPositiveButton(R.string.ongoing_privacy_dialog_open_settings, object : DialogInterface.OnClickListener { - val intent = context.packageManager - .getLaunchIntentForPackage(dialogBuilder.app.packageName) + val intent = Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE) override fun onClick(dialog: DialogInterface?, which: Int) { Dependency.get(ActivityStarter::class.java).startActivity(intent, false) } }) - builder.setNegativeButton(R.string.ongoing_privacy_dialog_cancel, null) - } else { - builder.setPositiveButton(R.string.ongoing_privacy_dialog_okay, null) } builder.setView(getContentView()) return builder.create() @@ -66,44 +62,67 @@ class OngoingPrivacyDialog constructor( val layoutInflater = LayoutInflater.from(context) val contentView = layoutInflater.inflate(R.layout.ongoing_privacy_dialog_content, null) - val iconsContainer = contentView.findViewById(R.id.icons_container) as LinearLayout - val textContainer = contentView.findViewById(R.id.text_container) as LinearLayout + val title = contentView.findViewById(R.id.title) as TextView + val appsList = contentView.findViewById(R.id.items_container) as LinearLayout + + title.setText(dialogBuilder.getDialogTitle()) - addIcons(dialogBuilder, iconsContainer) - val lm = ViewGroup.MarginLayoutParams( - ViewGroup.MarginLayoutParams.WRAP_CONTENT, - ViewGroup.MarginLayoutParams.WRAP_CONTENT) - lm.topMargin = textMargin - val now = System.currentTimeMillis() - dialogBuilder.generateText(now).forEach { - val text = layoutInflater.inflate(R.layout.ongoing_privacy_text_item, null) as TextView - text.setText(it) - textContainer.addView(text, lm) + val numItems = dialogBuilder.appsAndTypes.size + for (i in 0..(numItems - 1)) { + if (i >= MAX_ITEMS) break + val item = dialogBuilder.appsAndTypes[i] + addAppItem(appsList, item.first, item.second, dialogBuilder.types.size > 1) + } + + if (numItems > MAX_ITEMS) { + val overflow = contentView.findViewById(R.id.overflow) as LinearLayout + overflow.visibility = View.VISIBLE + val overflowText = overflow.findViewById(R.id.app_name) as TextView + overflowText.text = context.resources.getQuantityString( + R.plurals.ongoing_privacy_dialog_overflow_text, + numItems - MAX_ITEMS, + numItems - MAX_ITEMS + ) + val overflowPlus = overflow.findViewById(R.id.app_icon) as ImageView + overflowPlus.apply { + imageTintList = ColorStateList.valueOf(iconColor) + setImageDrawable(context.getDrawable(R.drawable.plus)) + } } + return contentView } - private fun addIcons(dialogBuilder: PrivacyDialogBuilder, iconsContainer: LinearLayout) { + private fun addAppItem( + itemList: LinearLayout, + app: PrivacyApplication, + types: List<PrivacyType>, + showIcons: Boolean = true + ) { + val layoutInflater = LayoutInflater.from(context) + val item = layoutInflater.inflate(R.layout.ongoing_privacy_dialog_item, itemList, false) + val appIcon = item.findViewById(R.id.app_icon) as ImageView + val appName = item.findViewById(R.id.app_name) as TextView + val icons = item.findViewById(R.id.icons) as LinearLayout - fun LinearLayout.addIcon(icon: Drawable) { - val image = ImageView(context).apply { - setImageDrawable(icon.apply { - setBounds(0, 0, iconHeight, iconHeight) - maxHeight = this@addIcon.height - }) - adjustViewBounds = true - } - addView(image, LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.MATCH_PARENT) + app.icon?.let { + appIcon.setImageDrawable(it) } - dialogBuilder.generateIcons().forEach { - it.mutate() - it.setTint(iconColor) - iconsContainer.addIcon(it) - } - dialogBuilder.app.let { - it?.icon?.let { iconsContainer.addIcon(it) } + appName.text = app.applicationName + if (showIcons) { + dialogBuilder.generateIconsForApp(types).forEach { + it.setBounds(0, 0, iconSize, iconSize) + val image = ImageView(context).apply { + imageTintList = ColorStateList.valueOf(iconColor) + setImageDrawable(it) + } + icons.addView(image, iconSize, LinearLayout.LayoutParams.WRAP_CONTENT) + } + icons.visibility = View.VISIBLE + } else { + icons.visibility = View.GONE } + itemList.addView(item) } } diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt index 5ce4ee738373..519df19f0e20 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogBuilder.kt @@ -15,59 +15,53 @@ package com.android.systemui.privacy import android.content.Context +import android.graphics.drawable.Drawable import com.android.systemui.R -import java.lang.Math.max class PrivacyDialogBuilder(val context: Context, itemsList: List<PrivacyItem>) { - companion object { - val MILLIS_IN_MINUTE: Long = 1000 * 60 - } - private val itemsByType: Map<PrivacyType, List<PrivacyItem>> + val appsAndTypes: List<Pair<PrivacyApplication, List<PrivacyType>>> + val types: List<PrivacyType> val app: PrivacyApplication? + private val separator = context.getString(R.string.ongoing_privacy_dialog_separator) + private val lastSeparator = context.getString(R.string.ongoing_privacy_dialog_last_separator) init { - itemsByType = itemsList.groupBy { it.privacyType } - val apps = itemsList.map { it.application }.distinct() - val singleApp = apps.size == 1 - app = if (singleApp) apps.get(0) else null + appsAndTypes = itemsList.groupBy({ it.application }, { it.privacyType }) + .toList() + .sortedWith(compareBy({ -it.second.size }, { it.first })) + types = itemsList.map { it.privacyType }.distinct().sorted() + val singleApp = appsAndTypes.size == 1 + app = if (singleApp) appsAndTypes[0].first else null + } + + fun generateIconsForApp(types: List<PrivacyType>): List<Drawable> { + return types.sorted().map { it.getIcon(context) } } - private fun buildTextForItem(type: PrivacyType, now: Long): String { - val items = itemsByType.getOrDefault(type, emptyList<PrivacyItem>()) - return when (items.size) { - 0 -> throw IllegalStateException("List cannot be empty") - 1 -> { - val item = items.get(0) - val minutesUsed = max(((now - item.timeStarted) / MILLIS_IN_MINUTE).toInt(), 1) - context.getString(R.string.ongoing_privacy_dialog_app_item, - item.application.applicationName, type.getName(context), minutesUsed) - } - else -> { - val apps = items.map { it.application.applicationName }.joinToString() - context.getString(R.string.ongoing_privacy_dialog_apps_item, - apps, type.getName(context)) - } + fun generateIcons() = types.map { it.getIcon(context) } + + private fun <T> List<T>.joinWithAnd(): StringBuilder { + return subList(0, size - 1).joinTo(StringBuilder(), separator = separator).apply { + append(lastSeparator) + append(this@joinWithAnd.last()) } } - private fun buildTextForApp(types: Set<PrivacyType>): List<String> { - app?.let { - val typesText = types.map { it.getName(context) }.sorted().joinToString() - return listOf(context.getString(R.string.ongoing_privacy_dialog_single_app, - it.applicationName, typesText)) - } ?: throw IllegalStateException("There has to be a single app") + fun joinTypes(): String { + return when (types.size) { + 0 -> "" + 1 -> types[0].getName(context) + else -> types.map { it.getName(context) }.joinWithAnd().toString() + } } - fun generateText(now: Long): List<String> { - if (app == null || itemsByType.keys.size == 1) { - return itemsByType.keys.map { buildTextForItem(it, now) } + fun getDialogTitle(): String { + if (app != null) { + return context.getString(R.string.ongoing_privacy_dialog_single_app_title, joinTypes()) } else { - return buildTextForApp(itemsByType.keys) + return context.getString(R.string.ongoing_privacy_dialog_multiple_apps_title, + joinTypes()) } } - - fun generateTypesText() = itemsByType.keys.map { it.getName(context) }.sorted().joinToString() - - fun generateIcons() = itemsByType.keys.map { it.getIcon(context) } } diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt index 3d9aa0ff083f..85e99f05f895 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItem.kt @@ -29,16 +29,21 @@ enum class PrivacyType(val nameId: Int, val iconId: Int) { fun getName(context: Context) = context.resources.getString(nameId) - fun getIcon(context: Context) = context.resources.getDrawable(iconId, null) + fun getIcon(context: Context) = context.resources.getDrawable(iconId, context.theme) } data class PrivacyItem( val privacyType: PrivacyType, - val application: PrivacyApplication, - val timeStarted: Long + val application: PrivacyApplication ) -data class PrivacyApplication(val packageName: String, val context: Context) { +data class PrivacyApplication(val packageName: String, val context: Context) + : Comparable<PrivacyApplication> { + + override fun compareTo(other: PrivacyApplication): Int { + return applicationName.compareTo(other.applicationName) + } + var icon: Drawable? = null var applicationName: String diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt index 5141e5055e9b..3fa3e8eec0ab 100644 --- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt +++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyItemController.kt @@ -95,7 +95,7 @@ class PrivacyItemController(val context: Context, val callback: Callback) { else -> return null } val app = PrivacyApplication(appOpItem.packageName, context) - return PrivacyItem(type, app, appOpItem.timeStarted) + return PrivacyItem(type, app) } // Used by containing class to get notified of changes diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java index e3f85d93b8da..427f638b0d30 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java +++ b/packages/SystemUI/src/com/android/systemui/qs/QuickStatusBarHeader.java @@ -325,15 +325,10 @@ public class QuickStatusBarHeader extends RelativeLayout implements newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE; mBatteryMeterView.useWallpaperTextColor(shouldUseWallpaperTextColor); mClockView.useWallpaperTextColor(shouldUseWallpaperTextColor); - - MarginLayoutParams lm = (MarginLayoutParams) mPrivacyChip.getLayoutParams(); - int sideMargins = lm.leftMargin; - int topBottomMargins = (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) - ? 0 : sideMargins; - lm.setMargins(sideMargins, topBottomMargins, sideMargins, topBottomMargins); - mPrivacyChip.setLayoutParams(lm); } + + @Override public void onRtlPropertiesChanged(int layoutDirection) { super.onRtlPropertiesChanged(layoutDirection); @@ -378,6 +373,15 @@ public class QuickStatusBarHeader extends RelativeLayout implements setLayoutParams(lp); + if (mPrivacyChip != null) { + MarginLayoutParams lm = (MarginLayoutParams) mPrivacyChip.getLayoutParams(); + int sideMargins = lm.leftMargin; + int topBottomMargins = resources.getDimensionPixelSize( + R.dimen.ongoing_appops_top_chip_margin); + lm.setMargins(sideMargins, topBottomMargins, sideMargins, topBottomMargins); + mPrivacyChip.setLayoutParams(lm); + } + updateStatusIconAlphaAnimator(); updateHeaderTextContainerAlphaAnimator(); } @@ -729,7 +733,8 @@ public class QuickStatusBarHeader extends RelativeLayout implements public void setMargins(int sideMargins) { for (int i = 0; i < getChildCount(); i++) { View v = getChildAt(i); - if (v == mSystemIconsView || v == mQuickQsStatusIcons || v == mHeaderQsPanel) { + if (v == mSystemIconsView || v == mQuickQsStatusIcons || v == mHeaderQsPanel + || v == mPrivacyChip) { continue; } RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) v.getLayoutParams(); diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt index 7204d310a76d..b23f667e4388 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt +++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogBuilderTest.kt @@ -27,55 +27,28 @@ import org.junit.runner.RunWith @SmallTest class PrivacyDialogBuilderTest : SysuiTestCase() { - companion object { - val MILLIS_IN_MINUTE: Long = 1000 * 60 - val NOW = 4 * MILLIS_IN_MINUTE - } - @Test - fun testGenerateText_multipleApps() { + fun testGenerateAppsList() { val bar2 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication( - "Bar", context), 2 * MILLIS_IN_MINUTE) + "Bar", context)) val bar3 = PrivacyItem(Privacy.TYPE_LOCATION, PrivacyApplication( - "Bar", context), 3 * MILLIS_IN_MINUTE) + "Bar", context)) val foo0 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication( - "Foo", context), 0) + "Foo", context)) val baz1 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication( - "Baz", context), 1 * MILLIS_IN_MINUTE) + "Baz", context)) val items = listOf(bar2, foo0, baz1, bar3) val textBuilder = PrivacyDialogBuilder(context, items) - val textList = textBuilder.generateText(NOW) - assertEquals(2, textList.size) - assertEquals("Bar, Foo, Baz are using your camera", textList[0]) - assertEquals("Bar is using your location for the last 1 min", textList[1]) - } - - @Test - fun testGenerateText_singleApp() { - val bar2 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication( - "Bar", context), 0) - val bar1 = PrivacyItem(Privacy.TYPE_LOCATION, PrivacyApplication( - "Bar", context), 0) - - val items = listOf(bar2, bar1) - - val textBuilder = PrivacyDialogBuilder(context, items) - val textList = textBuilder.generateText(NOW) - assertEquals(1, textList.size) - assertEquals("Bar is using your camera, location", textList[0]) - } - - @Test - fun testGenerateText_singleApp_singleType() { - val bar2 = PrivacyItem(Privacy.TYPE_CAMERA, PrivacyApplication( - "Bar", context), 2 * MILLIS_IN_MINUTE) - val items = listOf(bar2) - val textBuilder = PrivacyDialogBuilder(context, items) - val textList = textBuilder.generateText(NOW) - assertEquals(1, textList.size) - assertEquals("Bar is using your camera for the last 2 min", textList[0]) + val list = textBuilder.appsAndTypes + assertEquals(3, list.size) + val appsList = list.map { it.first } + val typesList = list.map { it.second } + assertEquals(listOf("Bar", "Baz", "Foo"), appsList.map { it.packageName }) + assertEquals(listOf(Privacy.TYPE_CAMERA, Privacy.TYPE_LOCATION), typesList[0]) + assertEquals(listOf(Privacy.TYPE_CAMERA), typesList[1]) + assertEquals(listOf(Privacy.TYPE_CAMERA), typesList[2]) } }
\ No newline at end of file |