Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 44a38820 authored by Ibrahim Yilmaz's avatar Ibrahim Yilmaz
Browse files

[MetricStyle] Implement MetricStyle Template

Flag: android.app.api_metric_style
Bug: 434910979
Bug: 415827681
Test: manual (Post a MetricStyle notification and check how it looks like)
Change-Id: Ie265dce1f285c2e6ea8a0d360dc2f5563ab04bd9
parent 68974fd2
Loading
Loading
Loading
Loading
+129 −53
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import static android.app.Flags.FLAG_NM_SUMMARIZATION;
import static android.app.Flags.FLAG_NM_SUMMARIZATION_ALL;
import static android.app.Flags.FLAG_NOTIFICATION_IS_ANIMATED_ACTION_API;
import static android.app.Flags.FLAG_HIDE_STATUS_BAR_NOTIFICATION;
import static android.app.Flags.apiMetricStyle;
import static android.app.Flags.notificationsRedesignTemplates;
import static android.app.admin.DevicePolicyResources.Drawables.Source.NOTIFICATION;
import static android.app.admin.DevicePolicyResources.Drawables.Style.SOLID_COLORED;
@@ -810,12 +811,12 @@ public class Notification implements Parcelable
            return true;
        }
        if (Flags.apiRichOngoing()) {
            return style.getClass() == ProgressStyle.class;
        if (Flags.apiRichOngoing() && style.getClass() == ProgressStyle.class) {
            return true;
        }
        if (Flags.apiMetricStyle()) {
            return style.getClass() == MetricStyle.class;
        if (Flags.apiMetricStyle() && style.getClass() == MetricStyle.class) {
            return true;
        }
        return false;
@@ -841,6 +842,10 @@ public class Notification implements Parcelable
                     R.layout.notification_2025_template_expanded_inbox -> true;
                case R.layout.notification_2025_template_expanded_progress
                        -> Flags.apiRichOngoing();
                case R.layout.notification_2025_template_collapsed_metric
                        -> Flags.apiMetricStyle();
                case R.layout.notification_2025_template_expanded_metric
                        -> Flags.apiMetricStyle();
                default -> false;
            };
        }
@@ -6181,6 +6186,8 @@ public class Notification implements Parcelable
                    || resId == getCollapsedMessagingLayoutResource()
                    || resId == getCollapsedMediaLayoutResource()
                    || resId == getCollapsedConversationLayoutResource()
                    || (apiMetricStyle()
                    && resId == getCollapsedMetricLayoutResource())
                    || (notificationsRedesignTemplates()
                    && resId == getCollapsedCallLayoutResource()));
            RemoteViews contentView = new BuilderRemoteViews(mContext.getApplicationInfo(), resId);
