[Manual Merge] Add a new car-uxr-client-lib

Its first ever residents are:
- UxrContentLimiter
- A set of stylables and a parser to parser config specified using these.

BUG: 160737115
BUG: 159766205
Test: manual
Change-Id: I1eb810f038fb81cb2f38484c4c6a296dd6879f50
diff --git a/car-uxr-client-lib/Android.bp b/car-uxr-client-lib/Android.bp
new file mode 100644
index 0000000..85f99de
--- /dev/null
+++ b/car-uxr-client-lib/Android.bp
@@ -0,0 +1,35 @@
+//
+// Copyright (C) 2020 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.
+
+android_library {
+
+    name: "car-uxr-client-lib",
+
+    srcs: ["src/**/*.java"],
+
+    optimize: {
+        enabled: false,
+    },
+
+    libs: [
+        "android.car",
+    ],
+
+    static_libs: [
+        "androidx.recyclerview_recyclerview",
+        "androidx.lifecycle_lifecycle-common-java8",
+        "car-ui-lib",
+    ],
+}
diff --git a/car-uxr-client-lib/AndroidManifest.xml b/car-uxr-client-lib/AndroidManifest.xml
new file mode 100644
index 0000000..9550419
--- /dev/null
+++ b/car-uxr-client-lib/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2020 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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.car.uxr">
+</manifest>
diff --git a/car-uxr-client-lib/OWNERS b/car-uxr-client-lib/OWNERS
new file mode 100644
index 0000000..55f7d43
--- /dev/null
+++ b/car-uxr-client-lib/OWNERS
@@ -0,0 +1,9 @@
+# People who can approve changes for submission.
+johnchoi@google.com
+stenning@google.com
+igorr@google.com
+
+# Engs
+pardis@google.com
+jjoz@google.com
+
diff --git a/car-uxr-client-lib/README.md b/car-uxr-client-lib/README.md
new file mode 100644
index 0000000..7f57376
--- /dev/null
+++ b/car-uxr-client-lib/README.md
@@ -0,0 +1,7 @@
+# Android Automotive App-side User Experience Restriction (UXR) library
+Components and resources designed to reduce the amount of work needed by
+Automotive app developers to add User Experience Restriction Engine
+support to their apps.
+
+Source: /packages/apps/Car/libs/car-uxr-client-lib
+
diff --git a/car-uxr-client-lib/res/values/attrs.xml b/car-uxr-client-lib/res/values/attrs.xml
new file mode 100644
index 0000000..084e30d
--- /dev/null
+++ b/car-uxr-client-lib/res/values/attrs.xml
@@ -0,0 +1,41 @@
+<!--
+  ~ Copyright (C) 2020 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
+  -->
+<!--
+  ~ An example uxr_config.xml file would look like:
+  ~ <Mapping xmlns:app="http://schemas.android.com/apk/res-auto">
+  ~     <ListConfig
+  ~         app:id="@+id/call_log_list_uxr_config"
+  ~         app:maxLength="10"
+  ~         app:message="@string/call_log_scrolling_limited_message"
+  ~     />
+  ~ </Mapping>
+  -->
+<resources>
+    <!-- Global container of uxr related app configs -->
+    <declare-styleable name="CarUxRestrictionsAppConfig"/>
+    <!-- The mapping of lists to their uxr related override values. -->
+    <declare-styleable name="CarUxRestrictionsAppConfig_Mapping"/>
+
+    <!-- Uxr related overrides for a specific list -->
+    <declare-styleable name="CarUxRestrictionsAppConfig_ListConfig">
+        <!-- Id of ListConfig, used to differentiate them -->
+        <attr name="id" format="reference"/>
+        <!-- Used to limit the length of a list. -->
+        <attr name="maxLength" format="integer"/>
+        <!-- Used to educate users why their scrolling experience is limited. -->
+        <attr name="message" format="string"/>
+    </declare-styleable>
+</resources>
\ No newline at end of file
diff --git a/car-uxr-client-lib/src/com/android/car/uxr/CarUxRestrictionsAppConfig.java b/car-uxr-client-lib/src/com/android/car/uxr/CarUxRestrictionsAppConfig.java
new file mode 100644
index 0000000..49634ca
--- /dev/null
+++ b/car-uxr-client-lib/src/com/android/car/uxr/CarUxRestrictionsAppConfig.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2020 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.car.uxr;
+
+import android.content.Context;
+
+import androidx.annotation.IdRes;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.annotation.XmlRes;
+
+import java.util.Map;
+
+/**
+ * A container class for app specific Car User Experience Restriction override configurations.
+ *
+ * <p>{@link #getInstance(Context, int)} will returned a lazily populated cached reference to the
+ * configurations object that is read using
+ * {@link CarUxRestrictionsAppConfigParser#parseConfig(Context, int)}.
+ *
+ * <p>{@link #getMapping()} can be used to access the mapping of component IDs to configurations
+ * specific to that component.
+ */
+public class CarUxRestrictionsAppConfig {
+
+    private final Map<Integer, ListConfig> mMapping;
+    private static CarUxRestrictionsAppConfig sInstance;
+
+    CarUxRestrictionsAppConfig(Map<Integer, ListConfig> mapping) {
+        mMapping = mapping;
+    }
+
+    /**
+     * Returns a cached reference to the {@link CarUxRestrictionsAppConfig} object
+     * resulting from parsing the contents of {@code xmlRes} xml resource.
+     *
+     * @param context - the app context
+     * @param xmlRes  - the xml resource that contains the UXR override configs.
+     */
+    public static CarUxRestrictionsAppConfig getInstance(Context context, @XmlRes int xmlRes) {
+        if (sInstance == null) {
+            sInstance = CarUxRestrictionsAppConfigParser.parseConfig(context, xmlRes);
+        }
+
+        return sInstance;
+    }
+
+    /**
+     * Returns a {@link Map} of Resource Ids as ints to {@link ListConfig} objects.
+     */
+    public Map<Integer, ListConfig> getMapping() {
+        return mMapping;
+    }
+
+    /**
+     * A class representing Car User Experience Restriction override configurations for a list UI
+     * component.
+     */
+    public static class ListConfig {
+        @IdRes
+        private final int mId;
+        private final Integer mContentLimit;
+        @StringRes
+        private final Integer mScrollingLimitedMessageResId;
+
+        private ListConfig(@IdRes int id, @Nullable Integer contentLimit,
+                @StringRes Integer scrollingLimitedMessageResId) {
+            mId = id;
+            mContentLimit = contentLimit;
+            mScrollingLimitedMessageResId = scrollingLimitedMessageResId;
+        }
+
+        /**
+         * Returns a {@code Builder} that can be used to build a {@link ListConfig} object for a
+         * component identified with the provided {@code id}.
+         *
+         * @param id - an identifier for the component whose behavior needs to be overridden with
+         *           the configurations specified in the resulting {@link ListConfig} object.
+         */
+        public static Builder builder(@IdRes int id) {
+            return new Builder(id);
+        }
+
+        /**
+         * Returns the identifier for the component whose behavior needs to be overridden by this
+         * config object.
+         */
+        @IdRes
+        public int getId() {
+            return mId;
+        }
+
+        /**
+         * Returns the item limit to impose on the contents of the corresponding list component.
+         */
+        @Nullable
+        public Integer getContentLimit() {
+            return mContentLimit;
+        }
+
+        /**
+         * Returns the string resource ID to use when educating users about why the content in the
+         * list they're browsing has been limited.
+         */
+        @Nullable
+        @StringRes
+        public Integer getScrollingLimitedMessageResId() {
+            return mScrollingLimitedMessageResId;
+        }
+
+        /**
+         * A Builder for {@link ListConfig}.
+         */
+        public static class Builder {
+            @IdRes
+            private final int mId;
+            private Integer mContentLimit;
+            @StringRes
+            private Integer mScrollingLimitedMessageResId;
+
+
+            /**
+             * Constructs a {@code Builder} that can be used to build a {@link ListConfig} object
+             * for a component identified with the provided {@code id}.
+             *
+             * @param id - an identifier for the component whose behavior needs to be overridden
+             *           with the configurations specified in the resulting {@link ListConfig}
+             *           object.
+             */
+            private Builder(@IdRes int id) {
+                mId = id;
+            }
+
+            /**
+             * Sets the item limit to impose on the contents of the corresponding list component.
+             *
+             * @param contentLimit - the item limit
+             * @return this {@code Builder} object to facilitate chaining.
+             */
+            public Builder setContentLimit(int contentLimit) {
+                mContentLimit = contentLimit;
+                return this;
+            }
+
+            /**
+             * Sets the string resource ID to use when educating users about why the content in the
+             * * list they're browsing has been limited.
+             *
+             * @param scrollingLimitedMessageResId - an educational message string resource ID
+             * @return this {@code Builder} object to facilitate chaining.
+             */
+            public Builder setScrollingLimitedMessageResId(
+                    @StringRes int scrollingLimitedMessageResId) {
+                mScrollingLimitedMessageResId = scrollingLimitedMessageResId;
+                return this;
+            }
+
+            /**
+             * Build and return a {@link ListConfig} object with the values provided to this
+             * {@code Builder} object.
+             */
+            public ListConfig build() {
+                return new ListConfig(mId, mContentLimit, mScrollingLimitedMessageResId);
+            }
+        }
+    }
+}
diff --git a/car-uxr-client-lib/src/com/android/car/uxr/CarUxRestrictionsAppConfigParser.java b/car-uxr-client-lib/src/com/android/car/uxr/CarUxRestrictionsAppConfigParser.java
new file mode 100644
index 0000000..341e426
--- /dev/null
+++ b/car-uxr-client-lib/src/com/android/car/uxr/CarUxRestrictionsAppConfigParser.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2020 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.car.uxr;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+import android.view.View;
+
+import androidx.annotation.XmlRes;
+
+import com.android.car.uxr.CarUxRestrictionsAppConfig.ListConfig;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A parser that can read an XML resource file and construct the corresponding
+ * {@link CarUxRestrictionsAppConfig} object.
+ *
+ * See car-uxr-client-lib/res/values/attrs.xml for the definition of the relevant XML tags.
+ */
+public class CarUxRestrictionsAppConfigParser {
+    private static final String TAG = "UxrAppConfigParser";
+
+    static CarUxRestrictionsAppConfig parseConfig(Context context, @XmlRes int xmlRes) {
+        try (XmlResourceParser parser = context.getResources().getXml(xmlRes)) {
+            AttributeSet attrs = Xml.asAttributeSet(parser);
+            Map<Integer, ListConfig> mapping = new HashMap<>();
+
+            // Skip over the xml version tag
+            parser.next();
+            // Skip over the copyright comment block
+            parser.next();
+            parser.require(XmlPullParser.START_TAG, null, "Mapping");
+            while (parser.next() != XmlPullParser.END_TAG) {
+                ListConfig listConfig = parseListConfigItem(context, parser, attrs);
+                mapping.put(listConfig.getId(), listConfig);
+            }
+
+            return new CarUxRestrictionsAppConfig(mapping);
+        } catch (XmlPullParserException | IOException e) {
+            throw new RuntimeException("Unable to parse CarUxRestrictionsAppConfig", e);
+        }
+    }
+
+    private static ListConfig parseListConfigItem(
+            Context context, XmlResourceParser parser, AttributeSet attrs)
+            throws XmlPullParserException, IOException {
+
+        parser.require(XmlPullParser.START_TAG, null, "ListConfig");
+
+        TypedArray a = context.obtainStyledAttributes(
+                attrs, R.styleable.CarUxRestrictionsAppConfig_ListConfig);
+
+        try {
+            int id = a.getResourceId(R.styleable.CarUxRestrictionsAppConfig_ListConfig_id,
+                    View.NO_ID);
+            if (id == View.NO_ID) {
+                throw new IllegalStateException("Id field is required");
+            }
+
+            boolean messageExists = a.hasValue(
+                    R.styleable.CarUxRestrictionsAppConfig_ListConfig_message);
+            int messageResId = a.getResourceId(
+                    R.styleable.CarUxRestrictionsAppConfig_ListConfig_message, View.NO_ID);
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, messageExists
+                        ? "message field is set to " + messageResId
+                        : "message field not specified");
+            }
+
+            boolean maxLengthExists = a.hasValue(
+                    R.styleable.CarUxRestrictionsAppConfig_ListConfig_maxLength);
+            int maxLengthInt = a.getInt(
+                    R.styleable.CarUxRestrictionsAppConfig_ListConfig_maxLength, 0);
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, maxLengthExists
+                        ? "maxLength field is set to " + maxLengthInt
+                        : "maxLength field not specified");
+            }
+
+            parser.next();
+            parser.require(XmlPullParser.END_TAG, null, "ListConfig");
+
+            ListConfig.Builder builder = ListConfig.builder(id);
+            if (maxLengthExists) {
+                builder.setContentLimit(maxLengthInt);
+            }
+            if (messageExists) {
+                builder.setScrollingLimitedMessageResId(messageResId);
+            }
+            return builder.build();
+        } finally {
+            a.recycle();
+        }
+    }
+}
diff --git a/car-uxr-client-lib/src/com/android/car/uxr/LifeCycleObserverUxrContentLimiter.java b/car-uxr-client-lib/src/com/android/car/uxr/LifeCycleObserverUxrContentLimiter.java
new file mode 100644
index 0000000..3c8905e
--- /dev/null
+++ b/car-uxr-client-lib/src/com/android/car/uxr/LifeCycleObserverUxrContentLimiter.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2020 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.car.uxr;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.DefaultLifecycleObserver;
+import androidx.lifecycle.LifecycleOwner;
+
+import com.android.car.ui.recyclerview.ContentLimiting;
+
+/**
+ * An implementation of {@link UxrContentLimiter} interface that also provides the functionality
+ * necessary for a {@link DefaultLifecycleObserver}.
+ *
+ * <p>Relies heavily on the {@link UxrContentLimiterImpl} implementation of the
+ * {@link UxrContentLimiter} interface.
+ *
+ * <p>For example, you could do the following to get yourself a lifecycle aware {@link
+ * UxrContentLimiter}:
+ * <pre>{@code
+ * new LifeCycleObserverUxrContentLimiter(new UxrContentLimiterImpl(context,xmlRes));
+ * }</pre>
+ */
+public class LifeCycleObserverUxrContentLimiter
+        implements UxrContentLimiter, DefaultLifecycleObserver {
+
+    private final UxrContentLimiterImpl mDelegate;
+
+    public LifeCycleObserverUxrContentLimiter(UxrContentLimiterImpl delegate) {
+        mDelegate = delegate;
+    }
+
+    @Override
+    public void onStart(@NonNull LifecycleOwner owner) {
+        mDelegate.start();
+    }
+
+    @Override
+    public void onStop(@NonNull LifecycleOwner owner) {
+        mDelegate.stop();
+    }
+
+    @Override
+    public void setAdapter(ContentLimiting adapter) {
+        mDelegate.setAdapter(adapter);
+    }
+}
diff --git a/car-uxr-client-lib/src/com/android/car/uxr/UxrContentLimiter.java b/car-uxr-client-lib/src/com/android/car/uxr/UxrContentLimiter.java
new file mode 100644
index 0000000..9fd877e
--- /dev/null
+++ b/car-uxr-client-lib/src/com/android/car/uxr/UxrContentLimiter.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2020 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.car.uxr;
+
+import com.android.car.ui.recyclerview.ContentLimiting;
+
+/**
+ * An interface to facilitate the content limiting ability of {@link ContentLimiting}
+ * {@link androidx.recyclerview.widget.RecyclerView.Adapter} objects based on changes to the state
+ * of the car.
+ */
+public interface UxrContentLimiter {
+
+    /**
+     * Registers the given {@link ContentLimiting} with this {@code UxrContentLimiter}.
+     *
+     * <p>That means that when the car state changes, if necessary, this
+     * {@code UxrContentLimiter} will limit the content in the given adapter.
+     *
+     * @param adapter - the adapter to associate with this content limiter.
+     */
+    void setAdapter(ContentLimiting adapter);
+}
diff --git a/car-uxr-client-lib/src/com/android/car/uxr/UxrContentLimiterImpl.java b/car-uxr-client-lib/src/com/android/car/uxr/UxrContentLimiterImpl.java
new file mode 100644
index 0000000..ce22cca
--- /dev/null
+++ b/car-uxr-client-lib/src/com/android/car/uxr/UxrContentLimiterImpl.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2020 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.car.uxr;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.util.Log;
+
+import androidx.annotation.IdRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.XmlRes;
+
+import com.android.car.ui.recyclerview.ContentLimiting;
+import com.android.car.ui.utils.CarUxRestrictionsUtil;
+import com.android.car.uxr.CarUxRestrictionsAppConfig.ListConfig;
+
+/**
+ * A class that can work together with a {@link ContentLimiting} {@link
+ * androidx.recyclerview.widget.RecyclerView.Adapter} object to provide content limiting ability
+ * based on changes to the state of the car, by listening for the latest {@link CarUxRestrictions}.
+ *
+ * <p>This class manages 3 things:
+ * <ul>
+ *     <li> Communications with the User Experience Restriction Engine
+ *     <li> Looking up app-side overrides for customizing the content-limiting behavior of a given
+ *     list of items in a particular screen
+ *     <li> Relaying the relevant parts of that information to the registered
+ *     adapter at the right time
+ * </ul>
+ *
+ * <p>The app-side overrides are accessed via the {@link CarUxRestrictionsAppConfig} object.
+ *
+ * <p>Because all but one of the dependencies for this class can be instantiated as soon as a
+ * {@link Context} is available, we provide a separate {@link #setAdapter(ContentLimiting)}
+ * API for linking the targeted adapter. That way the registration can happen in a different part of
+ * code, and potentially in a different lifecycle method to provide maximum flexibility.
+ */
+public class UxrContentLimiterImpl implements UxrContentLimiter {
+
+    private ContentLimiting mAdapter;
+    private ListConfig mListConfig;
+
+    private final CarUxRestrictionsUtil mCarUxRestrictionsUtil;
+    private final CarUxRestrictionsAppConfig mCarUxRestrictionsAppConfig;
+    private final CarUxRestrictionsUtil.OnUxRestrictionsChangedListener mListener =
+            new Listener();
+
+    private class Listener implements CarUxRestrictionsUtil.OnUxRestrictionsChangedListener {
+        private static final String TAG = "ContentLimitListener";
+
+        @Override
+        public void onRestrictionsChanged(@NonNull CarUxRestrictions carUxRestrictions) {
+            if (mAdapter == null) {
+                Log.w(TAG, "No adapter registered.");
+                return;
+            }
+
+            int maxItems = getMaxItemsToUse(carUxRestrictions, mAdapter.getConfigurationId());
+            logD("New limit " + maxItems);
+            mAdapter.setMaxItems(maxItems);
+        }
+
+        private int getMaxItemsToUse(CarUxRestrictions carUxRestrictions, @IdRes int id) {
+            // Unrelated restrictions are active. Quit early.
+            if ((carUxRestrictions.getActiveRestrictions()
+                    & CarUxRestrictions.UX_RESTRICTIONS_LIMIT_CONTENT)
+                    == 0) {
+                logD("Lists are unrestricted.");
+                return ContentLimiting.UNLIMITED;
+            }
+
+            if (mListConfig == null || mListConfig.getContentLimit() == null) {
+                logD("No configs found for adapter with the ID: " + id
+                        + " Using the default limit");
+                return carUxRestrictions.getMaxCumulativeContentItems();
+            }
+
+            logD("Using the provided override.");
+            return mListConfig.getContentLimit();
+        }
+
+        private void logD(String s) {
+            if (Log.isLoggable(TAG, Log.DEBUG)) {
+                Log.d(TAG, s);
+            }
+        }
+    }
+
+    /**
+     * Constructs a {@link UxrContentLimiterImpl} object given the app context and the XML resource
+     * file to parse the User Experience Restriction override configs from.
+     *
+     * @param context - the app context
+     * @param xmlRes  - the UXR override config XML resource
+     */
+    public UxrContentLimiterImpl(Context context, @XmlRes int xmlRes) {
+        mCarUxRestrictionsUtil = CarUxRestrictionsUtil.getInstance(context);
+        mCarUxRestrictionsAppConfig = CarUxRestrictionsAppConfig.getInstance(context, xmlRes);
+    }
+
+    @Override
+    public void setAdapter(ContentLimiting adapter) {
+        mAdapter = adapter;
+        int key = mAdapter.getConfigurationId();
+        if (mCarUxRestrictionsAppConfig.getMapping().containsKey(key)) {
+            mListConfig = mCarUxRestrictionsAppConfig.getMapping().get(key);
+            Integer overriddenMessageResId = mListConfig.getScrollingLimitedMessageResId();
+            if (overriddenMessageResId != null) {
+                mAdapter.setScrollingLimitedMessageResId(overriddenMessageResId);
+            }
+        }
+    }
+
+    /**
+     * Start listening for changes to {@link CarUxRestrictions}.
+     */
+    public void start() {
+        mCarUxRestrictionsUtil.register(mListener);
+    }
+
+    /**
+     * Stop listening for changes to {@link CarUxRestrictions}.
+     */
+    public void stop() {
+        mCarUxRestrictionsUtil.unregister(mListener);
+    }
+}