Loading packages/SystemUI/aconfig/systemui.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -983,6 +983,16 @@ flag { } } flag { name: "use_notif_inflation_thread_for_row" namespace: "systemui" description: "use the @NotifInflation thread for ExpandableNotificationRow inflation" bug: "375320642" metadata { purpose: PURPOSE_BUGFIX } } flag { name: "notify_power_manager_user_activity_background" namespace: "systemui" Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AsyncRowInflater.kt 0 → 100644 +90 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.statusbar.notification.row import android.content.Context import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.annotation.LayoutRes import androidx.annotation.UiThread import com.android.app.tracing.coroutines.launchTraced import com.android.app.tracing.coroutines.withContextTraced import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dagger.qualifiers.NotifInflation import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @SysUISingleton class AsyncRowInflater @Inject constructor( @Application private val applicationScope: CoroutineScope, @Main private val mainCoroutineDispatcher: CoroutineDispatcher, @NotifInflation private val inflationCoroutineDispatcher: CoroutineDispatcher, ) { /** * Inflate the layout on the background thread, and invoke the listener on the main thread when * finished. * * If the inflation fails on the background, it will be retried once on the main thread. */ @UiThread fun inflate( context: Context, layoutFactory: LayoutInflater.Factory2, @LayoutRes resId: Int, parent: ViewGroup, listener: OnInflateFinishedListener, ): Job { val inflater = BasicRowInflater(context).apply { factory2 = layoutFactory } return applicationScope.launchTraced("AsyncRowInflater-bg", inflationCoroutineDispatcher) { val view = try { inflater.inflate(resId, parent, false) } catch (ex: RuntimeException) { // Probably a Looper failure, retry on the UI thread Log.w( "AsyncRowInflater", "Failed to inflate resource in the background!" + " Retrying on the UI thread", ex, ) null } withContextTraced("AsyncRowInflater-ui", mainCoroutineDispatcher) { // If the inflate failed on the inflation thread, try again on the main thread val finalView = view ?: inflater.inflate(resId, parent, false) // Inform the listener of the completion listener.onInflateFinished(finalView, resId, parent) } } } /** * Callback interface (identical to the one from AsyncLayoutInflater) for receiving the inflated * view */ interface OnInflateFinishedListener { @UiThread fun onInflateFinished(view: View, @LayoutRes resId: Int, parent: ViewGroup?) } } packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BasicRowInflater.kt 0 → 100644 +51 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.statusbar.notification.row import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater import android.view.View /** * A [LayoutInflater] that is copy of * [androidx.asynclayoutinflater.view.AsyncLayoutInflater.BasicInflater] */ internal class BasicRowInflater(context: Context) : LayoutInflater(context) { override fun cloneInContext(newContext: Context): LayoutInflater { return BasicRowInflater(newContext) } @Throws(ClassNotFoundException::class) override fun onCreateView(name: String, attrs: AttributeSet): View { for (prefix in sClassPrefixList) { try { val view = createView(name, prefix, attrs) if (view != null) { return view } } catch (e: ClassNotFoundException) { // In this case we want to let the base class take a crack at it. } } return super.onCreateView(name, attrs) } companion object { private val sClassPrefixList = arrayOf("android.widget.", "android.webkit.", "android.app.") } } packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java +14 −37 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import androidx.annotation.VisibleForTesting; import androidx.asynclayoutinflater.view.AsyncLayoutFactory; import androidx.asynclayoutinflater.view.AsyncLayoutInflater; import com.android.systemui.Flags; import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.InflationTask; Loading @@ -44,7 +45,8 @@ import javax.inject.Inject; /** * An inflater task that asynchronously inflates a ExpandableNotificationRow */ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInflateFinishedListener { public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInflateFinishedListener, AsyncRowInflater.OnInflateFinishedListener { private static final String TAG = "RowInflaterTask"; private static final boolean TRACE_ORIGIN = true; Loading @@ -55,15 +57,17 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf private Throwable mInflateOrigin; private final SystemClock mSystemClock; private final RowInflaterTaskLogger mLogger; private final AsyncRowInflater mAsyncRowInflater; private long mInflateStartTimeMs; private UserTracker mUserTracker; @Inject public RowInflaterTask(SystemClock systemClock, RowInflaterTaskLogger logger, UserTracker userTracker) { UserTracker userTracker, AsyncRowInflater asyncRowInflater) { mSystemClock = systemClock; mLogger = logger; mUserTracker = userTracker; mAsyncRowInflater = asyncRowInflater; } /** Loading @@ -87,14 +91,20 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf mInflateOrigin = new Throwable("inflate requested here"); } mListener = listener; AsyncLayoutInflater inflater = new AsyncLayoutInflater(context, makeRowInflater(entry)); RowAsyncLayoutInflater asyncLayoutFactory = makeRowInflater(entry); mEntry = entry; entry.setInflationTask(this); mLogger.logInflateStart(entry); mInflateStartTimeMs = mSystemClock.elapsedRealtime(); if (Flags.useNotifInflationThreadForRow()) { mAsyncRowInflater.inflate(context, asyncLayoutFactory, R.layout.status_bar_notification_row, parent, this); } else { AsyncLayoutInflater inflater = new AsyncLayoutInflater(context, asyncLayoutFactory); inflater.inflate(R.layout.status_bar_notification_row, parent, listenerExecutor, this); } } /** * Inflates a new notificationView synchronously. Loading @@ -117,39 +127,6 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf entry, mSystemClock, mLogger, mUserTracker.getUserHandle()); } /** * A {@link LayoutInflater} that is copy of BasicLayoutInflater. */ private static class BasicRowInflater extends LayoutInflater { private static final String[] sClassPrefixList = {"android.widget.", "android.webkit.", "android.app."}; BasicRowInflater(Context context) { super(context); } @Override public LayoutInflater cloneInContext(Context newContext) { return new BasicRowInflater(newContext); } @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { for (String prefix : sClassPrefixList) { try { View view = createView(name, prefix, attrs); if (view != null) { return view; } } catch (ClassNotFoundException e) { // In this case we want to let the base class take a crack // at it. } } return super.onCreateView(name, attrs); } } @VisibleForTesting public static class RowAsyncLayoutInflater implements AsyncLayoutFactory { private final NotificationEntry mEntry; Loading packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt +2 −1 Original line number Diff line number Diff line Loading @@ -354,7 +354,8 @@ class ExpandableNotificationRowBuilder( RowInflaterTask( mFakeSystemClock, Mockito.mock(RowInflaterTaskLogger::class.java, STUB_ONLY), userTracker userTracker, Mockito.mock(AsyncRowInflater::class.java, STUB_ONLY), ) val row = rowInflaterTask.inflateSynchronously(context, null, entry) Loading Loading
packages/SystemUI/aconfig/systemui.aconfig +10 −0 Original line number Diff line number Diff line Loading @@ -983,6 +983,16 @@ flag { } } flag { name: "use_notif_inflation_thread_for_row" namespace: "systemui" description: "use the @NotifInflation thread for ExpandableNotificationRow inflation" bug: "375320642" metadata { purpose: PURPOSE_BUGFIX } } flag { name: "notify_power_manager_user_activity_background" namespace: "systemui" Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/row/AsyncRowInflater.kt 0 → 100644 +90 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.statusbar.notification.row import android.content.Context import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.annotation.LayoutRes import androidx.annotation.UiThread import com.android.app.tracing.coroutines.launchTraced import com.android.app.tracing.coroutines.withContextTraced import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Main import com.android.systemui.dagger.qualifiers.NotifInflation import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job @SysUISingleton class AsyncRowInflater @Inject constructor( @Application private val applicationScope: CoroutineScope, @Main private val mainCoroutineDispatcher: CoroutineDispatcher, @NotifInflation private val inflationCoroutineDispatcher: CoroutineDispatcher, ) { /** * Inflate the layout on the background thread, and invoke the listener on the main thread when * finished. * * If the inflation fails on the background, it will be retried once on the main thread. */ @UiThread fun inflate( context: Context, layoutFactory: LayoutInflater.Factory2, @LayoutRes resId: Int, parent: ViewGroup, listener: OnInflateFinishedListener, ): Job { val inflater = BasicRowInflater(context).apply { factory2 = layoutFactory } return applicationScope.launchTraced("AsyncRowInflater-bg", inflationCoroutineDispatcher) { val view = try { inflater.inflate(resId, parent, false) } catch (ex: RuntimeException) { // Probably a Looper failure, retry on the UI thread Log.w( "AsyncRowInflater", "Failed to inflate resource in the background!" + " Retrying on the UI thread", ex, ) null } withContextTraced("AsyncRowInflater-ui", mainCoroutineDispatcher) { // If the inflate failed on the inflation thread, try again on the main thread val finalView = view ?: inflater.inflate(resId, parent, false) // Inform the listener of the completion listener.onInflateFinished(finalView, resId, parent) } } } /** * Callback interface (identical to the one from AsyncLayoutInflater) for receiving the inflated * view */ interface OnInflateFinishedListener { @UiThread fun onInflateFinished(view: View, @LayoutRes resId: Int, parent: ViewGroup?) } }
packages/SystemUI/src/com/android/systemui/statusbar/notification/row/BasicRowInflater.kt 0 → 100644 +51 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 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.statusbar.notification.row import android.content.Context import android.util.AttributeSet import android.view.LayoutInflater import android.view.View /** * A [LayoutInflater] that is copy of * [androidx.asynclayoutinflater.view.AsyncLayoutInflater.BasicInflater] */ internal class BasicRowInflater(context: Context) : LayoutInflater(context) { override fun cloneInContext(newContext: Context): LayoutInflater { return BasicRowInflater(newContext) } @Throws(ClassNotFoundException::class) override fun onCreateView(name: String, attrs: AttributeSet): View { for (prefix in sClassPrefixList) { try { val view = createView(name, prefix, attrs) if (view != null) { return view } } catch (e: ClassNotFoundException) { // In this case we want to let the base class take a crack at it. } } return super.onCreateView(name, attrs) } companion object { private val sClassPrefixList = arrayOf("android.widget.", "android.webkit.", "android.app.") } }
packages/SystemUI/src/com/android/systemui/statusbar/notification/row/RowInflaterTask.java +14 −37 Original line number Diff line number Diff line Loading @@ -30,6 +30,7 @@ import androidx.annotation.VisibleForTesting; import androidx.asynclayoutinflater.view.AsyncLayoutFactory; import androidx.asynclayoutinflater.view.AsyncLayoutInflater; import com.android.systemui.Flags; import com.android.systemui.res.R; import com.android.systemui.settings.UserTracker; import com.android.systemui.statusbar.InflationTask; Loading @@ -44,7 +45,8 @@ import javax.inject.Inject; /** * An inflater task that asynchronously inflates a ExpandableNotificationRow */ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInflateFinishedListener { public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInflateFinishedListener, AsyncRowInflater.OnInflateFinishedListener { private static final String TAG = "RowInflaterTask"; private static final boolean TRACE_ORIGIN = true; Loading @@ -55,15 +57,17 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf private Throwable mInflateOrigin; private final SystemClock mSystemClock; private final RowInflaterTaskLogger mLogger; private final AsyncRowInflater mAsyncRowInflater; private long mInflateStartTimeMs; private UserTracker mUserTracker; @Inject public RowInflaterTask(SystemClock systemClock, RowInflaterTaskLogger logger, UserTracker userTracker) { UserTracker userTracker, AsyncRowInflater asyncRowInflater) { mSystemClock = systemClock; mLogger = logger; mUserTracker = userTracker; mAsyncRowInflater = asyncRowInflater; } /** Loading @@ -87,14 +91,20 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf mInflateOrigin = new Throwable("inflate requested here"); } mListener = listener; AsyncLayoutInflater inflater = new AsyncLayoutInflater(context, makeRowInflater(entry)); RowAsyncLayoutInflater asyncLayoutFactory = makeRowInflater(entry); mEntry = entry; entry.setInflationTask(this); mLogger.logInflateStart(entry); mInflateStartTimeMs = mSystemClock.elapsedRealtime(); if (Flags.useNotifInflationThreadForRow()) { mAsyncRowInflater.inflate(context, asyncLayoutFactory, R.layout.status_bar_notification_row, parent, this); } else { AsyncLayoutInflater inflater = new AsyncLayoutInflater(context, asyncLayoutFactory); inflater.inflate(R.layout.status_bar_notification_row, parent, listenerExecutor, this); } } /** * Inflates a new notificationView synchronously. Loading @@ -117,39 +127,6 @@ public class RowInflaterTask implements InflationTask, AsyncLayoutInflater.OnInf entry, mSystemClock, mLogger, mUserTracker.getUserHandle()); } /** * A {@link LayoutInflater} that is copy of BasicLayoutInflater. */ private static class BasicRowInflater extends LayoutInflater { private static final String[] sClassPrefixList = {"android.widget.", "android.webkit.", "android.app."}; BasicRowInflater(Context context) { super(context); } @Override public LayoutInflater cloneInContext(Context newContext) { return new BasicRowInflater(newContext); } @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException { for (String prefix : sClassPrefixList) { try { View view = createView(name, prefix, attrs); if (view != null) { return view; } } catch (ClassNotFoundException e) { // In this case we want to let the base class take a crack // at it. } } return super.onCreateView(name, attrs); } } @VisibleForTesting public static class RowAsyncLayoutInflater implements AsyncLayoutFactory { private final NotificationEntry mEntry; Loading
packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt +2 −1 Original line number Diff line number Diff line Loading @@ -354,7 +354,8 @@ class ExpandableNotificationRowBuilder( RowInflaterTask( mFakeSystemClock, Mockito.mock(RowInflaterTaskLogger::class.java, STUB_ONLY), userTracker userTracker, Mockito.mock(AsyncRowInflater::class.java, STUB_ONLY), ) val row = rowInflaterTask.inflateSynchronously(context, null, entry) Loading