@@ -6193,6 +6200,11 @@ public class Notification implements Parcelable
            bindLargeIconAndApplyMargin(contentView, p, result);
            boolean showProgress = handleProgressBar(contentView, ex, p);
            boolean hasSecondLine = showProgress;
            // Metrics are considered as second line for MetricStyle.
            final boolean showMetrics = apiMetricStyle()
                    && (resId == getCollapsedMetricLayoutResource()
                    || resId == getExpandedMetricLayoutResource());
            hasSecondLine |= showMetrics;
            if (p.hasTitle()) {
                contentView.setViewVisibility(p.mTitleViewId, View.VISIBLE);
                contentView.setTextViewText(p.mTitleViewId,
@@ -8030,6 +8042,14 @@ public class Notification implements Parcelable
            }
        }
        private int getCollapsedMetricLayoutResource() {
            return R.layout.notification_2025_template_collapsed_metric;
        }
        private int getExpandedMetricLayoutResource() {
            return R.layout.notification_2025_template_expanded_metric;
        }
        private int getCollapsedMediaLayoutResource() {
            if (Flags.notificationsRedesignTemplates()) {
                return R.layout.notification_2025_template_collapsed_media;
@@ -11758,71 +11778,127 @@ public class Notification implements Parcelable
        /** @hide */
        @Override
        public RemoteViews makeContentView() {
            return null;
            // TODO(b/415828647): Implement for MetricStyle
            // Remember: Add new layout resources to isStandardLayout()
            final StandardTemplateParams p = mBuilder.mParams.reset()
                    .viewType(StandardTemplateParams.VIEW_TYPE_NORMAL)
                    .fillTextsFrom(mBuilder).text(null)
                    .hideRightIcon(true);
            final TemplateBindResult result = new TemplateBindResult();
            final RemoteViews contentView = getStandardView(
                    mBuilder.getCollapsedMetricLayoutResource(), p, result);
            return bindMetricStyleMetrics(contentView, /* isExpandedView = */false);
        }
        /** @hide */
        @Override
        public RemoteViews makeHeadsUpContentView() {
            return null;
            // TODO(b/415828647): Implement for MetricStyle
            // Remember: Add new layout resources to isStandardLayout()
            final StandardTemplateParams p = mBuilder.mParams.reset()
                    .viewType(StandardTemplateParams.VIEW_TYPE_HEADS_UP)
                    .fillTextsFrom(mBuilder).text(null)
                    .hideRightIcon(true);
            final TemplateBindResult result = new TemplateBindResult();
            final RemoteViews contentView = getStandardView(
                    mBuilder.getCollapsedMetricLayoutResource(), p, result);
            return bindMetricStyleMetrics(contentView, /* isExpandedView = */false);
        }
        /** @hide */
        @Override
        public RemoteViews makeExpandedContentView() {
            // TODO: b/415828647 - Implement properly; this is a temporary version using
            //  InboxStyle, for prototyping.
            // And remember: Add new layout resources to isStandardLayout()
            StandardTemplateParams p = mBuilder.mParams.reset()
            final StandardTemplateParams p = mBuilder.mParams.reset()
                    .viewType(StandardTemplateParams.VIEW_TYPE_EXPANDED)
                    .fillTextsFrom(mBuilder).text(null);
            TemplateBindResult result = new TemplateBindResult();
            RemoteViews contentView = getStandardView(mBuilder.getInboxLayoutResource(), p, result);
            int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3,
                    R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6};
            // Make sure all rows are gone in case we reuse a view.
            for (int rowId : rowIds) {
                contentView.setViewVisibility(rowId, View.GONE);
            }
            int i = 0;
            int topPadding = mBuilder.mContext.getResources().getDimensionPixelSize(
                    R.dimen.notification_inbox_item_top_padding);
            boolean first = true;
            int onlyViewId = 0;
            while (i < mMetrics.size() && i < MAX_METRICS) {
                Metric metric = mMetrics.get(i);
                contentView.setViewVisibility(rowIds[i], View.VISIBLE);
                Metric.MetricValue.ValueString valueString = metric.getValue().toValueString(
                    .fillTextsFrom(mBuilder).text(null)
                    .hideRightIcon(true);
            final TemplateBindResult result = new TemplateBindResult();
            final RemoteViews contentView = getStandardView(
                    mBuilder.getExpandedMetricLayoutResource(), p, result);
            return bindMetricStyleMetrics(contentView, /* isExpandedView = */true);
        }
        private RemoteViews bindMetricStyleMetrics(
                RemoteViews contentView, boolean isExpandedView) {
            for (int i = 0; i < MAX_METRICS; i++) {
                final MetricView metricView = MetricView.VIEWS.get(i);
                if (i < mMetrics.size()) {
                    contentView.setViewVisibility(metricView.containerId(), View.VISIBLE);
                    final Metric metric = mMetrics.get(i);
                    final Metric.MetricValue metricValue = metric.getValue();
                    final Metric.MetricValue.ValueString valueString = metricValue.toValueString(
                            mBuilder.mContext);
                contentView.setTextViewText(rowIds[i],
                        metric.getLabel() + ": " + valueString.text
                                + (valueString.subtext != null ? " " + valueString.subtext : ""));
                mBuilder.setTextViewColorSecondary(contentView, rowIds[i], p);
                contentView.setViewPadding(rowIds[i], 0, topPadding, 0, 0);
                if (first) {
                    onlyViewId = rowIds[i];
                    final String metricLabel;
                    if (isExpandedView) {
                        metricLabel = metric.getLabel();
                    } else {
                    onlyViewId = 0;
                        metricLabel = mBuilder.mContext.getString(
                                com.android.internal.R.string.notification_metric_label_template,
                                metric.getLabel());
                    }
                    contentView.setTextViewText(metricView.labelId(), metricLabel);
                    contentView.setViewVisibility(metricView.unitId(),
                            TextUtils.isEmpty(valueString.subtext()) ? View.GONE : View.VISIBLE);
                    contentView.setTextViewText(metricView.unitId(), valueString.subtext());
                    if (metricValue instanceof  Metric.TimeDifference timeDifference
                            && timeDifference.getPausedDuration() == null) {
                        contentView.setViewVisibility(metricView.textValueId(), View.GONE);
                        contentView.setViewVisibility(metricView.chronometerId(), View.VISIBLE);
                        contentView.setBoolean(
                                metricView.chronometerId(), "setStarted", true);
                        contentView.setChronometerCountDown(
                                metricView.chronometerId(), timeDifference.mCountDown);
                        contentView.setLong(
                                metricView.chronometerId(),
                                "setBase", calculateBase(timeDifference));
                        // TODO(b/434910979): implement format support for Chronometer.
                    } else {
                        contentView.setViewVisibility(metricView.chronometerId(), View.GONE);
                        contentView.setViewVisibility(metricView.textValueId(), View.VISIBLE);
                        contentView.setTextViewText(metricView.textValueId(), valueString.text());
                    }
                first = false;
                i++;
                } else {
                    contentView.setViewVisibility(metricView.containerId(), View.GONE);
                }
            if (onlyViewId != 0) {
                // We only have 1 entry, lets make it look like the normal Text of a Bigtext
                topPadding = mBuilder.mContext.getResources().getDimensionPixelSize(
                        R.dimen.notification_text_margin_top);
                contentView.setViewPadding(onlyViewId, 0, topPadding, 0, 0);
            }
            return contentView;
        }
        // TODO(b/435150348): Add Instant support to Chronometer..
        private long calculateBase(@NonNull Metric.TimeDifference timeDifference) {
            if (timeDifference.mZeroTime != null) {
                return getElapsedRealtimeClock().getAsLong()
                        + (timeDifference.mZeroTime.toEpochMilli() - getSystemClock().millis());
            } else if (timeDifference.mZeroElapsedRealtime != null) {
                return timeDifference.mZeroElapsedRealtime;
            } else {
                throw new IllegalStateException("None of mZeroTime, mZeroElapsedRealtime set!");
            }
        }
        private record MetricView(int containerId,
                           int labelId,
                           int textValueId,
                           int chronometerId,
                           int unitId) {
            private static final List<MetricView> VIEWS = List.of(
                    new MetricView(
                            /* containerId = */R.id.metric_view_0,
                            /* labelId = */R.id.metric_label_0,
                            /* textValueId = */R.id.metric_value_0,
                            /* chronometerId = */R.id.metric_chronometer_0,
                            /* unitId = */R.id.metric_unit_0),
                    new MetricView(
                            /* containerId = */R.id.metric_view_1,
                            /* labelId = */R.id.metric_label_1,
                            /* textValueId = */R.id.metric_value_1,
                            /* chronometerId = */R.id.metric_chronometer_1,
                            /* unitId = */R.id.metric_unit_1),
                    new MetricView(
                            /* containerId = */R.id.metric_view_2,
                            /* labelId = */R.id.metric_label_2,
                            /* textValueId = */R.id.metric_value_2,
                            /* chronometerId = */R.id.metric_chronometer_2,
                            /* unitId = */R.id.metric_unit_2)
            );
        }
    }
    /**
+79 −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.internal.widget;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.RemoteViews;

import androidx.annotation.Nullable;

/***
 * Used in the collapsed Notification.MetricStyle, this horizontal linear layout renders only
 * the children that can fit within its available width; otherwise it hides them.
 */
@RemoteViews.RemoteView
public class NotificationCollapsedMetricContainer extends LinearLayout {

    public NotificationCollapsedMetricContainer(Context context) {
        super(context);
    }

    public NotificationCollapsedMetricContainer(Context context,
            @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public NotificationCollapsedMetricContainer(Context context, @Nullable AttributeSet attrs,
            int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    public NotificationCollapsedMetricContainer(Context context, AttributeSet attrs,
            int defStyleAttr,
            int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        final int availableWidth = MeasureSpec.getSize(widthMeasureSpec);
        final int childCount = getChildCount();
        int usedWidth = 0;
        boolean hideRemainingChildren = false;
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            if (child == null || child.getVisibility() == View.GONE) {
                continue;
            }

            final MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
            final int childUsedWidth = child.getMeasuredWidth()
                    + layoutParams.getMarginStart() + layoutParams.getMarginEnd();
            if (hideRemainingChildren || usedWidth + childUsedWidth > availableWidth) {
                child.setVisibility(View.GONE);
                hideRemainingChildren = true;
            } else {
                usedWidth += childUsedWidth;
            }
        }
    }
}
+109 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="utf-8"?><!--
~ 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
-->
<com.android.internal.widget.NotificationCollapsedMetricContainer
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="@dimen/notification_2025_additional_margin"
android:layout_marginEnd="@dimen/notification_2025_margin"
android:minHeight="@dimen/notification_headerless_line_height"
android:orientation="horizontal"
>
    <LinearLayout
        android:id="@+id/metric_view_0"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginEnd="@dimen/notification_collapsed_metric_margin"
        >
        <TextView
            android:id="@+id/metric_label_0"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/NotificationMetricLabel"
            android:layout_marginEnd="@dimen/notification_collapsed_metric_label_margin"
            />
        <Chronometer
            android:id="@+id/metric_chronometer_0"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:visibility="gone"
            style="@style/NotificationMetricValue"
            />
        <TextView
            android:id="@+id/metric_value_0"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/NotificationMetricValue"
            />
    </LinearLayout>
    <LinearLayout
        android:id="@+id/metric_view_1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginEnd="@dimen/notification_collapsed_metric_margin"
        >
        <TextView
            android:id="@+id/metric_label_1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="@dimen/notification_collapsed_metric_label_margin"
            style="@style/NotificationMetricLabel"
            />
        <Chronometer
            android:id="@+id/metric_chronometer_1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:visibility="gone"
            style="@style/NotificationMetricValue"
            />
        <TextView
            android:id="@+id/metric_value_1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/NotificationMetricValue"
            />
    </LinearLayout>
    <LinearLayout
        android:id="@+id/metric_view_2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:layout_marginEnd="@dimen/notification_collapsed_metric_margin"
        >
        <TextView
            android:id="@+id/metric_label_2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginEnd="@dimen/notification_collapsed_metric_label_margin"
            style="@style/NotificationMetricLabel"
            />
        <Chronometer
            android:id="@+id/metric_chronometer_2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:visibility="gone"
            style="@style/NotificationMetricValue"
            />
        <TextView
            android:id="@+id/metric_value_2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="@style/NotificationMetricValue"
            />
    </LinearLayout>
</com.android.internal.widget.NotificationCollapsedMetricContainer>
+147 −0

File added.

Preview size limit exceeded, changes collapsed.

+157 −0

File added.

Preview size limit exceeded, changes collapsed.

Loading