diff options
5 files changed, 138 insertions, 2 deletions
diff --git a/packages/SystemUI/res/layout/status_bar_notification_row.xml b/packages/SystemUI/res/layout/status_bar_notification_row.xml index 2c08f5db0323..356b36fdbcd6 100644 --- a/packages/SystemUI/res/layout/status_bar_notification_row.xml +++ b/packages/SystemUI/res/layout/status_bar_notification_row.xml @@ -39,8 +39,11 @@ <com.android.systemui.statusbar.notification.row.NotificationContentView android:id="@+id/expanded" - android:layout_width="match_parent" - android:layout_height="wrap_content" /> + android:layout_width="match_parent" + 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" diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index d492e53c5390..939171479d32 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -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> diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java index c534860d12c6..39e4000c5d05 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationContentInflater.java @@ -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; @@ -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; @@ -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 { @@ -475,6 +480,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder result.packageContext, existingView, remoteViewClickHandler); + validateView(existingView, entry, row.getResources()); existingWrapper.onReinflated(); } } catch (Exception e) { @@ -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); @@ -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) { diff --git a/packages/SystemUI/tests/res/layout/invalid_notification_height.xml b/packages/SystemUI/tests/res/layout/invalid_notification_height.xml new file mode 100644 index 000000000000..aac43bf45676 --- /dev/null +++ b/packages/SystemUI/tests/res/layout/invalid_notification_height.xml @@ -0,0 +1,18 @@ +<?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 diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java index 5394d88ad103..3face350526a 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationContentInflaterTest.java @@ -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; @@ -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; @@ -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) |