diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
index dbfa192f5ec48..37b1ee543e463 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/DialogLaunchAnimator.kt
@@ -32,11 +32,10 @@ import android.view.ViewRootImpl
import android.view.WindowInsets
import android.view.WindowManager
import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
-import android.widget.FrameLayout
import com.android.app.animation.Interpolators
import com.android.internal.jank.InteractionJankMonitor
import com.android.internal.jank.InteractionJankMonitor.CujType
-import com.android.systemui.animation.view.LaunchableFrameLayout
+import com.android.systemui.util.maybeForceFullscreen
import com.android.systemui.util.registerAnimationOnBackInvoked
import kotlin.math.roundToInt
@@ -622,96 +621,12 @@ private class AnimatedDialog(
viewGroupWithBackground
} else {
- // We will make the dialog window (and therefore its DecorView) fullscreen to make
- // it possible to animate outside its bounds.
- //
- // Before that, we add a new View as a child of the DecorView with the same size and
- // gravity as that DecorView, then we add all original children of the DecorView to
- // that new View. Finally we remove the background of the DecorView and add it to
- // the new View, then we make the DecorView fullscreen. This new View now acts as a
- // fake (non fullscreen) window.
- //
- // On top of that, we also add a fullscreen transparent background between the
- // DecorView and the view that we added so that we can dismiss the dialog when this
- // view is clicked. This is necessary because DecorView overrides onTouchEvent and
- // therefore we can't set the click listener directly on the (now fullscreen)
- // DecorView.
- val fullscreenTransparentBackground = FrameLayout(dialog.context)
- decorView.addView(
- fullscreenTransparentBackground,
- 0 /* index */,
- FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
- )
-
- val dialogContentWithBackground = LaunchableFrameLayout(dialog.context)
- dialogContentWithBackground.background = decorView.background
-
- // Make the window background transparent. Note that setting the window (or
- // DecorView) background drawable to null leads to issues with background color (not
- // being transparent) or with insets that are not refreshed. Therefore we need to
- // set it to something not null, hence we are using android.R.color.transparent
- // here.
- window.setBackgroundDrawableResource(android.R.color.transparent)
-
- // Close the dialog when clicking outside of it.
- fullscreenTransparentBackground.setOnClickListener { dialog.dismiss() }
- dialogContentWithBackground.isClickable = true
-
- // Make sure the transparent and dialog backgrounds are not focusable by
- // accessibility
- // features.
- fullscreenTransparentBackground.importantForAccessibility =
- View.IMPORTANT_FOR_ACCESSIBILITY_NO
- dialogContentWithBackground.importantForAccessibility =
- View.IMPORTANT_FOR_ACCESSIBILITY_NO
-
- fullscreenTransparentBackground.addView(
- dialogContentWithBackground,
- FrameLayout.LayoutParams(
- window.attributes.width,
- window.attributes.height,
- window.attributes.gravity
- )
- )
-
- // Move all original children of the DecorView to the new View we just added.
- for (i in 1 until decorView.childCount) {
- val view = decorView.getChildAt(1)
- decorView.removeViewAt(1)
- dialogContentWithBackground.addView(view)
- }
-
- // Make the window fullscreen and add a layout listener to ensure it stays
- // fullscreen.
- window.setLayout(MATCH_PARENT, MATCH_PARENT)
- decorViewLayoutListener =
- View.OnLayoutChangeListener {
- v,
- left,
- top,
- right,
- bottom,
- oldLeft,
- oldTop,
- oldRight,
- oldBottom ->
- if (
- window.attributes.width != MATCH_PARENT ||
- window.attributes.height != MATCH_PARENT
- ) {
- // The dialog size changed, copy its size to dialogContentWithBackground
- // and make the dialog window full screen again.
- val layoutParams = dialogContentWithBackground.layoutParams
- layoutParams.width = window.attributes.width
- layoutParams.height = window.attributes.height
- dialogContentWithBackground.layoutParams = layoutParams
- window.setLayout(MATCH_PARENT, MATCH_PARENT)
- }
- }
- decorView.addOnLayoutChangeListener(decorViewLayoutListener)
-
+ val (dialogContentWithBackground, decorViewLayoutListener) =
+ dialog.maybeForceFullscreen()!!
+ this.decorViewLayoutListener = decorViewLayoutListener
dialogContentWithBackground
}
+
this.dialogContentWithBackground = dialogContentWithBackground
dialogContentWithBackground.setTag(R.id.tag_dialog_background, true)
diff --git a/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt b/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt
index 428856dc5f300..0f63548b6f0c1 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/util/Dialog.kt
@@ -18,6 +18,9 @@ package com.android.systemui.util
import android.app.Dialog
import android.view.View
+import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.widget.FrameLayout
import android.window.OnBackInvokedDispatcher
import com.android.systemui.animation.back.BackAnimationSpec
import com.android.systemui.animation.back.BackTransformation
@@ -25,6 +28,7 @@ import com.android.systemui.animation.back.applyTo
import com.android.systemui.animation.back.floatingSystemSurfacesForSysUi
import com.android.systemui.animation.back.onBackAnimationCallbackFrom
import com.android.systemui.animation.back.registerOnBackInvokedCallbackOnViewAttached
+import com.android.systemui.animation.view.LaunchableFrameLayout
/**
* Register on the Dialog's [OnBackInvokedDispatcher] an animation using the [BackAnimationSpec].
@@ -49,3 +53,110 @@ fun Dialog.registerAnimationOnBackInvoked(
),
)
}
+
+/**
+ * Make the dialog window (and therefore its DecorView) fullscreen to make it possible to animate
+ * outside its bounds. No-op if the dialog is already fullscreen.
+ *
+ *
Returns null if the dialog is already fullscreen. Otherwise, returns a pair containing a view
+ * and a layout listener. The new view matches the original dialog DecorView in size, position, and
+ * background. This new view will be a child of the modified, transparent, fullscreen DecorView. The
+ * layout listener is listening to changes to the modified DecorView. It is the responsibility of
+ * the caller to deregister the listener when the dialog is dismissed.
+ */
+fun Dialog.maybeForceFullscreen(): Pair? {
+ // Create the dialog so that its onCreate() method is called, which usually sets the dialog
+ // content.
+ create()
+
+ val window = window!!
+ val decorView = window.decorView as ViewGroup
+
+ val isWindowFullscreen =
+ window.attributes.width == MATCH_PARENT && window.attributes.height == MATCH_PARENT
+ if (isWindowFullscreen) {
+ return null
+ }
+
+ // We will make the dialog window (and therefore its DecorView) fullscreen to make it possible
+ // to animate outside its bounds.
+ //
+ // Before that, we add a new View as a child of the DecorView with the same size and gravity as
+ // that DecorView, then we add all original children of the DecorView to that new View. Finally
+ // we remove the background of the DecorView and add it to the new View, then we make the
+ // DecorView fullscreen. This new View now acts as a fake (non fullscreen) window.
+ //
+ // On top of that, we also add a fullscreen transparent background between the DecorView and the
+ // view that we added so that we can dismiss the dialog when this view is clicked. This is
+ // necessary because DecorView overrides onTouchEvent and therefore we can't set the click
+ // listener directly on the (now fullscreen) DecorView.
+ val fullscreenTransparentBackground = FrameLayout(context)
+ decorView.addView(
+ fullscreenTransparentBackground,
+ 0 /* index */,
+ FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+ )
+
+ val dialogContentWithBackground = LaunchableFrameLayout(context)
+ dialogContentWithBackground.background = decorView.background
+
+ // Make the window background transparent. Note that setting the window (or DecorView)
+ // background drawable to null leads to issues with background color (not being transparent) or
+ // with insets that are not refreshed. Therefore we need to set it to something not null, hence
+ // we are using android.R.color.transparent here.
+ window.setBackgroundDrawableResource(android.R.color.transparent)
+
+ // Close the dialog when clicking outside of it.
+ fullscreenTransparentBackground.setOnClickListener { dismiss() }
+ dialogContentWithBackground.isClickable = true
+
+ // Make sure the transparent and dialog backgrounds are not focusable by accessibility
+ // features.
+ fullscreenTransparentBackground.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+ dialogContentWithBackground.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+
+ fullscreenTransparentBackground.addView(
+ dialogContentWithBackground,
+ FrameLayout.LayoutParams(
+ window.attributes.width,
+ window.attributes.height,
+ window.attributes.gravity
+ )
+ )
+
+ // Move all original children of the DecorView to the new View we just added.
+ for (i in 1 until decorView.childCount) {
+ val view = decorView.getChildAt(1)
+ decorView.removeViewAt(1)
+ dialogContentWithBackground.addView(view)
+ }
+
+ // Make the window fullscreen and add a layout listener to ensure it stays fullscreen.
+ window.setLayout(MATCH_PARENT, MATCH_PARENT)
+ val decorViewLayoutListener =
+ View.OnLayoutChangeListener {
+ v,
+ left,
+ top,
+ right,
+ bottom,
+ oldLeft,
+ oldTop,
+ oldRight,
+ oldBottom ->
+ if (
+ window.attributes.width != MATCH_PARENT || window.attributes.height != MATCH_PARENT
+ ) {
+ // The dialog size changed, copy its size to dialogContentWithBackground and make
+ // the dialog window full screen again.
+ val layoutParams = dialogContentWithBackground.layoutParams
+ layoutParams.width = window.attributes.width
+ layoutParams.height = window.attributes.height
+ dialogContentWithBackground.layoutParams = layoutParams
+ window.setLayout(MATCH_PARENT, MATCH_PARENT)
+ }
+ }
+ decorView.addOnLayoutChangeListener(decorViewLayoutListener)
+
+ return dialogContentWithBackground to decorViewLayoutListener
+}
diff --git a/packages/SystemUI/res/drawable-night/privacy_dialog_expand_toggle_down.xml b/packages/SystemUI/res/drawable-night/privacy_dialog_expand_toggle_down.xml
new file mode 100644
index 0000000000000..16076b17a6e57
--- /dev/null
+++ b/packages/SystemUI/res/drawable-night/privacy_dialog_expand_toggle_down.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/drawable-night/privacy_dialog_expand_toggle_up.xml b/packages/SystemUI/res/drawable-night/privacy_dialog_expand_toggle_up.xml
new file mode 100644
index 0000000000000..309770ddd76d3
--- /dev/null
+++ b/packages/SystemUI/res/drawable-night/privacy_dialog_expand_toggle_up.xml
@@ -0,0 +1,31 @@
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_background_circle.xml b/packages/SystemUI/res/drawable/privacy_dialog_background_circle.xml
new file mode 100644
index 0000000000000..f63c2ffbfdad9
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_background_circle.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_background_large_top_large_bottom.xml b/packages/SystemUI/res/drawable/privacy_dialog_background_large_top_large_bottom.xml
new file mode 100644
index 0000000000000..5d5529ff1a508
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_background_large_top_large_bottom.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_background_large_top_small_bottom.xml b/packages/SystemUI/res/drawable/privacy_dialog_background_large_top_small_bottom.xml
new file mode 100644
index 0000000000000..310b0becf10c4
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_background_large_top_small_bottom.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_background_small_top_large_bottom.xml b/packages/SystemUI/res/drawable/privacy_dialog_background_small_top_large_bottom.xml
new file mode 100644
index 0000000000000..e89bdd31ca179
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_background_small_top_large_bottom.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_background_small_top_small_bottom.xml b/packages/SystemUI/res/drawable/privacy_dialog_background_small_top_small_bottom.xml
new file mode 100644
index 0000000000000..fcf0b1c5091f2
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_background_small_top_small_bottom.xml
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_check_icon.xml b/packages/SystemUI/res/drawable/privacy_dialog_check_icon.xml
new file mode 100644
index 0000000000000..b9f5d60abd8bb
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_check_icon.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_default_permission_icon.xml b/packages/SystemUI/res/drawable/privacy_dialog_default_permission_icon.xml
new file mode 100644
index 0000000000000..ea8f9c2839ec1
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_default_permission_icon.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_expand_toggle_down.xml b/packages/SystemUI/res/drawable/privacy_dialog_expand_toggle_down.xml
new file mode 100644
index 0000000000000..f8b99f4a0ee4b
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_expand_toggle_down.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/drawable/privacy_dialog_expand_toggle_up.xml b/packages/SystemUI/res/drawable/privacy_dialog_expand_toggle_up.xml
new file mode 100644
index 0000000000000..ae60d517ceb4d
--- /dev/null
+++ b/packages/SystemUI/res/drawable/privacy_dialog_expand_toggle_up.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
diff --git a/packages/SystemUI/res/layout/privacy_dialog_card_button.xml b/packages/SystemUI/res/layout/privacy_dialog_card_button.xml
new file mode 100644
index 0000000000000..e297b939e2b81
--- /dev/null
+++ b/packages/SystemUI/res/layout/privacy_dialog_card_button.xml
@@ -0,0 +1,26 @@
+
+
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/privacy_dialog_item_v2.xml b/packages/SystemUI/res/layout/privacy_dialog_item_v2.xml
new file mode 100644
index 0000000000000..b84f3a9794be6
--- /dev/null
+++ b/packages/SystemUI/res/layout/privacy_dialog_item_v2.xml
@@ -0,0 +1,89 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/privacy_dialog_v2.xml b/packages/SystemUI/res/layout/privacy_dialog_v2.xml
new file mode 100644
index 0000000000000..843dad03bca45
--- /dev/null
+++ b/packages/SystemUI/res/layout/privacy_dialog_v2.xml
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index db7eb7a049e75..ab754985e11db 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -16,7 +16,8 @@
* limitations under the License.
*/
-->
-
+#ffffffff@color/system_bar_background_opaque#ff000000
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 3366f4f6d443c..0217a6d5166b3 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1097,6 +1097,9 @@
16dp
+ 12dp
+ 4dp
+
16dp14dp
diff --git a/packages/SystemUI/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index 3a2177a0045c0..15ca9d48c62a5 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -214,4 +214,8 @@
+
+
+
+
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f8c13b008fd99..d46b2b7792455 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -3167,10 +3167,43 @@
Set default notes app in Settings
-
+
Install app
+
+
+ Microphone & Camera
+
+ Recent app use
+
+ See recent access
+
+ Done
+
+ Expand and show options
+
+ Collapse
+
+ Close this app
+
+ %1$s closed
+
+ Manage service
+
+ Manage access
+
+ In use by phone call
+
+ Recently used in phone call
+
+ In use by %1$s
+
+ Recently used by %1$s
+
+ In use by %1$s (%2$s)
+
+ Recently used by %1$s (%2$s)
+
+ In use by %1$s (%2$s \u2022 %3$s)
+
+ Recently used by %1$s (%2$s \u2022 %3$s)
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index 6b8562105c74c..9f42fee47bb53 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -1439,4 +1439,22 @@
@anim/long_press_lock_screen_popup_enter@anim/long_press_lock_screen_popup_exit
+
+
+
+
+
+
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
index 805a102b1a1ca..fdc70a83e8b1a 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -26,11 +26,11 @@ import android.content.pm.PackageManager
import android.os.UserHandle
import android.permission.PermissionGroupUsage
import android.permission.PermissionManager
-import android.util.Log
+import android.view.View
import androidx.annotation.MainThread
-import androidx.annotation.VisibleForTesting
import androidx.annotation.WorkerThread
import com.android.internal.logging.UiEventLogger
+import com.android.systemui.animation.DialogLaunchAnimator
import com.android.systemui.appops.AppOpsController
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Background
@@ -42,15 +42,18 @@ import com.android.systemui.statusbar.policy.KeyguardStateController
import java.util.concurrent.Executor
import javax.inject.Inject
-private val defaultDialogProvider = object : PrivacyDialogControllerV2.DialogProvider {
- override fun makeDialog(
- context: Context,
- list: List,
- starter: (String, Int, CharSequence?, Intent?) -> Unit
- ): PrivacyDialogV2 {
- return PrivacyDialogV2(context, list, starter)
+private val defaultDialogProvider =
+ object : PrivacyDialogControllerV2.DialogProvider {
+ override fun makeDialog(
+ context: Context,
+ list: List,
+ manageApp: (String, Int, Intent) -> Unit,
+ closeApp: (String, Int) -> Unit,
+ openPrivacyDashboard: () -> Unit
+ ): PrivacyDialogV2 {
+ return PrivacyDialogV2(context, list, manageApp, closeApp, openPrivacyDashboard)
+ }
}
-}
/**
* Controller for [PrivacyDialogV2].
@@ -71,7 +74,8 @@ class PrivacyDialogControllerV2(
private val keyguardStateController: KeyguardStateController,
private val appOpsController: AppOpsController,
private val uiEventLogger: UiEventLogger,
- @VisibleForTesting private val dialogProvider: DialogProvider
+ private val dialogLaunchAnimator: DialogLaunchAnimator,
+ private val dialogProvider: DialogProvider
) {
@Inject
@@ -86,57 +90,72 @@ class PrivacyDialogControllerV2(
privacyLogger: PrivacyLogger,
keyguardStateController: KeyguardStateController,
appOpsController: AppOpsController,
- uiEventLogger: UiEventLogger
+ uiEventLogger: UiEventLogger,
+ dialogLaunchAnimator: DialogLaunchAnimator
) : this(
- permissionManager,
- packageManager,
- privacyItemController,
- userTracker,
- activityStarter,
- backgroundExecutor,
- uiExecutor,
- privacyLogger,
- keyguardStateController,
- appOpsController,
- uiEventLogger,
- defaultDialogProvider
+ permissionManager,
+ packageManager,
+ privacyItemController,
+ userTracker,
+ activityStarter,
+ backgroundExecutor,
+ uiExecutor,
+ privacyLogger,
+ keyguardStateController,
+ appOpsController,
+ uiEventLogger,
+ dialogLaunchAnimator,
+ defaultDialogProvider
)
- companion object {
- private const val TAG = "PrivacyDialogController"
- }
-
private var dialog: Dialog? = null
- private val onDialogDismissed = object : PrivacyDialogV2.OnDialogDismissed {
- override fun onDialogDismissed() {
- privacyLogger.logPrivacyDialogDismissed()
- uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_DISMISSED)
- dialog = null
+ private val onDialogDismissed =
+ object : PrivacyDialogV2.OnDialogDismissed {
+ override fun onDialogDismissed() {
+ privacyLogger.logPrivacyDialogDismissed()
+ uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_DISMISSED)
+ dialog = null
+ }
}
+
+ @WorkerThread
+ private fun closeApp(packageName: String, userId: Int) {
+ uiEventLogger.log(
+ PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_CLOSE_APP,
+ userId,
+ packageName
+ )
+ privacyLogger.logCloseAppFromDialog(packageName, userId)
+ ActivityManager.getService().stopAppForUser(packageName, userId)
}
@MainThread
- private fun startActivity(
- packageName: String,
- userId: Int,
- attributionTag: CharSequence?,
- navigationIntent: Intent?
- ) {
- val intent = if (navigationIntent == null) {
- getDefaultManageAppPermissionsIntent(packageName, userId)
- } else {
- navigationIntent
- }
- uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS,
- userId, packageName)
+ private fun manageApp(packageName: String, userId: Int, navigationIntent: Intent) {
+ uiEventLogger.log(
+ PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS,
+ userId,
+ packageName
+ )
privacyLogger.logStartSettingsActivityFromDialog(packageName, userId)
+ startActivity(navigationIntent)
+ }
+
+ @MainThread
+ private fun openPrivacyDashboard() {
+ uiEventLogger.log(PrivacyDialogEvent.PRIVACY_DIALOG_CLICK_TO_PRIVACY_DASHBOARD)
+ privacyLogger.logStartPrivacyDashboardFromDialog()
+ startActivity(Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE))
+ }
+
+ @MainThread
+ private fun startActivity(navigationIntent: Intent) {
if (!keyguardStateController.isUnlocked) {
// If we are locked, hide the dialog so the user can unlock
dialog?.hide()
}
// startActivity calls internally startActivityDismissingKeyguard
- activityStarter.startActivity(intent, true) {
+ activityStarter.startActivity(navigationIntent, true) {
if (ActivityManager.isStartResultSuccessful(it)) {
dismissDialog()
} else {
@@ -146,31 +165,29 @@ class PrivacyDialogControllerV2(
}
@WorkerThread
- private fun getManagePermissionIntent(
+ private fun getStartViewPermissionUsageIntent(
packageName: String,
- userId: Int,
- permGroupName: CharSequence,
+ permGroupName: String,
attributionTag: CharSequence?,
isAttributionSupported: Boolean
- ): Intent
- {
- lateinit var intent: Intent
+ ): Intent? {
if (attributionTag != null && isAttributionSupported) {
- intent = Intent(Intent.ACTION_MANAGE_PERMISSION_USAGE)
+ val intent = Intent(Intent.ACTION_MANAGE_PERMISSION_USAGE)
intent.setPackage(packageName)
- intent.putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, permGroupName.toString())
+ intent.putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, permGroupName)
intent.putExtra(Intent.EXTRA_ATTRIBUTION_TAGS, arrayOf(attributionTag.toString()))
intent.putExtra(Intent.EXTRA_SHOWING_ATTRIBUTION, true)
- val resolveInfo = packageManager.resolveActivity(
- intent, PackageManager.ResolveInfoFlags.of(0))
- if (resolveInfo != null && resolveInfo.activityInfo != null &&
- resolveInfo.activityInfo.permission ==
- android.Manifest.permission.START_VIEW_PERMISSION_USAGE) {
+ val resolveInfo =
+ packageManager.resolveActivity(intent, PackageManager.ResolveInfoFlags.of(0))
+ if (
+ resolveInfo?.activityInfo?.permission ==
+ Manifest.permission.START_VIEW_PERMISSION_USAGE
+ ) {
intent.component = ComponentName(packageName, resolveInfo.activityInfo.name)
return intent
}
}
- return getDefaultManageAppPermissionsIntent(packageName, userId)
+ return null
}
fun getDefaultManageAppPermissionsIntent(packageName: String, userId: Int): Intent {
@@ -197,73 +214,84 @@ class PrivacyDialogControllerV2(
* @param context A context to use to create the dialog.
* @see filterAndSelect
*/
- fun showDialog(context: Context) {
+ fun showDialog(context: Context, view: View? = null) {
dismissDialog()
backgroundExecutor.execute {
val usage = permGroupUsage()
val userInfos = userTracker.userProfiles
privacyLogger.logUnfilteredPermGroupUsage(usage)
- val items = usage.mapNotNull {
- val type = filterType(permGroupToPrivacyType(it.permissionGroupName))
- val userInfo = userInfos.firstOrNull { ui -> ui.id == UserHandle.getUserId(it.uid) }
- if (userInfo != null || it.isPhoneCall) {
- type?.let { t ->
+ val items =
+ usage.mapNotNull {
+ val userInfo =
+ userInfos.firstOrNull { ui -> ui.id == UserHandle.getUserId(it.uid) }
+ if (
+ isAvailable(it.permissionGroupName) && (userInfo != null || it.isPhoneCall)
+ ) {
// Only try to get the app name if we actually need it
- val appName = if (it.isPhoneCall) {
- ""
- } else {
- getLabelForPackage(it.packageName, it.uid)
- }
+ val appName =
+ if (it.isPhoneCall) {
+ ""
+ } else {
+ getLabelForPackage(it.packageName, it.uid)
+ }
val userId = UserHandle.getUserId(it.uid)
- PrivacyDialogV2.PrivacyElement(
- t,
+ val viewUsageIntent =
+ getStartViewPermissionUsageIntent(
it.packageName,
- userId,
- appName,
- it.attributionTag,
- it.attributionLabel,
- it.proxyLabel,
- it.lastAccessTimeMillis,
- it.isActive,
- // If there's no user info, we're in a phoneCall in secondary user
- userInfo?.isManagedProfile ?: false,
- it.isPhoneCall,
it.permissionGroupName,
- getManagePermissionIntent(
- it.packageName,
- userId,
- it.permissionGroupName,
- it.attributionTag,
- // attributionLabel is set only when subattribution policies
- // are supported and satisfied
- it.attributionLabel != null
- )
+ it.attributionTag,
+ // attributionLabel is set only when subattribution policies
+ // are supported and satisfied
+ it.attributionLabel != null
+ )
+ PrivacyDialogV2.PrivacyElement(
+ permGroupToPrivacyType(it.permissionGroupName)!!,
+ it.packageName,
+ userId,
+ appName,
+ it.attributionTag,
+ it.attributionLabel,
+ it.proxyLabel,
+ it.lastAccessTimeMillis,
+ it.isActive,
+ it.isPhoneCall,
+ viewUsageIntent != null,
+ it.permissionGroupName,
+ viewUsageIntent
+ ?: getDefaultManageAppPermissionsIntent(it.packageName, userId)
)
+ } else {
+ null
}
- } else {
- // No matching user or phone call
- null
}
- }
uiExecutor.execute {
val elements = filterAndSelect(items)
if (elements.isNotEmpty()) {
- val d = dialogProvider.makeDialog(context, elements, this::startActivity)
+ val d =
+ dialogProvider.makeDialog(
+ context,
+ elements,
+ this::manageApp,
+ this::closeApp,
+ this::openPrivacyDashboard
+ )
d.setShowForAllUsers(true)
d.addOnDismissListener(onDialogDismissed)
- d.show()
+ if (view != null) {
+ dialogLaunchAnimator.showFromView(d, view)
+ } else {
+ d.show()
+ }
privacyLogger.logShowDialogV2Contents(elements)
dialog = d
} else {
- Log.w(TAG, "Trying to show empty dialog")
+ privacyLogger.logEmptyDialog()
}
}
}
}
- /**
- * Dismisses the dialog
- */
+ /** Dismisses the dialog */
fun dismissDialog() {
dialog?.dismiss()
}
@@ -275,7 +303,7 @@ class PrivacyDialogControllerV2(
.getApplicationInfoAsUser(packageName, 0, UserHandle.getUserId(uid))
.loadLabel(packageManager)
} catch (_: PackageManager.NameNotFoundException) {
- Log.w(TAG, "Label not found for: $packageName")
+ privacyLogger.logLabelNotFound(packageName)
packageName
}
}
@@ -289,16 +317,12 @@ class PrivacyDialogControllerV2(
}
}
- private fun filterType(type: PrivacyType?): PrivacyType? {
- return type?.let {
- if ((it == PrivacyType.TYPE_CAMERA || it == PrivacyType.TYPE_MICROPHONE) &&
- privacyItemController.micCameraAvailable) {
- it
- } else if (it == PrivacyType.TYPE_LOCATION && privacyItemController.locationAvailable) {
- it
- } else {
- null
- }
+ private fun isAvailable(group: String): Boolean {
+ return when (group) {
+ Manifest.permission_group.CAMERA -> privacyItemController.micCameraAvailable
+ Manifest.permission_group.MICROPHONE -> privacyItemController.micCameraAvailable
+ Manifest.permission_group.LOCATION -> privacyItemController.locationAvailable
+ else -> false
}
}
@@ -311,16 +335,18 @@ class PrivacyDialogControllerV2(
private fun filterAndSelect(
list: List
): List {
- return list.groupBy { it.type }.toSortedMap().flatMap { (_, elements) ->
- val actives = elements.filter { it.active }
- if (actives.isNotEmpty()) {
- actives.sortedByDescending { it.lastActiveTimestamp }
- } else {
- elements.maxByOrNull { it.lastActiveTimestamp }?.let {
- listOf(it)
- } ?: emptyList()
+ return list
+ .groupBy { it.type }
+ .toSortedMap()
+ .flatMap { (_, elements) ->
+ val actives = elements.filter { it.isActive }
+ if (actives.isNotEmpty()) {
+ actives.sortedByDescending { it.lastActiveTimestamp }
+ } else {
+ elements.maxByOrNull { it.lastActiveTimestamp }?.let { listOf(it) }
+ ?: emptyList()
+ }
}
- }
}
/**
@@ -329,13 +355,13 @@ class PrivacyDialogControllerV2(
* Can be used to inject a mock creator.
*/
interface DialogProvider {
- /**
- * Create a [PrivacyDialogV2].
- */
+ /** Create a [PrivacyDialogV2]. */
fun makeDialog(
context: Context,
list: List,
- starter: (String, Int, CharSequence?, Intent?) -> Unit
+ manageApp: (String, Int, Intent) -> Unit,
+ closeApp: (String, Int) -> Unit,
+ openPrivacyDashboard: () -> Unit
): PrivacyDialogV2
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt
index 3ecc5a5e5b00b..250976cf47a4d 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogEvent.kt
@@ -18,13 +18,20 @@ package com.android.systemui.privacy
import com.android.internal.logging.UiEvent
import com.android.internal.logging.UiEventLogger
+import com.android.internal.logging.UiEventLogger.UiEventEnum.RESERVE_NEW_UI_EVENT_ID
enum class PrivacyDialogEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
@UiEvent(doc = "Privacy dialog is clicked by user to go to the app settings page.")
PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS(904),
@UiEvent(doc = "Privacy dialog is dismissed by user.")
- PRIVACY_DIALOG_DISMISSED(905);
+ PRIVACY_DIALOG_DISMISSED(905),
+
+ @UiEvent(doc = "Privacy dialog item is clicked by user to close the app using a sensor.")
+ PRIVACY_DIALOG_ITEM_CLICKED_TO_CLOSE_APP(1396),
+
+ @UiEvent(doc = "Privacy dialog is clicked by user to see the privacy dashboard.")
+ PRIVACY_DIALOG_CLICK_TO_PRIVACY_DASHBOARD(1397);
override fun getId() = _id
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
index a00775ad0e4bf..f4aa27d5fcbba 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogV2.kt
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 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.
@@ -18,63 +18,58 @@ package com.android.systemui.privacy
import android.content.Context
import android.content.Intent
+import android.content.pm.PackageItemInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.NameNotFoundException
+import android.content.res.Resources.NotFoundException
+import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.os.Bundle
import android.text.TextUtils
+import android.util.Log
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
-import android.view.WindowInsets
+import android.widget.Button
import android.widget.ImageView
import android.widget.TextView
+import androidx.annotation.ColorInt
+import androidx.annotation.DrawableRes
+import androidx.annotation.WorkerThread
+import androidx.core.view.ViewCompat
+import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
import com.android.settingslib.Utils
import com.android.systemui.R
+import com.android.systemui.animation.ViewHierarchyAnimator
import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.maybeForceFullscreen
import java.lang.ref.WeakReference
import java.util.concurrent.atomic.AtomicBoolean
/**
- * Dialog to show ongoing and recent app ops usage.
+ * Dialog to show ongoing and recent app ops element.
*
- * @see PrivacyDialogV2Controller
* @param context A context to create the dialog
* @param list list of elements to show in the dialog. The elements will show in the same order they
- * appear in the list
- * @param activityStarter a callback to start an activity for a given package name, user id, attributionTag and intent
+ * appear in the list
+ * @param manageApp a callback to start an activity for a given package name, user id, and intent
+ * @param closeApp a callback to close an app for a given package name, user id
+ * @param openPrivacyDashboard a callback to open the privacy dashboard
+ * @see PrivacyDialogControllerV2
*/
class PrivacyDialogV2(
context: Context,
private val list: List,
- activityStarter: (String, Int, CharSequence?, Intent?) -> Unit
-) : SystemUIDialog(context, R.style.PrivacyDialog) {
+ private val manageApp: (String, Int, Intent) -> Unit,
+ private val closeApp: (String, Int) -> Unit,
+ private val openPrivacyDashboard: () -> Unit
+) : SystemUIDialog(context, R.style.Theme_PrivacyDialog) {
private val dismissListeners = mutableListOf>()
private val dismissed = AtomicBoolean(false)
-
- private val iconColorSolid = Utils.getColorAttrDefaultColor(
- this.context, com.android.internal.R.attr.colorPrimary
- )
- private val enterpriseText = " ${context.getString(R.string.ongoing_privacy_dialog_enterprise)}"
- private val phonecall = context.getString(R.string.ongoing_privacy_dialog_phonecall)
-
- private lateinit var rootView: ViewGroup
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- window?.apply {
- attributes.fitInsetsTypes = attributes.fitInsetsTypes or WindowInsets.Type.statusBars()
- attributes.receiveInsetsIgnoringZOrder = true
- setGravity(Gravity.TOP or Gravity.CENTER_HORIZONTAL)
- }
- setTitle(R.string.ongoing_privacy_dialog_a11y_title)
- setContentView(R.layout.privacy_dialog)
- rootView = requireViewById(R.id.root)
-
- list.forEach {
- rootView.addView(createView(it))
- }
- }
+ // Note: this will call the dialog create method during init
+ private val decorViewLayoutListener = maybeForceFullscreen()?.component2()
/**
* Add a listener that will be called when the dialog is dismissed.
@@ -98,85 +93,397 @@ class PrivacyDialogV2(
iterator.remove()
el.get()?.onDialogDismissed()
}
+ // Remove the layout change listener we may have added to the DecorView.
+ if (decorViewLayoutListener != null) {
+ window!!.decorView.removeOnLayoutChangeListener(decorViewLayoutListener)
+ }
}
- private fun createView(element: PrivacyElement): View {
- val newView = LayoutInflater.from(context).inflate(
- R.layout.privacy_dialog_item, rootView, false
- ) as ViewGroup
- val d = getDrawableForType(element.type)
- d.findDrawableByLayerId(R.id.icon).setTint(iconColorSolid)
- newView.requireViewById(R.id.icon).apply {
- setImageDrawable(d)
- contentDescription = element.type.getName(context)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ window!!.setGravity(Gravity.CENTER)
+ setTitle(R.string.privacy_dialog_title)
+ setContentView(R.layout.privacy_dialog_v2)
+
+ val closeButton = requireViewById