Merge "Add a proxy activity for launch bubble for another user in primary user" into udc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
794e48ab44
@@ -354,6 +354,7 @@ android_library {
|
||||
"androidx.test.uiautomator_uiautomator",
|
||||
"mockito-target-extended-minus-junit4",
|
||||
"androidx.test.ext.junit",
|
||||
"androidx.test.ext.truth",
|
||||
],
|
||||
libs: [
|
||||
"android.test.runner",
|
||||
|
||||
@@ -992,6 +992,16 @@
|
||||
android:excludeFromRecents="true"
|
||||
android:resizeableActivity="false"
|
||||
android:theme="@android:style/Theme.NoDisplay" />
|
||||
|
||||
<!-- LaunchNoteTaskManagedProfileProxyActivity MUST NOT be exported because it allows caller
|
||||
to specify an Android user when launching the default notes app. -->
|
||||
<activity
|
||||
android:name=".notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity"
|
||||
android:exported="false"
|
||||
android:enabled="true"
|
||||
android:excludeFromRecents="true"
|
||||
android:resizeableActivity="false"
|
||||
android:theme="@android:style/Theme.NoDisplay" />
|
||||
<!-- endregion -->
|
||||
|
||||
<!-- started from ControlsRequestReceiver -->
|
||||
|
||||
@@ -40,6 +40,7 @@ import com.android.systemui.devicepolicy.areKeyguardShortcutsDisabled
|
||||
import com.android.systemui.notetask.NoteTaskRoleManagerExt.createNoteShortcutInfoAsUser
|
||||
import com.android.systemui.notetask.NoteTaskRoleManagerExt.getDefaultRoleHolderAsUser
|
||||
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
|
||||
import com.android.systemui.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity
|
||||
import com.android.systemui.settings.UserTracker
|
||||
import com.android.systemui.shared.system.ActivityManagerKt.isInForeground
|
||||
import com.android.systemui.util.kotlin.getOrNull
|
||||
@@ -97,6 +98,18 @@ constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/** Starts [LaunchNoteTaskProxyActivity] on the given [user]. */
|
||||
fun startNoteTaskProxyActivityForUser(user: UserHandle) {
|
||||
context.startActivityAsUser(
|
||||
Intent().apply {
|
||||
component =
|
||||
ComponentName(context, LaunchNoteTaskManagedProfileProxyActivity::class.java)
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
},
|
||||
user
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows a note task. How the task is shown will depend on when the method is invoked.
|
||||
*
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.android.systemui.flags.Flags
|
||||
import com.android.systemui.notetask.quickaffordance.NoteTaskQuickAffordanceModule
|
||||
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
|
||||
import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
|
||||
import com.android.systemui.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
@@ -36,6 +37,9 @@ interface NoteTaskModule {
|
||||
@[Binds IntoMap ClassKey(LaunchNoteTaskActivity::class)]
|
||||
fun LaunchNoteTaskActivity.bindNoteTaskLauncherActivity(): Activity
|
||||
|
||||
@[Binds IntoMap ClassKey(LaunchNoteTaskManagedProfileProxyActivity::class)]
|
||||
fun LaunchNoteTaskManagedProfileProxyActivity.bindNoteTaskLauncherProxyActivity(): Activity
|
||||
|
||||
@[Binds IntoMap ClassKey(CreateNoteTaskShortcutActivity::class)]
|
||||
fun CreateNoteTaskShortcutActivity.bindNoteTaskShortcutActivity(): Activity
|
||||
|
||||
|
||||
@@ -18,10 +18,13 @@ package com.android.systemui.notetask.shortcut
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.UserInfo
|
||||
import android.os.Bundle
|
||||
import android.os.UserManager
|
||||
import androidx.activity.ComponentActivity
|
||||
import com.android.systemui.notetask.NoteTaskController
|
||||
import com.android.systemui.notetask.NoteTaskEntryPoint
|
||||
import com.android.systemui.settings.UserTracker
|
||||
import javax.inject.Inject
|
||||
|
||||
/** Activity responsible for launching the note experience, and finish. */
|
||||
@@ -29,11 +32,43 @@ class LaunchNoteTaskActivity
|
||||
@Inject
|
||||
constructor(
|
||||
private val controller: NoteTaskController,
|
||||
private val userManager: UserManager,
|
||||
private val userTracker: UserTracker,
|
||||
) : ComponentActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
controller.showNoteTask(entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT)
|
||||
|
||||
// Under the hood, notes app shortcuts are shown in a floating window, called Bubble.
|
||||
// Bubble API is only available in the main user but not work profile.
|
||||
//
|
||||
// On devices with work profile (WP), SystemUI provides both personal notes app shortcuts &
|
||||
// work profile notes app shortcuts. In order to make work profile notes app shortcuts to
|
||||
// show in Bubble, a few redirections across users are required:
|
||||
// 1. When `LaunchNoteTaskActivity` is started in the work profile user, we launch
|
||||
// `LaunchNoteTaskManagedProfileProxyActivity` on the main user, which has access to the
|
||||
// Bubble API.
|
||||
// 2. `LaunchNoteTaskManagedProfileProxyActivity` calls `Bubble#showOrHideAppBubble` with
|
||||
// the work profile user ID.
|
||||
// 3. Bubble renders the work profile notes app activity in a floating window, which is
|
||||
// hosted in the main user.
|
||||
//
|
||||
// WP main user
|
||||
// ------------------------ -------------------------------------------
|
||||
// | LaunchNoteTaskActivity | -> | LaunchNoteTaskManagedProfileProxyActivity |
|
||||
// ------------------------ -------------------------------------------
|
||||
// |
|
||||
// main user |
|
||||
// ---------------------------- |
|
||||
// | Bubble#showOrHideAppBubble | <--------------
|
||||
// | (with WP user ID) |
|
||||
// ----------------------------
|
||||
val mainUser: UserInfo? = userTracker.userProfiles.firstOrNull { it.isMain }
|
||||
if (userManager.isManagedProfile && mainUser != null) {
|
||||
controller.startNoteTaskProxyActivityForUser(mainUser.userHandle)
|
||||
} else {
|
||||
controller.showNoteTask(entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT)
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
@@ -43,7 +78,6 @@ constructor(
|
||||
fun newIntent(context: Context): Intent {
|
||||
return Intent(context, LaunchNoteTaskActivity::class.java).apply {
|
||||
// Intent's action must be set in shortcuts, or an exception will be thrown.
|
||||
// TODO(b/254606432): Use Intent.ACTION_CREATE_NOTE instead.
|
||||
action = Intent.ACTION_CREATE_NOTE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.notetask.shortcut
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.UserManager
|
||||
import android.util.Log
|
||||
import androidx.activity.ComponentActivity
|
||||
import com.android.systemui.notetask.NoteTaskController
|
||||
import com.android.systemui.notetask.NoteTaskEntryPoint
|
||||
import com.android.systemui.settings.UserTracker
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* An internal proxy activity that starts notes app in the work profile.
|
||||
*
|
||||
* If there is no work profile, this activity finishes gracefully.
|
||||
*
|
||||
* This activity MUST NOT be exported because that would expose the INTERACT_ACROSS_USER privilege
|
||||
* to any apps.
|
||||
*/
|
||||
class LaunchNoteTaskManagedProfileProxyActivity
|
||||
@Inject
|
||||
constructor(
|
||||
private val controller: NoteTaskController,
|
||||
private val userTracker: UserTracker,
|
||||
private val userManager: UserManager,
|
||||
) : ComponentActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val managedProfileUser =
|
||||
userTracker.userProfiles.firstOrNull { userManager.isManagedProfile(it.id) }
|
||||
|
||||
if (managedProfileUser == null) {
|
||||
logDebug { "Fail to find the work profile user." }
|
||||
} else {
|
||||
controller.showNoteTaskAsUser(
|
||||
entryPoint = NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT,
|
||||
user = managedProfileUser.userHandle
|
||||
)
|
||||
}
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun logDebug(message: () -> String) {
|
||||
if (Build.IS_DEBUGGABLE) {
|
||||
Log.d(NoteTaskController.TAG, message())
|
||||
}
|
||||
}
|
||||
@@ -184,6 +184,18 @@
|
||||
android:exported="false"
|
||||
android:permission="com.android.systemui.permission.SELF"
|
||||
android:excludeFromRecents="true" />
|
||||
|
||||
<activity
|
||||
android:name="com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity"
|
||||
android:exported="false"
|
||||
android:permission="com.android.systemui.permission.SELF"
|
||||
android:excludeFromRecents="true" />
|
||||
|
||||
<activity
|
||||
android:name="com.android.systemui.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity"
|
||||
android:exported="false"
|
||||
android:permission="com.android.systemui.permission.SELF"
|
||||
android:excludeFromRecents="true" />
|
||||
</application>
|
||||
|
||||
<instrumentation android:name="android.testing.TestableInstrumentation"
|
||||
|
||||
@@ -36,6 +36,7 @@ import android.content.pm.ShortcutManager
|
||||
import android.content.pm.UserInfo
|
||||
import android.os.UserHandle
|
||||
import android.os.UserManager
|
||||
import androidx.test.ext.truth.content.IntentSubject.assertThat
|
||||
import androidx.test.filters.SmallTest
|
||||
import androidx.test.runner.AndroidJUnit4
|
||||
import com.android.systemui.R
|
||||
@@ -44,6 +45,7 @@ import com.android.systemui.notetask.NoteTaskController.Companion.EXTRA_SHORTCUT
|
||||
import com.android.systemui.notetask.NoteTaskController.Companion.SHORTCUT_ID
|
||||
import com.android.systemui.notetask.shortcut.CreateNoteTaskShortcutActivity
|
||||
import com.android.systemui.notetask.shortcut.LaunchNoteTaskActivity
|
||||
import com.android.systemui.notetask.shortcut.LaunchNoteTaskManagedProfileProxyActivity
|
||||
import com.android.systemui.settings.FakeUserTracker
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.argumentCaptor
|
||||
@@ -591,6 +593,24 @@ internal class NoteTaskControllerTest : SysuiTestCase() {
|
||||
}
|
||||
// endregion
|
||||
|
||||
// startregion startNoteTaskProxyActivityForUser
|
||||
@Test
|
||||
fun startNoteTaskProxyActivityForUser_shouldStartLaunchNoteTaskProxyActivityWithExpectedUser() {
|
||||
val user0 = UserHandle.of(0)
|
||||
createNoteTaskController().startNoteTaskProxyActivityForUser(user0)
|
||||
|
||||
val intentCaptor = argumentCaptor<Intent>()
|
||||
verify(context).startActivityAsUser(intentCaptor.capture(), eq(user0))
|
||||
intentCaptor.value.let { intent ->
|
||||
assertThat(intent)
|
||||
.hasComponent(
|
||||
ComponentName(context, LaunchNoteTaskManagedProfileProxyActivity::class.java)
|
||||
)
|
||||
assertThat(intent).hasFlags(FLAG_ACTIVITY_NEW_TASK)
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
private companion object {
|
||||
const val NOTE_TASK_SHORT_LABEL = "Notetaking"
|
||||
const val NOTE_TASK_ACTIVITY_NAME = "NoteTaskActivity"
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.notetask.shortcut
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.UserInfo
|
||||
import android.os.UserHandle
|
||||
import android.os.UserManager
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import androidx.test.filters.SmallTest
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import androidx.test.runner.intercepting.SingleActivityFactory
|
||||
import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.notetask.NoteTaskController
|
||||
import com.android.systemui.notetask.NoteTaskEntryPoint
|
||||
import com.android.systemui.settings.FakeUserTracker
|
||||
import com.android.systemui.util.mockito.eq
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@SmallTest
|
||||
@TestableLooper.RunWithLooper
|
||||
class LaunchNoteTaskActivityTest : SysuiTestCase() {
|
||||
|
||||
@Mock lateinit var noteTaskController: NoteTaskController
|
||||
@Mock lateinit var userManager: UserManager
|
||||
private val userTracker: FakeUserTracker = FakeUserTracker()
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val activityRule =
|
||||
ActivityTestRule<LaunchNoteTaskActivity>(
|
||||
/* activityFactory= */ object :
|
||||
SingleActivityFactory<LaunchNoteTaskActivity>(LaunchNoteTaskActivity::class.java) {
|
||||
override fun create(intent: Intent?) =
|
||||
LaunchNoteTaskActivity(
|
||||
controller = noteTaskController,
|
||||
userManager = userManager,
|
||||
userTracker = userTracker
|
||||
)
|
||||
},
|
||||
/* initialTouchMode= */ false,
|
||||
/* launchActivity= */ false,
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
whenever(userManager.isManagedProfile(eq(workProfileUser.id))).thenReturn(true)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
activityRule.finishActivity()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startActivityOnNonWorkProfileUser_shouldLaunchNoteTask() {
|
||||
activityRule.launchActivity(/* startIntent= */ null)
|
||||
|
||||
verify(noteTaskController).showNoteTask(eq(NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startActivityOnWorkProfileUser_shouldLaunchProxyActivity() {
|
||||
userTracker.set(listOf(mainUser, workProfileUser), selectedUserIndex = 1)
|
||||
whenever(userManager.isManagedProfile).thenReturn(true)
|
||||
|
||||
activityRule.launchActivity(/* startIntent= */ null)
|
||||
|
||||
val mainUserHandle: UserHandle = mainUser.userHandle
|
||||
verify(noteTaskController).startNoteTaskProxyActivityForUser(eq(mainUserHandle))
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val mainUser = UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN)
|
||||
val workProfileUser =
|
||||
UserInfo(/* id= */ 10, /* name= */ "work", /* flags= */ UserInfo.FLAG_PROFILE)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* 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.
|
||||
* 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.notetask.shortcut
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.pm.UserInfo
|
||||
import android.os.UserHandle
|
||||
import android.os.UserManager
|
||||
import android.testing.AndroidTestingRunner
|
||||
import android.testing.TestableLooper
|
||||
import androidx.test.filters.SmallTest
|
||||
import androidx.test.rule.ActivityTestRule
|
||||
import androidx.test.runner.intercepting.SingleActivityFactory
|
||||
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
|
||||
import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
|
||||
import com.android.systemui.SysuiTestCase
|
||||
import com.android.systemui.notetask.NoteTaskController
|
||||
import com.android.systemui.notetask.NoteTaskEntryPoint
|
||||
import com.android.systemui.settings.FakeUserTracker
|
||||
import com.android.systemui.util.mockito.any
|
||||
import com.android.systemui.util.mockito.eq
|
||||
import com.android.systemui.util.mockito.whenever
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.mockito.Mock
|
||||
import org.mockito.MockitoAnnotations
|
||||
|
||||
@RunWith(AndroidTestingRunner::class)
|
||||
@SmallTest
|
||||
@TestableLooper.RunWithLooper
|
||||
class LaunchNoteTaskManagedProfileProxyActivityTest : SysuiTestCase() {
|
||||
|
||||
@Mock lateinit var noteTaskController: NoteTaskController
|
||||
@Mock lateinit var userManager: UserManager
|
||||
private val userTracker = FakeUserTracker()
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val activityRule =
|
||||
ActivityTestRule<LaunchNoteTaskManagedProfileProxyActivity>(
|
||||
/* activityFactory= */ object :
|
||||
SingleActivityFactory<LaunchNoteTaskManagedProfileProxyActivity>(
|
||||
LaunchNoteTaskManagedProfileProxyActivity::class.java
|
||||
) {
|
||||
override fun create(intent: Intent?) =
|
||||
LaunchNoteTaskManagedProfileProxyActivity(
|
||||
controller = noteTaskController,
|
||||
userManager = userManager,
|
||||
userTracker = userTracker
|
||||
)
|
||||
},
|
||||
/* initialTouchMode= */ false,
|
||||
/* launchActivity= */ false,
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
MockitoAnnotations.initMocks(this)
|
||||
whenever(userManager.isManagedProfile(eq(workProfileUser.id))).thenReturn(true)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
activityRule.finishActivity()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startActivity_noWorkProfileUser_shouldNotLaunchNoteTask() {
|
||||
userTracker.set(listOf(mainUser), selectedUserIndex = 0)
|
||||
activityRule.launchActivity(/* startIntent= */ null)
|
||||
|
||||
verify(noteTaskController, never()).showNoteTaskAsUser(any(), any())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startActivity_hasWorkProfileUser_shouldLaunchNoteTaskOnTheWorkProfileUser() {
|
||||
userTracker.set(mainAndWorkProfileUsers, mainAndWorkProfileUsers.indexOf(mainUser))
|
||||
activityRule.launchActivity(/* startIntent= */ null)
|
||||
|
||||
val workProfileUserHandle: UserHandle = workProfileUser.userHandle
|
||||
verify(noteTaskController)
|
||||
.showNoteTaskAsUser(
|
||||
eq(NoteTaskEntryPoint.WIDGET_PICKER_SHORTCUT),
|
||||
eq(workProfileUserHandle)
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val mainUser = UserInfo(/* id= */ 0, /* name= */ "primary", /* flags= */ UserInfo.FLAG_MAIN)
|
||||
val workProfileUser =
|
||||
UserInfo(/* id= */ 10, /* name= */ "work", /* flags= */ UserInfo.FLAG_PROFILE)
|
||||
val mainAndWorkProfileUsers = listOf(mainUser, workProfileUser)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user