Loading packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt +6 −5 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.systemui.controls.ui import android.annotation.MainThread import android.app.Dialog import android.app.PendingIntent import android.content.Context import android.content.Intent import android.content.pm.PackageManager Loading Loading @@ -88,7 +89,7 @@ class ControlActionCoordinatorImpl @Inject constructor( bouncerOrRun(createAction(cvh.cws.ci.controlId, { cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK) if (cvh.usePanel()) { showDetail(cvh, control.getAppIntent().getIntent()) showDetail(cvh, control.getAppIntent()) } else { cvh.action(CommandAction(templateId)) } Loading Loading @@ -116,7 +117,7 @@ class ControlActionCoordinatorImpl @Inject constructor( // Long press snould only be called when there is valid control state, otherwise ignore cvh.cws.control?.let { cvh.layout.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) showDetail(cvh, it.getAppIntent().getIntent()) showDetail(cvh, it.getAppIntent()) } }, false /* blockable */)) } Loading Loading @@ -167,10 +168,10 @@ class ControlActionCoordinatorImpl @Inject constructor( bgExecutor.execute { vibrator.vibrate(effect) } } private fun showDetail(cvh: ControlViewHolder, intent: Intent) { private fun showDetail(cvh: ControlViewHolder, pendingIntent: PendingIntent) { bgExecutor.execute { val activities: List<ResolveInfo> = context.packageManager.queryIntentActivities( intent, pendingIntent.getIntent(), PackageManager.MATCH_DEFAULT_ONLY ) Loading @@ -178,7 +179,7 @@ class ControlActionCoordinatorImpl @Inject constructor( // make sure the intent is valid before attempting to open the dialog if (activities.isNotEmpty() && taskViewFactory.isPresent) { taskViewFactory.get().create(context, uiExecutor, { dialog = DetailDialog(activityContext, it, intent, cvh).also { dialog = DetailDialog(activityContext, it, pendingIntent, cvh).also { it.setOnDismissListener { _ -> dialog = null } it.show() } Loading packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt +16 −12 Original line number Diff line number Diff line Loading @@ -43,7 +43,7 @@ import com.android.wm.shell.TaskView class DetailDialog( val activityContext: Context?, val taskView: TaskView, val intent: Intent, val pendingIntent: PendingIntent, val cvh: ControlViewHolder ) : Dialog( activityContext ?: cvh.context, Loading @@ -59,6 +59,14 @@ class DetailDialog( var detailTaskId = INVALID_TASK_ID private val fillInIntent = Intent().apply { putExtra(EXTRA_USE_PANEL, true) // Apply flags to make behaviour match documentLaunchMode=always. addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT) addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) } fun removeDetailTask() { if (detailTaskId == INVALID_TASK_ID) return ActivityTaskManager.getInstance().removeTask(detailTaskId) Loading @@ -67,13 +75,6 @@ class DetailDialog( val stateCallback = object : TaskView.Listener { override fun onInitialized() { val launchIntent = Intent(intent) launchIntent.putExtra(EXTRA_USE_PANEL, true) // Apply flags to make behaviour match documentLaunchMode=always. launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT) launchIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) val options = activityContext?.let { ActivityOptions.makeCustomAnimation( it, Loading @@ -82,9 +83,8 @@ class DetailDialog( ) } ?: ActivityOptions.makeBasic() taskView.startActivity( PendingIntent.getActivity(context, 0, launchIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE), null /* fillInIntent */, pendingIntent, fillInIntent, options, getTaskViewBounds() ) Loading @@ -97,6 +97,9 @@ class DetailDialog( override fun onTaskCreated(taskId: Int, name: ComponentName?) { detailTaskId = taskId requireViewById<ViewGroup>(R.id.controls_activity_view).apply { setAlpha(1f) } } override fun onReleased() { Loading @@ -121,6 +124,7 @@ class DetailDialog( requireViewById<ViewGroup>(R.id.controls_activity_view).apply { addView(taskView) setAlpha(0f) } requireViewById<ImageView>(R.id.control_detail_close).apply { Loading @@ -134,7 +138,7 @@ class DetailDialog( removeDetailTask() dismiss() context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) v.context.startActivity(intent) pendingIntent.send() } } Loading packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt 0 → 100644 +71 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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. */ package com.android.systemui.controls.ui import android.app.PendingIntent import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.wm.shell.TaskView import org.junit.Before 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.verify import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper class DetailDialogTest : SysuiTestCase() { @Mock private lateinit var taskView: TaskView @Mock private lateinit var controlViewHolder: ControlViewHolder @Mock private lateinit var pendingIntent: PendingIntent @Before fun setUp() { MockitoAnnotations.initMocks(this) } @Test fun testPendingIntentIsUnModified() { // GIVEN the dialog is created with a PendingIntent val dialog = createDialog(pendingIntent) // WHEN the TaskView is initialized dialog.stateCallback.onInitialized() // THEN the PendingIntent used to call startActivity is unmodified by systemui verify(taskView).startActivity(eq(pendingIntent), any(), any(), any()) } private fun createDialog(pendingIntent: PendingIntent): DetailDialog { return DetailDialog( mContext, taskView, pendingIntent, controlViewHolder ) } } Loading
packages/SystemUI/src/com/android/systemui/controls/ui/ControlActionCoordinatorImpl.kt +6 −5 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package com.android.systemui.controls.ui import android.annotation.MainThread import android.app.Dialog import android.app.PendingIntent import android.content.Context import android.content.Intent import android.content.pm.PackageManager Loading Loading @@ -88,7 +89,7 @@ class ControlActionCoordinatorImpl @Inject constructor( bouncerOrRun(createAction(cvh.cws.ci.controlId, { cvh.layout.performHapticFeedback(HapticFeedbackConstants.CONTEXT_CLICK) if (cvh.usePanel()) { showDetail(cvh, control.getAppIntent().getIntent()) showDetail(cvh, control.getAppIntent()) } else { cvh.action(CommandAction(templateId)) } Loading Loading @@ -116,7 +117,7 @@ class ControlActionCoordinatorImpl @Inject constructor( // Long press snould only be called when there is valid control state, otherwise ignore cvh.cws.control?.let { cvh.layout.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS) showDetail(cvh, it.getAppIntent().getIntent()) showDetail(cvh, it.getAppIntent()) } }, false /* blockable */)) } Loading Loading @@ -167,10 +168,10 @@ class ControlActionCoordinatorImpl @Inject constructor( bgExecutor.execute { vibrator.vibrate(effect) } } private fun showDetail(cvh: ControlViewHolder, intent: Intent) { private fun showDetail(cvh: ControlViewHolder, pendingIntent: PendingIntent) { bgExecutor.execute { val activities: List<ResolveInfo> = context.packageManager.queryIntentActivities( intent, pendingIntent.getIntent(), PackageManager.MATCH_DEFAULT_ONLY ) Loading @@ -178,7 +179,7 @@ class ControlActionCoordinatorImpl @Inject constructor( // make sure the intent is valid before attempting to open the dialog if (activities.isNotEmpty() && taskViewFactory.isPresent) { taskViewFactory.get().create(context, uiExecutor, { dialog = DetailDialog(activityContext, it, intent, cvh).also { dialog = DetailDialog(activityContext, it, pendingIntent, cvh).also { it.setOnDismissListener { _ -> dialog = null } it.show() } Loading
packages/SystemUI/src/com/android/systemui/controls/ui/DetailDialog.kt +16 −12 Original line number Diff line number Diff line Loading @@ -43,7 +43,7 @@ import com.android.wm.shell.TaskView class DetailDialog( val activityContext: Context?, val taskView: TaskView, val intent: Intent, val pendingIntent: PendingIntent, val cvh: ControlViewHolder ) : Dialog( activityContext ?: cvh.context, Loading @@ -59,6 +59,14 @@ class DetailDialog( var detailTaskId = INVALID_TASK_ID private val fillInIntent = Intent().apply { putExtra(EXTRA_USE_PANEL, true) // Apply flags to make behaviour match documentLaunchMode=always. addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT) addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) } fun removeDetailTask() { if (detailTaskId == INVALID_TASK_ID) return ActivityTaskManager.getInstance().removeTask(detailTaskId) Loading @@ -67,13 +75,6 @@ class DetailDialog( val stateCallback = object : TaskView.Listener { override fun onInitialized() { val launchIntent = Intent(intent) launchIntent.putExtra(EXTRA_USE_PANEL, true) // Apply flags to make behaviour match documentLaunchMode=always. launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT) launchIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK) val options = activityContext?.let { ActivityOptions.makeCustomAnimation( it, Loading @@ -82,9 +83,8 @@ class DetailDialog( ) } ?: ActivityOptions.makeBasic() taskView.startActivity( PendingIntent.getActivity(context, 0, launchIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE), null /* fillInIntent */, pendingIntent, fillInIntent, options, getTaskViewBounds() ) Loading @@ -97,6 +97,9 @@ class DetailDialog( override fun onTaskCreated(taskId: Int, name: ComponentName?) { detailTaskId = taskId requireViewById<ViewGroup>(R.id.controls_activity_view).apply { setAlpha(1f) } } override fun onReleased() { Loading @@ -121,6 +124,7 @@ class DetailDialog( requireViewById<ViewGroup>(R.id.controls_activity_view).apply { addView(taskView) setAlpha(0f) } requireViewById<ImageView>(R.id.control_detail_close).apply { Loading @@ -134,7 +138,7 @@ class DetailDialog( removeDetailTask() dismiss() context.sendBroadcast(Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) v.context.startActivity(intent) pendingIntent.send() } } Loading
packages/SystemUI/tests/src/com/android/systemui/controls/ui/DetailDialogTest.kt 0 → 100644 +71 −0 Original line number Diff line number Diff line /* * Copyright (C) 2021 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. */ package com.android.systemui.controls.ui import android.app.PendingIntent import android.testing.AndroidTestingRunner import android.testing.TestableLooper import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.wm.shell.TaskView import org.junit.Before 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.verify import org.mockito.MockitoAnnotations @SmallTest @RunWith(AndroidTestingRunner::class) @TestableLooper.RunWithLooper class DetailDialogTest : SysuiTestCase() { @Mock private lateinit var taskView: TaskView @Mock private lateinit var controlViewHolder: ControlViewHolder @Mock private lateinit var pendingIntent: PendingIntent @Before fun setUp() { MockitoAnnotations.initMocks(this) } @Test fun testPendingIntentIsUnModified() { // GIVEN the dialog is created with a PendingIntent val dialog = createDialog(pendingIntent) // WHEN the TaskView is initialized dialog.stateCallback.onInitialized() // THEN the PendingIntent used to call startActivity is unmodified by systemui verify(taskView).startActivity(eq(pendingIntent), any(), any(), any()) } private fun createDialog(pendingIntent: PendingIntent): DetailDialog { return DetailDialog( mContext, taskView, pendingIntent, controlViewHolder ) } }