Merge "Enact minimum height requirement for notifications" into tm-qpr-dev am: 0f6acd6fb7

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/21097606

Change-Id: Iecd45ead5c116da1da5b52c3ce42b0b5fec573c6
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
Steve Elliott
2023-02-09 17:47:09 +00:00
committed by Automerger Merge Worker
5 changed files with 138 additions and 2 deletions

View File

@@ -39,8 +39,11 @@
<com.android.systemui.statusbar.notification.row.NotificationContentView <com.android.systemui.statusbar.notification.row.NotificationContentView
android:id="@+id/expanded" android:id="@+id/expanded"
android:layout_width="match_parent" 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 <com.android.systemui.statusbar.notification.row.NotificationContentView
android:id="@+id/expandedPublic" android:id="@+id/expandedPublic"

View File

@@ -184,6 +184,15 @@
<!-- Height of a small notification in the status bar--> <!-- Height of a small notification in the status bar-->
<dimen name="notification_min_height">@*android:dimen/notification_min_height</dimen> <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 --> <!-- Increased height of a small notification in the status bar -->
<dimen name="notification_min_height_increased">146dp</dimen> <dimen name="notification_min_height_increased">146dp</dimen>

View File

@@ -28,8 +28,11 @@ import android.content.Context;
import android.content.ContextWrapper; import android.content.ContextWrapper;
import android.content.pm.ApplicationInfo; import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build;
import android.os.CancellationSignal; import android.os.CancellationSignal;
import android.os.Trace;
import android.os.UserHandle; import android.os.UserHandle;
import android.service.notification.StatusBarNotification; import android.service.notification.StatusBarNotification;
import android.util.Log; import android.util.Log;
@@ -38,6 +41,7 @@ import android.widget.RemoteViews;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.widget.ImageMessageConsumer; import com.android.internal.widget.ImageMessageConsumer;
import com.android.systemui.R;
import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Background;
import com.android.systemui.media.controls.util.MediaFeatureFlag; import com.android.systemui.media.controls.util.MediaFeatureFlag;
@@ -468,6 +472,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
result.packageContext, result.packageContext,
parentLayout, parentLayout,
remoteViewClickHandler); remoteViewClickHandler);
validateView(v, entry, row.getResources());
v.setIsRootNamespace(true); v.setIsRootNamespace(true);
applyCallback.setResultView(v); applyCallback.setResultView(v);
} else { } else {
@@ -475,6 +480,7 @@ public class NotificationContentInflater implements NotificationRowContentBinder
result.packageContext, result.packageContext,
existingView, existingView,
remoteViewClickHandler); remoteViewClickHandler);
validateView(existingView, entry, row.getResources());
existingWrapper.onReinflated(); existingWrapper.onReinflated();
} }
} catch (Exception e) { } catch (Exception e) {
@@ -496,6 +502,13 @@ public class NotificationContentInflater implements NotificationRowContentBinder
@Override @Override
public void onViewApplied(View v) { 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) { if (isNewView) {
v.setIsRootNamespace(true); v.setIsRootNamespace(true);
applyCallback.setResultView(v); applyCallback.setResultView(v);
@@ -553,6 +566,65 @@ public class NotificationContentInflater implements NotificationRowContentBinder
runningInflations.put(inflationId, cancellationSignal); 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( private static void handleInflationError(
HashMap<Integer, CancellationSignal> runningInflations, Exception e, HashMap<Integer, CancellationSignal> runningInflations, Exception e,
NotificationEntry notification, @Nullable InflationCallback callback) { NotificationEntry notification, @Nullable InflationCallback callback) {

View File

@@ -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"/>

View File

@@ -24,6 +24,7 @@ import static com.android.systemui.statusbar.notification.row.NotificationRowCon
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
@@ -42,6 +43,7 @@ import android.os.Looper;
import android.testing.AndroidTestingRunner; import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper; import android.testing.TestableLooper;
import android.testing.TestableLooper.RunWithLooper; import android.testing.TestableLooper.RunWithLooper;
import android.util.TypedValue;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.RemoteViews; import android.widget.RemoteViews;
@@ -332,6 +334,38 @@ public class NotificationContentInflaterTest extends SysuiTestCase {
eq(FLAG_CONTENT_VIEW_HEADS_UP)); 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, private static void inflateAndWait(NotificationContentInflater inflater,
@InflationFlag int contentToInflate, @InflationFlag int contentToInflate,
ExpandableNotificationRow row) ExpandableNotificationRow row)