Loading packages/SystemUI/res/layout/status_bar_notification_row.xml +5 −2 Original line number Diff line number Diff line Loading @@ -40,7 +40,10 @@ <com.android.systemui.statusbar.notification.row.NotificationContentView android:id="@+id/expanded" android:layout_width="match_parent" android:layout_height="wrap_content" /> android:layout_height="wrap_content" android:minHeight="@dimen/notification_content_min_height" android:gravity="center_vertical" /> <com.android.systemui.statusbar.notification.row.NotificationContentView android:id="@+id/expandedPublic" Loading packages/SystemUI/res/values/dimens.xml +9 −0 Original line number Diff line number Diff line Loading @@ -184,6 +184,15 @@ <!-- Height of a small notification in the status bar--> <dimen name="notification_min_height">@*android:dimen/notification_min_height</dimen> <!-- Minimum allowed height of notifications --> <dimen name="notification_validation_minimum_allowed_height">10dp</dimen> <!-- Minimum height for displaying notification content. --> <dimen name="notification_content_min_height">48dp</dimen> <!-- Reference width used when validating notification layouts --> <dimen name="notification_validation_reference_width">320dp</dimen> <!-- Increased height of a small notification in the status bar --> <dimen name="notification_min_height_increased">146dp</dimen> Loading packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +72 −0 Original line number Diff line number Diff line Loading @@ -28,8 +28,11 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.AsyncTask; import android.os.Build; import android.os.CancellationSignal; import android.os.Trace; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.util.Log; Loading @@ -38,6 +41,7 @@ import android.widget.RemoteViews; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ImageMessageConsumer; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.media.controls.util.MediaFeatureFlag; Loading Loading @@ -468,6 +472,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder result.packageContext, parentLayout, remoteViewClickHandler); validateView(v, entry, row.getResources()); v.setIsRootNamespace(true); applyCallback.setResultView(v); } else { Loading @@ -475,6 +480,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder result.packageContext, existingView, remoteViewClickHandler); validateView(existingView, entry, row.getResources()); existingWrapper.onReinflated(); } } catch (Exception e) { Loading @@ -496,6 +502,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder @Override public void onViewApplied(View v) { String invalidReason = isValidView(v, entry, row.getResources()); if (invalidReason != null) { handleInflationError(runningInflations, new InflationException(invalidReason), row.getEntry(), callback); runningInflations.remove(inflationId); return; } if (isNewView) { v.setIsRootNamespace(true); applyCallback.setResultView(v); Loading Loading @@ -553,6 +566,65 @@ public class NotificationContentInflater implements NotificationRowContentBinder runningInflations.put(inflationId, cancellationSignal); } /** * Checks if the given View is a valid notification View. * * @return null == valid, non-null == invalid, String represents reason for rejection. */ @VisibleForTesting @Nullable static String isValidView(View view, NotificationEntry entry, Resources resources) { if (!satisfiesMinHeightRequirement(view, entry, resources)) { return "inflated notification does not meet minimum height requirement"; } return null; } private static boolean satisfiesMinHeightRequirement(View view, NotificationEntry entry, Resources resources) { if (!requiresHeightCheck(entry)) { return true; } Trace.beginSection("NotificationContentInflater#satisfiesMinHeightRequirement"); int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); int referenceWidth = resources.getDimensionPixelSize( R.dimen.notification_validation_reference_width); int widthSpec = View.MeasureSpec.makeMeasureSpec(referenceWidth, View.MeasureSpec.EXACTLY); view.measure(widthSpec, heightSpec); int minHeight = resources.getDimensionPixelSize( R.dimen.notification_validation_minimum_allowed_height); boolean result = view.getMeasuredHeight() >= minHeight; Trace.endSection(); return result; } private static boolean requiresHeightCheck(NotificationEntry entry) { // Undecorated custom views are disallowed from S onwards if (entry.targetSdk >= Build.VERSION_CODES.S) { return false; } // No need to check if the app isn't using any custom views Notification notification = entry.getSbn().getNotification(); if (notification.contentView == null && notification.bigContentView == null && notification.headsUpContentView == null) { return false; } return true; } private static void validateView(View view, NotificationEntry entry, Resources resources) throws InflationException { String invalidReason = isValidView(view, entry, resources); if (invalidReason != null) { throw new InflationException(invalidReason); } } private static void handleInflationError( HashMap<Integer, CancellationSignal> runningInflations, Exception e, NotificationEntry notification, @Nullable InflationCallback callback) { Loading packages/SystemUI/tests/res/layout/invalid_notification_height.xml 0 → 100644 +18 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- 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. --> <View xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="5dp"/> No newline at end of file packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +34 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import static com.android.systemui.statusbar.notification.row.NotificationRowCon import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; Loading @@ -42,6 +43,7 @@ import android.os.Looper; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.widget.RemoteViews; Loading Loading @@ -332,6 +334,38 @@ public class NotificationContentInflaterTest extends SysuiTestCase { eq(FLAG_CONTENT_VIEW_HEADS_UP)); } @Test public void testNotificationViewHeightTooSmallFailsValidation() { View view = mock(View.class); when(view.getHeight()) .thenReturn((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, mContext.getResources().getDisplayMetrics())); String result = NotificationContentInflater.isValidView(view, mRow.getEntry(), mContext.getResources()); assertNotNull(result); } @Test public void testNotificationViewPassesValidation() { View view = mock(View.class); when(view.getHeight()) .thenReturn((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 17, mContext.getResources().getDisplayMetrics())); String result = NotificationContentInflater.isValidView(view, mRow.getEntry(), mContext.getResources()); assertNull(result); } @Test public void testInvalidNotificationDoesNotInvokeCallback() throws Exception { mRow.getPrivateLayout().removeAllViews(); mRow.getEntry().getSbn().getNotification().contentView = new RemoteViews(mContext.getPackageName(), R.layout.invalid_notification_height); inflateAndWait(true, mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow); assertEquals(0, mRow.getPrivateLayout().getChildCount()); verify(mRow, times(0)).onNotificationUpdated(); } private static void inflateAndWait(NotificationContentInflater inflater, @InflationFlag int contentToInflate, ExpandableNotificationRow row) Loading Loading
packages/SystemUI/res/layout/status_bar_notification_row.xml +5 −2 Original line number Diff line number Diff line Loading @@ -40,7 +40,10 @@ <com.android.systemui.statusbar.notification.row.NotificationContentView android:id="@+id/expanded" android:layout_width="match_parent" android:layout_height="wrap_content" /> android:layout_height="wrap_content" android:minHeight="@dimen/notification_content_min_height" android:gravity="center_vertical" /> <com.android.systemui.statusbar.notification.row.NotificationContentView android:id="@+id/expandedPublic" Loading
packages/SystemUI/res/values/dimens.xml +9 −0 Original line number Diff line number Diff line Loading @@ -184,6 +184,15 @@ <!-- Height of a small notification in the status bar--> <dimen name="notification_min_height">@*android:dimen/notification_min_height</dimen> <!-- Minimum allowed height of notifications --> <dimen name="notification_validation_minimum_allowed_height">10dp</dimen> <!-- Minimum height for displaying notification content. --> <dimen name="notification_content_min_height">48dp</dimen> <!-- Reference width used when validating notification layouts --> <dimen name="notification_validation_reference_width">320dp</dimen> <!-- Increased height of a small notification in the status bar --> <dimen name="notification_min_height_increased">146dp</dimen> Loading
packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +72 −0 Original line number Diff line number Diff line Loading @@ -28,8 +28,11 @@ import android.content.Context; import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.Resources; import android.os.AsyncTask; import android.os.Build; import android.os.CancellationSignal; import android.os.Trace; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.util.Log; Loading @@ -38,6 +41,7 @@ import android.widget.RemoteViews; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.widget.ImageMessageConsumer; import com.android.systemui.R; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.media.controls.util.MediaFeatureFlag; Loading Loading @@ -468,6 +472,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder result.packageContext, parentLayout, remoteViewClickHandler); validateView(v, entry, row.getResources()); v.setIsRootNamespace(true); applyCallback.setResultView(v); } else { Loading @@ -475,6 +480,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder result.packageContext, existingView, remoteViewClickHandler); validateView(existingView, entry, row.getResources()); existingWrapper.onReinflated(); } } catch (Exception e) { Loading @@ -496,6 +502,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder @Override public void onViewApplied(View v) { String invalidReason = isValidView(v, entry, row.getResources()); if (invalidReason != null) { handleInflationError(runningInflations, new InflationException(invalidReason), row.getEntry(), callback); runningInflations.remove(inflationId); return; } if (isNewView) { v.setIsRootNamespace(true); applyCallback.setResultView(v); Loading Loading @@ -553,6 +566,65 @@ public class NotificationContentInflater implements NotificationRowContentBinder runningInflations.put(inflationId, cancellationSignal); } /** * Checks if the given View is a valid notification View. * * @return null == valid, non-null == invalid, String represents reason for rejection. */ @VisibleForTesting @Nullable static String isValidView(View view, NotificationEntry entry, Resources resources) { if (!satisfiesMinHeightRequirement(view, entry, resources)) { return "inflated notification does not meet minimum height requirement"; } return null; } private static boolean satisfiesMinHeightRequirement(View view, NotificationEntry entry, Resources resources) { if (!requiresHeightCheck(entry)) { return true; } Trace.beginSection("NotificationContentInflater#satisfiesMinHeightRequirement"); int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); int referenceWidth = resources.getDimensionPixelSize( R.dimen.notification_validation_reference_width); int widthSpec = View.MeasureSpec.makeMeasureSpec(referenceWidth, View.MeasureSpec.EXACTLY); view.measure(widthSpec, heightSpec); int minHeight = resources.getDimensionPixelSize( R.dimen.notification_validation_minimum_allowed_height); boolean result = view.getMeasuredHeight() >= minHeight; Trace.endSection(); return result; } private static boolean requiresHeightCheck(NotificationEntry entry) { // Undecorated custom views are disallowed from S onwards if (entry.targetSdk >= Build.VERSION_CODES.S) { return false; } // No need to check if the app isn't using any custom views Notification notification = entry.getSbn().getNotification(); if (notification.contentView == null && notification.bigContentView == null && notification.headsUpContentView == null) { return false; } return true; } private static void validateView(View view, NotificationEntry entry, Resources resources) throws InflationException { String invalidReason = isValidView(view, entry, resources); if (invalidReason != null) { throw new InflationException(invalidReason); } } private static void handleInflationError( HashMap<Integer, CancellationSignal> runningInflations, Exception e, NotificationEntry notification, @Nullable InflationCallback callback) { Loading
packages/SystemUI/tests/res/layout/invalid_notification_height.xml 0 → 100644 +18 −0 Original line number Diff line number Diff line <?xml version="1.0" encoding="utf-8"?> <!-- 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. --> <View xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="5dp"/> No newline at end of file
packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +34 −0 Original line number Diff line number Diff line Loading @@ -24,6 +24,7 @@ import static com.android.systemui.statusbar.notification.row.NotificationRowCon import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; Loading @@ -42,6 +43,7 @@ import android.os.Looper; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; import android.testing.TestableLooper.RunWithLooper; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.widget.RemoteViews; Loading Loading @@ -332,6 +334,38 @@ public class NotificationContentInflaterTest extends SysuiTestCase { eq(FLAG_CONTENT_VIEW_HEADS_UP)); } @Test public void testNotificationViewHeightTooSmallFailsValidation() { View view = mock(View.class); when(view.getHeight()) .thenReturn((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 10, mContext.getResources().getDisplayMetrics())); String result = NotificationContentInflater.isValidView(view, mRow.getEntry(), mContext.getResources()); assertNotNull(result); } @Test public void testNotificationViewPassesValidation() { View view = mock(View.class); when(view.getHeight()) .thenReturn((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 17, mContext.getResources().getDisplayMetrics())); String result = NotificationContentInflater.isValidView(view, mRow.getEntry(), mContext.getResources()); assertNull(result); } @Test public void testInvalidNotificationDoesNotInvokeCallback() throws Exception { mRow.getPrivateLayout().removeAllViews(); mRow.getEntry().getSbn().getNotification().contentView = new RemoteViews(mContext.getPackageName(), R.layout.invalid_notification_height); inflateAndWait(true, mNotificationInflater, FLAG_CONTENT_VIEW_ALL, mRow); assertEquals(0, mRow.getPrivateLayout().getChildCount()); verify(mRow, times(0)).onNotificationUpdated(); } private static void inflateAndWait(NotificationContentInflater inflater, @InflationFlag int contentToInflate, ExpandableNotificationRow row) Loading