summaryrefslogtreecommitdiff
path: root/java/src
diff options
context:
space:
mode:
author Mark Renouf <mrenouf@google.com> 2024-04-03 20:27:47 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2024-04-03 20:27:47 +0000
commitfd12143a9536509117ce3b78ff4d358d210a96ab (patch)
tree7d542d8123c425286e747492c26239c52f97d3c6 /java/src
parent931f6f7500af03b0ccbc1842983f655230441181 (diff)
parent8e7e81138fc72d5df530cb77032f9aae1f18cb2c (diff)
Merge changes from topic "ir_user_repo_flows" into main
* changes: Simplify startup and Profile flow handling Add @Broadcast Handler, broadcastFlow() -> BroadcastSubscriber
Diffstat (limited to 'java/src')
-rw-r--r--java/src/com/android/intentresolver/inject/ConcurrencyModule.kt29
-rw-r--r--java/src/com/android/intentresolver/inject/Qualifiers.kt2
-rw-r--r--java/src/com/android/intentresolver/v2/ChooserActivity.java19
-rw-r--r--java/src/com/android/intentresolver/v2/ChooserHelper.kt40
-rw-r--r--java/src/com/android/intentresolver/v2/ProfileAvailability.kt23
-rw-r--r--java/src/com/android/intentresolver/v2/ProfileHelper.kt19
-rw-r--r--java/src/com/android/intentresolver/v2/data/BroadcastFlow.kt46
-rw-r--r--java/src/com/android/intentresolver/v2/data/BroadcastSubscriber.kt73
-rw-r--r--java/src/com/android/intentresolver/v2/data/repository/UserRepository.kt60
9 files changed, 177 insertions, 134 deletions
diff --git a/java/src/com/android/intentresolver/inject/ConcurrencyModule.kt b/java/src/com/android/intentresolver/inject/ConcurrencyModule.kt
index e0f8e88b..5fbdf090 100644
--- a/java/src/com/android/intentresolver/inject/ConcurrencyModule.kt
+++ b/java/src/com/android/intentresolver/inject/ConcurrencyModule.kt
@@ -16,6 +16,10 @@
package com.android.intentresolver.inject
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.Looper
+import android.os.Process
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@@ -26,6 +30,10 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
+// thread
+private const val BROADCAST_SLOW_DISPATCH_THRESHOLD = 1000L
+private const val BROADCAST_SLOW_DELIVERY_THRESHOLD = 1000L
+
@Module
@InstallIn(SingletonComponent::class)
object ConcurrencyModule {
@@ -40,4 +48,25 @@ object ConcurrencyModule {
CoroutineScope(SupervisorJob() + mainDispatcher)
@Provides @Background fun backgroundDispatcher(): CoroutineDispatcher = Dispatchers.IO
+
+ @Provides
+ @Singleton
+ @Broadcast
+ fun provideBroadcastLooper(): Looper {
+ val thread = HandlerThread("BroadcastReceiver", Process.THREAD_PRIORITY_BACKGROUND)
+ thread.start()
+ thread.looper.setSlowLogThresholdMs(
+ BROADCAST_SLOW_DISPATCH_THRESHOLD,
+ BROADCAST_SLOW_DELIVERY_THRESHOLD
+ )
+ return thread.looper
+ }
+
+ /** Provide a BroadcastReceiver Executor (for sending and receiving broadcasts). */
+ @Provides
+ @Singleton
+ @Broadcast
+ fun provideBroadcastHandler(@Broadcast looper: Looper): Handler {
+ return Handler(looper)
+ }
}
diff --git a/java/src/com/android/intentresolver/inject/Qualifiers.kt b/java/src/com/android/intentresolver/inject/Qualifiers.kt
index f267328b..77315cac 100644
--- a/java/src/com/android/intentresolver/inject/Qualifiers.kt
+++ b/java/src/com/android/intentresolver/inject/Qualifiers.kt
@@ -35,6 +35,8 @@ annotation class ApplicationOwned
@Retention(AnnotationRetention.RUNTIME)
annotation class ApplicationUser
+@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class Broadcast
+
@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class ProfileParent
@Qualifier @MustBeDocumented @Retention(AnnotationRetention.RUNTIME) annotation class Background
diff --git a/java/src/com/android/intentresolver/v2/ChooserActivity.java b/java/src/com/android/intentresolver/v2/ChooserActivity.java
index d624c9e4..5f3129f8 100644
--- a/java/src/com/android/intentresolver/v2/ChooserActivity.java
+++ b/java/src/com/android/intentresolver/v2/ChooserActivity.java
@@ -124,6 +124,7 @@ import com.android.intentresolver.emptystate.EmptyState;
import com.android.intentresolver.emptystate.EmptyStateProvider;
import com.android.intentresolver.grid.ChooserGridAdapter;
import com.android.intentresolver.icons.TargetDataLoader;
+import com.android.intentresolver.inject.Background;
import com.android.intentresolver.logging.EventLog;
import com.android.intentresolver.measurements.Tracer;
import com.android.intentresolver.model.AbstractResolverComparator;
@@ -185,6 +186,8 @@ import java.util.function.Supplier;
import javax.inject.Inject;
+import kotlinx.coroutines.CoroutineDispatcher;
+
/**
* The Chooser Activity handles intent resolution specifically for sharing intents -
* for example, as generated by {@see android.content.Intent#createChooser(Intent, CharSequence)}.
@@ -265,6 +268,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
private static final int SCROLL_STATUS_SCROLLING_HORIZONTAL = 2;
@Inject public UserInteractor mUserInteractor;
+ @Inject @Background public CoroutineDispatcher mBackgroundDispatcher;
@Inject public ChooserHelper mChooserHelper;
@Inject public FeatureFlags mFeatureFlags;
@Inject public android.service.chooser.FeatureFlags mChooserServiceFeatureFlags;
@@ -352,7 +356,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
setTheme(R.style.Theme_DeviceDefault_Chooser);
// Initializer is invoked when this function returns, via Lifecycle.
- mChooserHelper.setInitializer(this::initializeWith);
+ mChooserHelper.setInitializer(this::initialize);
if (mChooserServiceFeatureFlags.chooserPayloadToggling()) {
mChooserHelper.setOnChooserRequestChanged(this::onChooserRequestChanged);
}
@@ -467,8 +471,7 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
}
/** DO NOT CALL. Only for use from ChooserHelper as a callback. */
- private void initializeWith(InitialState initialState) {
- Log.d(TAG, "initializeWith: " + initialState);
+ private void initialize() {
mViewModel = new ViewModelProvider(this).get(ChooserViewModel.class);
mRequest = mViewModel.getRequest().getValue();
@@ -476,14 +479,14 @@ public class ChooserActivity extends Hilt_ChooserActivity implements
mProfiles = new ProfileHelper(
mUserInteractor,
- mFeatureFlags,
- initialState.getProfiles(),
- initialState.getLaunchedAs());
+ getCoroutineScope(getLifecycle()),
+ mBackgroundDispatcher,
+ mFeatureFlags);
mProfileAvailability = new ProfileAvailability(
- getCoroutineScope(getLifecycle()),
mUserInteractor,
- initialState.getAvailability());
+ getCoroutineScope(getLifecycle()),
+ mBackgroundDispatcher);
mProfileAvailability.setOnProfileStatusChange(this::onWorkProfileStatusUpdated);
diff --git a/java/src/com/android/intentresolver/v2/ChooserHelper.kt b/java/src/com/android/intentresolver/v2/ChooserHelper.kt
index 503e46d8..9da0d605 100644
--- a/java/src/com/android/intentresolver/v2/ChooserHelper.kt
+++ b/java/src/com/android/intentresolver/v2/ChooserHelper.kt
@@ -31,7 +31,6 @@ import com.android.intentresolver.inject.Background
import com.android.intentresolver.v2.annotation.JavaInterop
import com.android.intentresolver.v2.data.model.ChooserRequest
import com.android.intentresolver.v2.domain.interactor.UserInteractor
-import com.android.intentresolver.v2.shared.model.Profile
import com.android.intentresolver.v2.ui.viewmodel.ChooserViewModel
import com.android.intentresolver.v2.validation.Invalid
import com.android.intentresolver.v2.validation.Valid
@@ -43,35 +42,10 @@ import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
-import kotlinx.coroutines.runBlocking
private const val TAG: String = "ChooserHelper"
/**
- * Provides initial values to ChooserActivity and completes initialization from onCreate.
- *
- * This information is collected and provided on behalf of ChooserActivity to eliminate the need for
- * suspending functions within remaining synchronous startup code.
- */
-@JavaInterop
-fun interface ChooserInitializer {
- /** @param initialState the initial state to provide to initialization */
- fun initializeWith(initialState: InitialState)
-}
-
-/**
- * A parameter object for Initialize which contains all the values which are required "early", on
- * the main thread and outside of any coroutines. This supports code which expects to be called by
- * the system on the main thread only. (This includes everything originally called from onCreate).
- */
-@JavaInterop
-data class InitialState(
- val profiles: List<Profile>,
- val availability: Map<Profile, Boolean>,
- val launchedAs: Profile
-)
-
-/**
* __Purpose__
*
* Cleanup aid. Provides a pathway to cleaner code.
@@ -113,7 +87,7 @@ constructor(
private val activity: ComponentActivity = hostActivity as ComponentActivity
private val viewModel by activity.viewModels<ChooserViewModel>()
- private lateinit var activityInitializer: ChooserInitializer
+ private lateinit var activityInitializer: Runnable
var onChooserRequestChanged: Consumer<ChooserRequest> = Consumer {}
@@ -126,7 +100,7 @@ constructor(
*
* This _must_ be called from [ChooserActivity.onCreate].
*/
- fun setInitializer(initializer: ChooserInitializer) {
+ fun setInitializer(initializer: Runnable) {
check(activity.lifecycle.currentState == Lifecycle.State.INITIALIZED) {
"setInitializer must be called before onCreate returns"
}
@@ -189,14 +163,6 @@ constructor(
private fun initializeActivity(request: Valid<ChooserRequest>) {
request.warnings.forEach { it.log(TAG) }
-
- val initialState =
- runBlocking(background) {
- val initialProfiles = userInteractor.profiles.first()
- val initialAvailability = userInteractor.availability.first()
- val launchedAsProfile = userInteractor.launchedAsProfile.first()
- InitialState(initialProfiles, initialAvailability, launchedAsProfile)
- }
- activityInitializer.initializeWith(initialState)
+ activityInitializer.run()
}
}
diff --git a/java/src/com/android/intentresolver/v2/ProfileAvailability.kt b/java/src/com/android/intentresolver/v2/ProfileAvailability.kt
index ddb57991..27d8c6bb 100644
--- a/java/src/com/android/intentresolver/v2/ProfileAvailability.kt
+++ b/java/src/com/android/intentresolver/v2/ProfileAvailability.kt
@@ -16,27 +16,26 @@
package com.android.intentresolver.v2
+import androidx.annotation.MainThread
import com.android.intentresolver.v2.annotation.JavaInterop
import com.android.intentresolver.v2.domain.interactor.UserInteractor
import com.android.intentresolver.v2.shared.model.Profile
+import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
-import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
/** Provides availability status for profiles */
@JavaInterop
class ProfileAvailability(
- private val scope: CoroutineScope,
private val userInteractor: UserInteractor,
- initialState: Map<Profile, Boolean>
+ private val scope: CoroutineScope,
+ private val background: CoroutineDispatcher,
) {
- private val availability =
- userInteractor.availability.stateIn(scope, SharingStarted.Eagerly, initialState)
-
/** Used by WorkProfilePausedEmptyStateProvider */
var waitingToEnableProfile = false
private set
@@ -45,8 +44,14 @@ class ProfileAvailability(
var onProfileStatusChange: Runnable? = null
private var waitJob: Job? = null
+
/** Query current profile availability. An unavailable profile is one which is not active. */
- fun isAvailable(profile: Profile) = availability.value[profile] ?: false
+ @MainThread
+ fun isAvailable(profile: Profile): Boolean {
+ return runBlocking(background) {
+ userInteractor.availability.map { it[profile] == true }.first()
+ }
+ }
/** Used by WorkProfilePausedEmptyStateProvider */
fun requestQuietModeState(profile: Profile, quietMode: Boolean) {
@@ -65,7 +70,7 @@ class ProfileAvailability(
val job =
scope.launch {
// Wait for the profile to become available
- availability.filter { it[profile] == true }.first()
+ userInteractor.availability.filter { it[profile] == true }.first()
}
job.invokeOnCompletion {
waitingToEnableProfile = false
diff --git a/java/src/com/android/intentresolver/v2/ProfileHelper.kt b/java/src/com/android/intentresolver/v2/ProfileHelper.kt
index 8a8e6b54..87948150 100644
--- a/java/src/com/android/intentresolver/v2/ProfileHelper.kt
+++ b/java/src/com/android/intentresolver/v2/ProfileHelper.kt
@@ -17,29 +17,40 @@
package com.android.intentresolver.v2
import android.os.UserHandle
+import androidx.annotation.MainThread
import com.android.intentresolver.inject.IntentResolverFlags
import com.android.intentresolver.v2.annotation.JavaInterop
import com.android.intentresolver.v2.domain.interactor.UserInteractor
import com.android.intentresolver.v2.shared.model.Profile
import com.android.intentresolver.v2.shared.model.User
import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.runBlocking
@JavaInterop
+@MainThread
class ProfileHelper
@Inject
constructor(
interactor: UserInteractor,
+ private val scope: CoroutineScope,
+ private val background: CoroutineDispatcher,
private val flags: IntentResolverFlags,
- val profiles: List<Profile>,
- val launchedAsProfile: Profile,
) {
private val launchedByHandle: UserHandle = interactor.launchedAs
+ val launchedAsProfile by lazy {
+ runBlocking(background) { interactor.launchedAsProfile.first() }
+ }
+ val profiles by lazy { runBlocking(background) { interactor.profiles.first() } }
+
// Map UserHandle back to a user within launchedByProfile
- private val launchedByUser =
+ private val launchedByUser: User =
when (launchedByHandle) {
launchedAsProfile.primary.handle -> launchedAsProfile.primary
- launchedAsProfile.clone?.handle -> launchedAsProfile.clone
+ launchedAsProfile.clone?.handle -> requireNotNull(launchedAsProfile.clone)
else -> error("launchedByUser must be a member of launchedByProfile")
}
val launchedAsProfileType: Profile.Type = launchedAsProfile.type
diff --git a/java/src/com/android/intentresolver/v2/data/BroadcastFlow.kt b/java/src/com/android/intentresolver/v2/data/BroadcastFlow.kt
deleted file mode 100644
index 1a58afcb..00000000
--- a/java/src/com/android/intentresolver/v2/data/BroadcastFlow.kt
+++ /dev/null
@@ -1,46 +0,0 @@
-package com.android.intentresolver.v2.data
-
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import android.os.UserHandle
-import android.util.Log
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.channels.onFailure
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.callbackFlow
-
-private const val TAG = "BroadcastFlow"
-
-/**
- * Returns a [callbackFlow] that, when collected, registers a broadcast receiver and emits a new
- * value whenever broadcast matching _filter_ is received. The result value will be computed using
- * [transform] and emitted if non-null.
- */
-internal fun <T> broadcastFlow(
- context: Context,
- filter: IntentFilter,
- user: UserHandle,
- transform: (Intent) -> T?
-): Flow<T> = callbackFlow {
- val receiver =
- object : BroadcastReceiver() {
- override fun onReceive(context: Context, intent: Intent) {
- transform(intent)?.also { result ->
- trySend(result).onFailure { Log.e(TAG, "Failed to send $result", it) }
- }
- ?: Log.w(TAG, "Ignored broadcast $intent")
- }
- }
-
- context.registerReceiverAsUser(
- receiver,
- user,
- IntentFilter(filter),
- null,
- null,
- Context.RECEIVER_NOT_EXPORTED
- )
- awaitClose { context.unregisterReceiver(receiver) }
-}
diff --git a/java/src/com/android/intentresolver/v2/data/BroadcastSubscriber.kt b/java/src/com/android/intentresolver/v2/data/BroadcastSubscriber.kt
new file mode 100644
index 00000000..f3013246
--- /dev/null
+++ b/java/src/com/android/intentresolver/v2/data/BroadcastSubscriber.kt
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2024 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.intentresolver.v2.data
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.Handler
+import android.os.UserHandle
+import android.util.Log
+import com.android.intentresolver.inject.Broadcast
+import dagger.hilt.android.qualifiers.ApplicationContext
+import javax.inject.Inject
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.channels.onFailure
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+
+private const val TAG = "BroadcastSubscriber"
+
+class BroadcastSubscriber
+@Inject
+constructor(
+ @ApplicationContext private val context: Context,
+ @Broadcast private val handler: Handler
+) {
+ /**
+ * Returns a [callbackFlow] that, when collected, registers a broadcast receiver and emits a new
+ * value whenever broadcast matching _filter_ is received. The result value will be computed
+ * using [transform] and emitted if non-null.
+ */
+ fun <T> createFlow(
+ filter: IntentFilter,
+ user: UserHandle,
+ transform: (Intent) -> T?,
+ ): Flow<T> = callbackFlow {
+ val receiver =
+ object : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ transform(intent)?.also { result ->
+ trySend(result).onFailure { Log.e(TAG, "Failed to send $result", it) }
+ }
+ ?: Log.w(TAG, "Ignored broadcast $intent")
+ }
+ }
+
+ @Suppress("MissingPermission")
+ context.registerReceiverAsUser(
+ receiver,
+ user,
+ IntentFilter(filter),
+ null,
+ handler,
+ Context.RECEIVER_NOT_EXPORTED
+ )
+ awaitClose { context.unregisterReceiver(receiver) }
+ }
+}
diff --git a/java/src/com/android/intentresolver/v2/data/repository/UserRepository.kt b/java/src/com/android/intentresolver/v2/data/repository/UserRepository.kt
index 40672249..d196d3e6 100644
--- a/java/src/com/android/intentresolver/v2/data/repository/UserRepository.kt
+++ b/java/src/com/android/intentresolver/v2/data/repository/UserRepository.kt
@@ -16,7 +16,6 @@
package com.android.intentresolver.v2.data.repository
-import android.content.Context
import android.content.Intent
import android.content.Intent.ACTION_MANAGED_PROFILE_AVAILABLE
import android.content.Intent.ACTION_MANAGED_PROFILE_UNAVAILABLE
@@ -36,9 +35,8 @@ import androidx.annotation.VisibleForTesting
import com.android.intentresolver.inject.Background
import com.android.intentresolver.inject.Main
import com.android.intentresolver.inject.ProfileParent
-import com.android.intentresolver.v2.data.broadcastFlow
+import com.android.intentresolver.v2.data.BroadcastSubscriber
import com.android.intentresolver.v2.shared.model.User
-import dagger.hilt.android.qualifiers.ApplicationContext
import javax.inject.Inject
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
@@ -88,6 +86,23 @@ internal data class UserWithState(val user: User, val available: Boolean)
internal typealias UserStates = List<UserWithState>
+internal val userBroadcastActions =
+ setOf(
+ ACTION_PROFILE_ADDED,
+ ACTION_PROFILE_REMOVED,
+
+ // Quiet mode enabled/disabled for managed
+ // From: UserController.broadcastProfileAvailabilityChanges
+ // In response to setQuietModeEnabled
+ ACTION_MANAGED_PROFILE_AVAILABLE, // quiet mode, sent for manage profiles only
+ ACTION_MANAGED_PROFILE_UNAVAILABLE, // quiet mode, sent for manage profiles only
+
+ // Quiet mode toggled for profile type, requires flag 'android.os.allow_private_profile
+ // true'
+ ACTION_PROFILE_AVAILABLE, // quiet mode,
+ ACTION_PROFILE_UNAVAILABLE, // quiet mode, sent for any profile type
+ )
+
/** Tracks and publishes state for the parent user and associated profiles. */
class UserRepositoryImpl
@VisibleForTesting
@@ -97,21 +112,26 @@ constructor(
/** A flow of events which represent user-state changes from [UserManager]. */
private val userEvents: Flow<UserEvent>,
scope: CoroutineScope,
- private val backgroundDispatcher: CoroutineDispatcher
+ private val backgroundDispatcher: CoroutineDispatcher,
) : UserRepository {
@Inject
constructor(
- @ApplicationContext context: Context,
@ProfileParent profileParent: UserHandle,
userManager: UserManager,
@Main scope: CoroutineScope,
- @Background background: CoroutineDispatcher
+ @Background background: CoroutineDispatcher,
+ broadcastSubscriber: BroadcastSubscriber,
) : this(
profileParent,
userManager,
- userEvents = userBroadcastFlow(context, profileParent),
+ userEvents =
+ broadcastSubscriber.createFlow(
+ createFilter(userBroadcastActions),
+ profileParent,
+ Intent::toUserEvent
+ ),
scope,
- background
+ background,
)
private fun debugLog(msg: () -> String) {
@@ -264,7 +284,7 @@ data class UnknownEvent(
) : UserEvent
/** Used with [broadcastFlow] to transform a UserManager broadcast action into a [UserEvent]. */
-private fun Intent.toUserEvent(): UserEvent {
+internal fun Intent.toUserEvent(): UserEvent {
val action = action
val user = extras?.getParcelable(EXTRA_USER, UserHandle::class.java)
val quietMode = extras?.getBoolean(EXTRA_QUIET_MODE, false)
@@ -280,30 +300,10 @@ private fun Intent.toUserEvent(): UserEvent {
}
}
-private fun createFilter(actions: Iterable<String>): IntentFilter {
+internal fun createFilter(actions: Iterable<String>): IntentFilter {
return IntentFilter().apply { actions.forEach(::addAction) }
}
internal fun UserInfo?.isAvailable(): Boolean {
return this?.isQuietModeEnabled != true
}
-
-internal fun userBroadcastFlow(context: Context, profileParent: UserHandle): Flow<UserEvent> {
- val userActions =
- setOf(
- ACTION_PROFILE_ADDED,
- ACTION_PROFILE_REMOVED,
-
- // Quiet mode enabled/disabled for managed
- // From: UserController.broadcastProfileAvailabilityChanges
- // In response to setQuietModeEnabled
- ACTION_MANAGED_PROFILE_AVAILABLE, // quiet mode, sent for manage profiles only
- ACTION_MANAGED_PROFILE_UNAVAILABLE, // quiet mode, sent for manage profiles only
-
- // Quiet mode toggled for profile type, requires flag 'android.os.allow_private_profile
- // true'
- ACTION_PROFILE_AVAILABLE, // quiet mode,
- ACTION_PROFILE_UNAVAILABLE, // quiet mode, sent for any profile type
- )
- return broadcastFlow(context, createFilter(userActions), profileParent, Intent::toUserEvent)
-}