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:
@@ -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"
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"/>
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user