Loading packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt +15 −43 Original line number Diff line number Diff line Loading @@ -18,26 +18,25 @@ package com.android.systemui.controls.ui import android.app.Activity import android.app.ActivityOptions import android.app.ActivityTaskManager import android.app.ActivityTaskManager.INVALID_TASK_ID import android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED import android.app.Dialog import android.app.PendingIntent import android.content.ComponentName import android.content.Context import android.content.Intent import android.graphics.Rect import android.view.View import android.view.ViewGroup import android.view.WindowInsets import android.view.WindowInsets.Type import android.view.WindowManager import android.widget.ImageView import androidx.annotation.VisibleForTesting import com.android.internal.policy.ScreenDecorationsUtils import com.android.systemui.res.R import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.boundsOnScreen import com.android.wm.shell.taskview.TaskView /** Loading Loading @@ -65,8 +64,8 @@ class DetailDialog( private const val EXTRA_USE_PANEL = "controls.DISPLAY_IN_PANEL" } var detailTaskId = INVALID_TASK_ID private lateinit var taskViewContainer: View private lateinit var controlDetailRoot: View private val taskWidthPercentWidth = activityContext.resources.getFloat( R.dimen.controls_task_view_width_percentage ) Loading @@ -79,12 +78,7 @@ class DetailDialog( addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) } fun removeDetailTask() { if (detailTaskId == INVALID_TASK_ID) return ActivityTaskManager.getInstance().removeTask(detailTaskId) detailTaskId = INVALID_TASK_ID } @VisibleForTesting val stateCallback = object : TaskView.Listener { override fun onInitialized() { taskViewContainer.apply { Loading @@ -98,33 +92,29 @@ class DetailDialog( activityContext, 0 /* enterResId */, 0 /* exitResId */ ).setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED) options.isPendingIntentBackgroundActivityLaunchAllowedByPermission = true ).apply { pendingIntentBackgroundActivityStartMode = MODE_BACKGROUND_ACTIVITY_START_ALLOWED isPendingIntentBackgroundActivityLaunchAllowedByPermission = true taskAlwaysOnTop = true } taskView.startActivity( pendingIntent, fillInIntent, options, getTaskViewBounds() taskView.boundsOnScreen, ) } override fun onTaskRemovalStarted(taskId: Int) { detailTaskId = INVALID_TASK_ID dismiss() taskView.release() } override fun onTaskCreated(taskId: Int, name: ComponentName?) { detailTaskId = taskId requireViewById<ViewGroup>(R.id.controls_activity_view).apply { setAlpha(1f) } } override fun onReleased() { removeDetailTask() } override fun onBackPressedOnTaskRoot(taskId: Int) { dismiss() } Loading @@ -138,6 +128,9 @@ class DetailDialog( setContentView(R.layout.controls_detail_dialog) taskViewContainer = requireViewById<ViewGroup>(R.id.control_task_view_container) controlDetailRoot = requireViewById<View>(R.id.control_detail_root).apply { setOnClickListener { _: View -> dismiss() } } requireViewById<ViewGroup>(R.id.controls_activity_view).apply { addView(taskView) Loading @@ -147,13 +140,9 @@ class DetailDialog( requireViewById<ImageView>(R.id.control_detail_close).apply { setOnClickListener { _: View -> dismiss() } } requireViewById<View>(R.id.control_detail_root).apply { setOnClickListener { _: View -> dismiss() } } requireViewById<ImageView>(R.id.control_detail_open_in_app).apply { setOnClickListener { v: View -> removeDetailTask() dismiss() val action = ActivityStarter.OnDismissAction { Loading Loading @@ -201,26 +190,9 @@ class DetailDialog( taskView.setListener(cvh.uiExecutor, stateCallback) } fun getTaskViewBounds(): Rect { val wm = checkNotNull(context.getSystemService(WindowManager::class.java)) val windowMetrics = wm.getCurrentWindowMetrics() val rect = windowMetrics.bounds val metricInsets = windowMetrics.windowInsets val insets = metricInsets.getInsetsIgnoringVisibility(Type.systemBars() or Type.displayCutout()) val headerHeight = context.resources.getDimensionPixelSize( R.dimen.controls_detail_dialog_header_height) val finalRect = Rect(rect.left - insets.left /* left */, rect.top + insets.top + headerHeight /* top */, rect.right - insets.right /* right */, rect.bottom - insets.bottom /* bottom */) return finalRect } override fun dismiss() { if (!isShowing()) return taskView.release() taskView.removeTask() val isActivityFinishing = (activityContext as? Activity)?.let { it.isFinishing || it.isDestroyed } Loading packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt +0 −6 Original line number Diff line number Diff line Loading @@ -18,7 +18,6 @@ package com.android.systemui.controls.ui import android.app.ActivityOptions import android.app.ActivityTaskManager.INVALID_TASK_ID import android.app.PendingIntent import android.content.ComponentName import android.content.Context Loading @@ -45,8 +44,6 @@ class PanelTaskViewController( taskView.alpha = 0f } private var detailTaskId = INVALID_TASK_ID private val fillInIntent = Intent().apply { // Apply flags to make behaviour match documentLaunchMode=always. Loading @@ -57,7 +54,6 @@ class PanelTaskViewController( private val stateCallback = object : TaskView.Listener { override fun onInitialized() { val options = ActivityOptions.makeCustomAnimation( activityContext, Loading Loading @@ -88,12 +84,10 @@ class PanelTaskViewController( } override fun onTaskRemovalStarted(taskId: Int) { detailTaskId = INVALID_TASK_ID release() } override fun onTaskCreated(taskId: Int, name: ComponentName?) { detailTaskId = taskId taskView.alpha = 1f } Loading packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt +49 −23 Original line number Diff line number Diff line Loading @@ -18,10 +18,13 @@ package com.android.systemui.controls.ui import android.app.ActivityOptions import android.app.PendingIntent import android.content.Context import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.activity.EmptyTestActivity import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.policy.KeyguardStateController Loading @@ -30,11 +33,13 @@ import com.android.systemui.util.mockito.capture import com.android.wm.shell.taskview.TaskView import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations Loading @@ -43,31 +48,31 @@ import org.mockito.MockitoAnnotations @TestableLooper.RunWithLooper class DetailDialogTest : SysuiTestCase() { @Mock private lateinit var taskView: TaskView @Mock private lateinit var broadcastSender: BroadcastSender @Mock private lateinit var controlViewHolder: ControlViewHolder @Mock private lateinit var pendingIntent: PendingIntent @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var activityStarter: ActivityStarter @Rule @JvmField val activityRule: ActivityScenarioRule<EmptyTestActivity> = ActivityScenarioRule(EmptyTestActivity::class.java) @Mock private lateinit var taskView: TaskView @Mock private lateinit var broadcastSender: BroadcastSender @Mock private lateinit var controlViewHolder: ControlViewHolder @Mock private lateinit var pendingIntent: PendingIntent @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var activityStarter: ActivityStarter private lateinit var underTest: DetailDialog @Before fun setUp() { MockitoAnnotations.initMocks(this) underTest = createDialog(pendingIntent) } @Test fun testPendingIntentIsUnModified() { // GIVEN the dialog is created with a PendingIntent val dialog = createDialog(pendingIntent) // WHEN the TaskView is initialized dialog.stateCallback.onInitialized() underTest.stateCallback.onInitialized() // THEN the PendingIntent used to call startActivity is unmodified by systemui verify(taskView).startActivity(eq(pendingIntent), any(), any(), any()) Loading @@ -75,11 +80,8 @@ class DetailDialogTest : SysuiTestCase() { @Test fun testActivityOptionsAllowBal() { // GIVEN the dialog is created with a PendingIntent val dialog = createDialog(pendingIntent) // WHEN the TaskView is initialized dialog.stateCallback.onInitialized() underTest.stateCallback.onInitialized() val optionsCaptor = argumentCaptor<ActivityOptions>() Loading @@ -90,17 +92,41 @@ class DetailDialogTest : SysuiTestCase() { .isEqualTo(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) assertThat(optionsCaptor.value.isPendingIntentBackgroundActivityLaunchAllowedByPermission) .isTrue() assertThat(optionsCaptor.value.taskAlwaysOnTop).isTrue() } @Test fun testDismissRemovesTheTask() { activityRule.scenario.onActivity { underTest = createDialog(pendingIntent, it) underTest.show() underTest.dismiss() verify(taskView).removeTask() verify(taskView, never()).release() } } @Test fun testTaskRemovalReleasesTaskView() { underTest.stateCallback.onTaskRemovalStarted(0) verify(taskView).release() } private fun createDialog(pendingIntent: PendingIntent): DetailDialog { private fun createDialog( pendingIntent: PendingIntent, context: Context = mContext, ): DetailDialog { return DetailDialog( mContext, context, broadcastSender, taskView, pendingIntent, controlViewHolder, keyguardStateController, activityStarter activityStarter, ) } } Loading
packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt +15 −43 Original line number Diff line number Diff line Loading @@ -18,26 +18,25 @@ package com.android.systemui.controls.ui import android.app.Activity import android.app.ActivityOptions import android.app.ActivityTaskManager import android.app.ActivityTaskManager.INVALID_TASK_ID import android.app.ComponentOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED import android.app.Dialog import android.app.PendingIntent import android.content.ComponentName import android.content.Context import android.content.Intent import android.graphics.Rect import android.view.View import android.view.ViewGroup import android.view.WindowInsets import android.view.WindowInsets.Type import android.view.WindowManager import android.widget.ImageView import androidx.annotation.VisibleForTesting import com.android.internal.policy.ScreenDecorationsUtils import com.android.systemui.res.R import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.policy.KeyguardStateController import com.android.systemui.util.boundsOnScreen import com.android.wm.shell.taskview.TaskView /** Loading Loading @@ -65,8 +64,8 @@ class DetailDialog( private const val EXTRA_USE_PANEL = "controls.DISPLAY_IN_PANEL" } var detailTaskId = INVALID_TASK_ID private lateinit var taskViewContainer: View private lateinit var controlDetailRoot: View private val taskWidthPercentWidth = activityContext.resources.getFloat( R.dimen.controls_task_view_width_percentage ) Loading @@ -79,12 +78,7 @@ class DetailDialog( addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) } fun removeDetailTask() { if (detailTaskId == INVALID_TASK_ID) return ActivityTaskManager.getInstance().removeTask(detailTaskId) detailTaskId = INVALID_TASK_ID } @VisibleForTesting val stateCallback = object : TaskView.Listener { override fun onInitialized() { taskViewContainer.apply { Loading @@ -98,33 +92,29 @@ class DetailDialog( activityContext, 0 /* enterResId */, 0 /* exitResId */ ).setPendingIntentBackgroundActivityStartMode(MODE_BACKGROUND_ACTIVITY_START_ALLOWED) options.isPendingIntentBackgroundActivityLaunchAllowedByPermission = true ).apply { pendingIntentBackgroundActivityStartMode = MODE_BACKGROUND_ACTIVITY_START_ALLOWED isPendingIntentBackgroundActivityLaunchAllowedByPermission = true taskAlwaysOnTop = true } taskView.startActivity( pendingIntent, fillInIntent, options, getTaskViewBounds() taskView.boundsOnScreen, ) } override fun onTaskRemovalStarted(taskId: Int) { detailTaskId = INVALID_TASK_ID dismiss() taskView.release() } override fun onTaskCreated(taskId: Int, name: ComponentName?) { detailTaskId = taskId requireViewById<ViewGroup>(R.id.controls_activity_view).apply { setAlpha(1f) } } override fun onReleased() { removeDetailTask() } override fun onBackPressedOnTaskRoot(taskId: Int) { dismiss() } Loading @@ -138,6 +128,9 @@ class DetailDialog( setContentView(R.layout.controls_detail_dialog) taskViewContainer = requireViewById<ViewGroup>(R.id.control_task_view_container) controlDetailRoot = requireViewById<View>(R.id.control_detail_root).apply { setOnClickListener { _: View -> dismiss() } } requireViewById<ViewGroup>(R.id.controls_activity_view).apply { addView(taskView) Loading @@ -147,13 +140,9 @@ class DetailDialog( requireViewById<ImageView>(R.id.control_detail_close).apply { setOnClickListener { _: View -> dismiss() } } requireViewById<View>(R.id.control_detail_root).apply { setOnClickListener { _: View -> dismiss() } } requireViewById<ImageView>(R.id.control_detail_open_in_app).apply { setOnClickListener { v: View -> removeDetailTask() dismiss() val action = ActivityStarter.OnDismissAction { Loading Loading @@ -201,26 +190,9 @@ class DetailDialog( taskView.setListener(cvh.uiExecutor, stateCallback) } fun getTaskViewBounds(): Rect { val wm = checkNotNull(context.getSystemService(WindowManager::class.java)) val windowMetrics = wm.getCurrentWindowMetrics() val rect = windowMetrics.bounds val metricInsets = windowMetrics.windowInsets val insets = metricInsets.getInsetsIgnoringVisibility(Type.systemBars() or Type.displayCutout()) val headerHeight = context.resources.getDimensionPixelSize( R.dimen.controls_detail_dialog_header_height) val finalRect = Rect(rect.left - insets.left /* left */, rect.top + insets.top + headerHeight /* top */, rect.right - insets.right /* right */, rect.bottom - insets.bottom /* bottom */) return finalRect } override fun dismiss() { if (!isShowing()) return taskView.release() taskView.removeTask() val isActivityFinishing = (activityContext as? Activity)?.let { it.isFinishing || it.isDestroyed } Loading
packages/SystemUI/src/com/android/systemui/controls/ui/PanelTaskViewController.kt +0 −6 Original line number Diff line number Diff line Loading @@ -18,7 +18,6 @@ package com.android.systemui.controls.ui import android.app.ActivityOptions import android.app.ActivityTaskManager.INVALID_TASK_ID import android.app.PendingIntent import android.content.ComponentName import android.content.Context Loading @@ -45,8 +44,6 @@ class PanelTaskViewController( taskView.alpha = 0f } private var detailTaskId = INVALID_TASK_ID private val fillInIntent = Intent().apply { // Apply flags to make behaviour match documentLaunchMode=always. Loading @@ -57,7 +54,6 @@ class PanelTaskViewController( private val stateCallback = object : TaskView.Listener { override fun onInitialized() { val options = ActivityOptions.makeCustomAnimation( activityContext, Loading Loading @@ -88,12 +84,10 @@ class PanelTaskViewController( } override fun onTaskRemovalStarted(taskId: Int) { detailTaskId = INVALID_TASK_ID release() } override fun onTaskCreated(taskId: Int, name: ComponentName?) { detailTaskId = taskId taskView.alpha = 1f } Loading
packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt +49 −23 Original line number Diff line number Diff line Loading @@ -18,10 +18,13 @@ package com.android.systemui.controls.ui import android.app.ActivityOptions import android.app.PendingIntent import android.content.Context import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.ext.junit.rules.ActivityScenarioRule import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.activity.EmptyTestActivity import com.android.systemui.broadcast.BroadcastSender import com.android.systemui.plugins.ActivityStarter import com.android.systemui.statusbar.policy.KeyguardStateController Loading @@ -30,11 +33,13 @@ import com.android.systemui.util.mockito.capture import com.android.wm.shell.taskview.TaskView import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.eq import org.mockito.Mock import org.mockito.Mockito.any import org.mockito.Mockito.never import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations Loading @@ -43,31 +48,31 @@ import org.mockito.MockitoAnnotations @TestableLooper.RunWithLooper class DetailDialogTest : SysuiTestCase() { @Mock private lateinit var taskView: TaskView @Mock private lateinit var broadcastSender: BroadcastSender @Mock private lateinit var controlViewHolder: ControlViewHolder @Mock private lateinit var pendingIntent: PendingIntent @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var activityStarter: ActivityStarter @Rule @JvmField val activityRule: ActivityScenarioRule<EmptyTestActivity> = ActivityScenarioRule(EmptyTestActivity::class.java) @Mock private lateinit var taskView: TaskView @Mock private lateinit var broadcastSender: BroadcastSender @Mock private lateinit var controlViewHolder: ControlViewHolder @Mock private lateinit var pendingIntent: PendingIntent @Mock private lateinit var keyguardStateController: KeyguardStateController @Mock private lateinit var activityStarter: ActivityStarter private lateinit var underTest: DetailDialog @Before fun setUp() { MockitoAnnotations.initMocks(this) underTest = createDialog(pendingIntent) } @Test fun testPendingIntentIsUnModified() { // GIVEN the dialog is created with a PendingIntent val dialog = createDialog(pendingIntent) // WHEN the TaskView is initialized dialog.stateCallback.onInitialized() underTest.stateCallback.onInitialized() // THEN the PendingIntent used to call startActivity is unmodified by systemui verify(taskView).startActivity(eq(pendingIntent), any(), any(), any()) Loading @@ -75,11 +80,8 @@ class DetailDialogTest : SysuiTestCase() { @Test fun testActivityOptionsAllowBal() { // GIVEN the dialog is created with a PendingIntent val dialog = createDialog(pendingIntent) // WHEN the TaskView is initialized dialog.stateCallback.onInitialized() underTest.stateCallback.onInitialized() val optionsCaptor = argumentCaptor<ActivityOptions>() Loading @@ -90,17 +92,41 @@ class DetailDialogTest : SysuiTestCase() { .isEqualTo(ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) assertThat(optionsCaptor.value.isPendingIntentBackgroundActivityLaunchAllowedByPermission) .isTrue() assertThat(optionsCaptor.value.taskAlwaysOnTop).isTrue() } @Test fun testDismissRemovesTheTask() { activityRule.scenario.onActivity { underTest = createDialog(pendingIntent, it) underTest.show() underTest.dismiss() verify(taskView).removeTask() verify(taskView, never()).release() } } @Test fun testTaskRemovalReleasesTaskView() { underTest.stateCallback.onTaskRemovalStarted(0) verify(taskView).release() } private fun createDialog(pendingIntent: PendingIntent): DetailDialog { private fun createDialog( pendingIntent: PendingIntent, context: Context = mContext, ): DetailDialog { return DetailDialog( mContext, context, broadcastSender, taskView, pendingIntent, controlViewHolder, keyguardStateController, activityStarter activityStarter, ) } }