Merge "Create a bugreport service" into qt-dev
diff --git a/car-lib/src/android/car/hardware/CarPropertyConfig.java b/car-lib/src/android/car/hardware/CarPropertyConfig.java
index d513415..e271fd5 100644
--- a/car-lib/src/android/car/hardware/CarPropertyConfig.java
+++ b/car-lib/src/android/car/hardware/CarPropertyConfig.java
@@ -108,26 +108,50 @@
     public static final int VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS = 2;
 
     /**
-     * @return Access type of properties.
-     * None = 0, READ = 1, WRITE = 2, READ_WRITE = 3
+     * Return the access type of the car property.
+     * <p>The access type could be one of the following:
+     * <ul>
+     *   <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_NONE}</li>
+     *   <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}</li>
+     *   <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_WRITE}</li>
+     *   <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ_WRITE}</li>
+     * </ul>
+     *
+     * @return the access type of the car property.
      */
     public @VehiclePropertyAccessType int getAccess() {
         return mAccess;
     }
 
     /**
+     * Return the area type of the car property.
+     * <p>The area type could be one of the following:
+     * <ul>
+     *   <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}</li>
+     *   <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_WINDOW}</li>
+     *   <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_SEAT}</li>
+     *   <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_DOOR}</li>
+     *   <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_MIRROR}</li>
+     *   <li>{@link VehicleAreaType#VEHICLE_AREA_TYPE_WHEEL}</li>
+     * </ul>
      *
-     * @return Area type of properties.
-     * Global = 0, Window = 2, Seat = 3, Door = 4, Mirror = 5, Wheel = 6
+     * @return the area type of the car property.
      */
     public @VehicleAreaTypeValue int getAreaType() {
         return mAreaType;
     }
 
     /**
+     * Return the change mode of the car property.
      *
-     * @return Change mode of properties.
-     * STATIC = 0, ON_CHANGE = 1, CONTINUOUS = 2
+     * <p>The change mode could be one of the following:
+     * <ul>
+     *   <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_STATIC }</li>
+     *   <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}</li>
+     *   <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS}</li>
+     * </ul>
+     *
+     * @return the change mode of properties.
      */
     public @VehiclePropertyChangeModeType int getChangeMode() {
         return mChangeMode;
diff --git a/car-lib/src/android/car/hardware/property/CarPropertyManager.java b/car-lib/src/android/car/hardware/property/CarPropertyManager.java
index d45ed3b..6317c66 100644
--- a/car-lib/src/android/car/hardware/property/CarPropertyManager.java
+++ b/car-lib/src/android/car/hardware/property/CarPropertyManager.java
@@ -48,7 +48,6 @@
     private static final boolean DBG = false;
     private static final String TAG = "CarPropertyManager";
     private static final int MSG_GENERIC_EVENT = 0;
-    private final List<CarPropertyConfig> mConfigs;
     private final SingleMessageHandler<CarPropertyEvent> mHandler;
     private final ICarProperty mService;
 
@@ -57,6 +56,8 @@
     /** Record of locally active properties. Key is propertyId */
     private final SparseArray<CarPropertyListeners> mActivePropertyListener =
             new SparseArray<>();
+    /** Record of properties' configs. Key is propertyId */
+    private final SparseArray<CarPropertyConfig> mConfigMap = new SparseArray<>();
 
     /**
      * Application registers {@link CarPropertyEventCallback} object to receive updates and changes
@@ -78,15 +79,15 @@
     }
 
     /** Read ON_CHANGE sensors */
-    public static final float SENSOR_RATE_ONCHANGE = 0;
+    public static final float SENSOR_RATE_ONCHANGE = 0f;
     /** Read sensors at the rate of  1 hertz */
-    public static final float SENSOR_RATE_NORMAL = 1;
+    public static final float SENSOR_RATE_NORMAL = 1f;
     /** Read sensors at the rate of 5 hertz */
-    public static final float SENSOR_RATE_UI = 5;
+    public static final float SENSOR_RATE_UI = 5f;
     /** Read sensors at the rate of 10 hertz */
-    public static final float SENSOR_RATE_FAST = 10;
+    public static final float SENSOR_RATE_FAST = 10f;
     /** Read sensors at the rate of 100 hertz */
-    public static final float SENSOR_RATE_FASTEST = 100;
+    public static final float SENSOR_RATE_FASTEST = 100f;
 
     /**
      * Get an instance of the CarPropertyManager.
@@ -99,7 +100,10 @@
     public CarPropertyManager(@NonNull ICarProperty service, @Nullable Handler handler) {
         mService = service;
         try {
-            mConfigs = mService.getPropertyList();
+            List<CarPropertyConfig> configs = mService.getPropertyList();
+            for (CarPropertyConfig carPropertyConfig : configs) {
+                mConfigMap.put(carPropertyConfig.getPropertyId(), carPropertyConfig);
+            }
         } catch (Exception e) {
             Log.e(TAG, "getPropertyList exception ", e);
             throw new RuntimeException(e);
@@ -135,14 +139,33 @@
 
     /**
      * Register {@link CarPropertyEventCallback} to get property updates. Multiple listeners
-     * can be registered for a single sensor or the same listener can be used for different sensors.
-     * If the same listener is registered again for the same sensor, it will be either ignored or
-     * updated depending on the rate.
+     * can be registered for a single property or the same listener can be used for different
+     * properties. If the same listener is registered again for the same property, it will be
+     * updated to new rate.
+     * <p>Rate could be one of the following:
+     * <ul>
+     *   <li>{@link CarPropertyManager#SENSOR_RATE_ONCHANGE}</li>
+     *   <li>{@link CarPropertyManager#SENSOR_RATE_NORMAL}</li>
+     *   <li>{@link CarPropertyManager#SENSOR_RATE_UI}</li>
+     *   <li>{@link CarPropertyManager#SENSOR_RATE_FAST}</li>
+     *   <li>{@link CarPropertyManager#SENSOR_RATE_FASTEST}</li>
+     * </ul>
+     * <p>
+     * <b>Note:</b>Rate has no effect if the property has one of the following change modes:
+     * <ul>
+     *   <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_STATIC}</li>
+     *   <li>{@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE}</li>
+     * </ul>
+     * See {@link CarPropertyConfig#getChangeMode()} for details.
+     * If rate is higher than {@link CarPropertyConfig#getMaxSampleRate()}, it will be registered
+     * with max sample rate.
+     * If rate is lower than {@link CarPropertyConfig#getMinSampleRate()}, it will be registered
+     * with min sample rate.
      *
      * @param callback CarPropertyEventCallback to be registered.
      * @param propertyId PropertyId to subscribe
-     * @param rate rate how fast the sensor events are delivered.
-     * @return if the sensor was successfully enabled.
+     * @param rate how fast the property events are delivered in Hz.
+     * @return true if the listener is successfully registered.
      * @throws SecurityException if missing the appropriate permission.
      */
     public boolean registerCallback(@NonNull CarPropertyEventCallback callback,
@@ -151,6 +174,14 @@
             if (mCarPropertyEventToService == null) {
                 mCarPropertyEventToService = new CarPropertyEventListenerToService(this);
             }
+            CarPropertyConfig config = mConfigMap.get(propertyId);
+            if (config == null) {
+                Log.e(TAG, "registerListener:  propId is not in config list:  " + propertyId);
+                return false;
+            }
+            if (config.getChangeMode() == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE) {
+                rate = SENSOR_RATE_ONCHANGE;
+            }
             boolean needsServerUpdate = false;
             CarPropertyListeners listeners;
             listeners = mActivePropertyListener.get(propertyId);
@@ -257,7 +288,11 @@
      */
     @NonNull
     public List<CarPropertyConfig> getPropertyList() {
-        return mConfigs;
+        List<CarPropertyConfig> configs = new ArrayList<>(mConfigMap.size());
+        for (int i = 0; i < mConfigMap.size(); i++) {
+            configs.add(mConfigMap.valueAt(i));
+        }
+        return configs;
     }
 
     /**
@@ -268,9 +303,10 @@
     @NonNull
     public List<CarPropertyConfig> getPropertyList(@NonNull ArraySet<Integer> propertyIds) {
         List<CarPropertyConfig> configs = new ArrayList<>();
-        for (CarPropertyConfig c : mConfigs) {
-            if (propertyIds.contains(c.getPropertyId())) {
-                configs.add(c);
+        for (int propId : propertyIds) {
+            CarPropertyConfig config = mConfigMap.get(propId);
+            if (config != null) {
+                configs.add(config);
             }
         }
         return configs;
diff --git a/service/res/values/strings.xml b/service/res/values/strings.xml
index 93021b5..acc7a58 100644
--- a/service/res/values/strings.xml
+++ b/service/res/values/strings.xml
@@ -256,7 +256,7 @@
     <string name="car_permission_label_enroll_trust">Enroll Trusted Device</string>
     <string name="car_permission_desc_enroll_trust">Allow Trusted Device Enrollment</string>
 
-    <!-- The default name of device enrolled as trust device [CHAR LIMIT=16] -->
+    <!-- The default name of device enrolled as trust device [CHAR LIMIT=NONE] -->
     <string name="trust_device_default_name">My Device</string>
 
     <!-- The package name of the media application that will be selected as the default [CHAR LIMIT=NONE] -->
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index b4faa82..5377920 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -141,7 +141,10 @@
         // onto disk, so it's sufficient to do it once + we minimize the number of disk writes.
         if (Settings.Global.getInt(mContext.getContentResolver(),
                 CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET, /* default= */ 0) == 0) {
-            setSystemUserRestrictions();
+            // Only apply the system user restrictions if the system user is headless.
+            if (mCarUserManagerHelper.isHeadlessSystemUser()) {
+                setSystemUserRestrictions();
+            }
             mCarUserManagerHelper.initDefaultGuestRestrictions();
             Settings.Global.putInt(mContext.getContentResolver(),
                     CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET, 1);
diff --git a/tests/DirectRenderingClusterSample/Android.mk b/tests/DirectRenderingClusterSample/Android.mk
index 635858f..41d2a65 100644
--- a/tests/DirectRenderingClusterSample/Android.mk
+++ b/tests/DirectRenderingClusterSample/Android.mk
@@ -39,7 +39,8 @@
     androidx-constraintlayout_constraintlayout \
     androidx.car_car-cluster \
     car-arch-common \
-    car-media-common
+    car-media-common \
+    car-telephony-common
 
 include $(BUILD_PACKAGE)
 
diff --git a/tests/DirectRenderingClusterSample/res/layout/fragment_phone.xml b/tests/DirectRenderingClusterSample/res/layout/fragment_phone.xml
index 42fe24a..b7f903b 100644
--- a/tests/DirectRenderingClusterSample/res/layout/fragment_phone.xml
+++ b/tests/DirectRenderingClusterSample/res/layout/fragment_phone.xml
@@ -1,13 +1,61 @@
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    tools:context="com.google.experiments.client.pavelm.fakeclusterux.PhoneFragment">
+    android:orientation="vertical">
 
-    <!-- TODO: Update blank fragment layout -->
-    <TextView
+    <FrameLayout
+        android:id="@+id/user_profile_container"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:text="@string/hello_blank_fragment" />
+        android:gravity="center"
+        android:layout_height="0dp"
+        android:layout_weight="1">
 
-</FrameLayout>
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center"
+            android:orientation="vertical">
+            <ImageView
+                android:id="@+id/avatar"
+                android:layout_width="@dimen/large_avatar_icon_size"
+                android:layout_height="@dimen/large_avatar_icon_size"
+                android:layout_gravity="center"
+                android:scaleType="fitCenter" />
+            <TextView
+                android:id="@+id/title"
+                android:textAppearance="?android:attr/textAppearanceLarge"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingTop="@dimen/user_profile_title_padding_top"
+                android:focusable="true"
+                android:maxLines="1"
+                android:gravity="center"/>
+            <TextView
+                android:id="@+id/body"
+                android:textAppearance="?android:attr/textAppearanceSmall"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:paddingTop="@dimen/user_profile_body_padding_top"
+                android:gravity="center"
+                android:maxLines="1"/>
+        </LinearLayout>
+
+    </FrameLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/DirectRenderingClusterSample/res/values/dimens.xml b/tests/DirectRenderingClusterSample/res/values/dimens.xml
index 9ada025..21aec67 100644
--- a/tests/DirectRenderingClusterSample/res/values/dimens.xml
+++ b/tests/DirectRenderingClusterSample/res/values/dimens.xml
@@ -88,4 +88,11 @@
     <dimen name="fragment_playback_guide_margin_x">@*android:dimen/car_margin</dimen>
     <dimen name="fragment_playback_guide_margin_top">@*android:dimen/car_padding_4</dimen>
 
+    <!--                     -->
+    <!-- Communication Facet -->
+    <!--                     -->
+    <dimen name="user_profile_title_padding_top">@*android:dimen/car_padding_3</dimen>
+    <dimen name="user_profile_body_padding_top">@*android:dimen/car_padding_3</dimen>
+
+    <dimen name="large_avatar_icon_size">@dimen/car_large_avatar_size</dimen>
 </resources>
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/HeartBeatLiveData.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/HeartBeatLiveData.java
new file mode 100644
index 0000000..42116b8
--- /dev/null
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/HeartBeatLiveData.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2018 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 android.car.cluster.sample;
+
+import android.os.Handler;
+import android.os.Looper;
+
+import androidx.lifecycle.LiveData;
+
+/**
+ * Emits a true value in a fixed periodical pace. The first beat begins when this live data becomes
+ * active.
+ *
+ * <p> Note that if this heart beat is shared, the time can be less than the given interval between
+ * observation and first beat for the second observer.
+ */
+public class HeartBeatLiveData extends LiveData<Boolean> {
+    private long mPulseRate;
+    private Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
+
+    public HeartBeatLiveData(long rateInMillis) {
+        mPulseRate = rateInMillis;
+    }
+
+    @Override
+    protected void onActive() {
+        super.onActive();
+        mMainThreadHandler.post(mUpdateDurationRunnable);
+    }
+
+    @Override
+    protected void onInactive() {
+        super.onInactive();
+        mMainThreadHandler.removeCallbacks(mUpdateDurationRunnable);
+    }
+
+    private final Runnable mUpdateDurationRunnable = new Runnable() {
+        @Override
+        public void run() {
+            setValue(true);
+            mMainThreadHandler.postDelayed(this, mPulseRate);
+        }
+    };
+}
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
index 9f4f931..9b0273d 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/MainClusterActivity.java
@@ -57,6 +57,8 @@
 import androidx.lifecycle.ViewModelProviders;
 import androidx.viewpager.widget.ViewPager;
 
+import com.android.car.telephony.common.InMemoryPhoneBook;
+
 import java.lang.ref.WeakReference;
 import java.lang.reflect.InvocationTargetException;
 import java.util.HashMap;
@@ -107,6 +109,8 @@
     private final Runnable mRetryLaunchNavigationActivity = this::tryLaunchNavigationActivity;
     private int mNavigationDisplayId = NO_DISPLAY;
 
+    private int mPreviousFacet;
+
     /**
      * Description of a virtual display
      */
@@ -170,7 +174,7 @@
         }
 
         public void register(Context context) {
-            IntentFilter intentFilter =  new IntentFilter(ACTION_USER_UNLOCKED);
+            IntentFilter intentFilter = new IntentFilter(ACTION_USER_UNLOCKED);
             intentFilter.addAction(ACTION_USER_SWITCHED);
             context.registerReceiver(this, intentFilter);
         }
@@ -235,6 +239,24 @@
 
         mUserReceiver = new UserReceiver(this);
         mUserReceiver.register(this);
+
+        InMemoryPhoneBook.init(this);
+
+        PhoneFragmentViewModel phoneViewModel = ViewModelProviders.of(this).get(
+                PhoneFragmentViewModel.class);
+
+        phoneViewModel.setPhoneStateCallback(new PhoneFragmentViewModel.PhoneStateCallback() {
+            @Override
+            public void onCall() {
+                mPreviousFacet = mPager.getCurrentItem();
+                mOrderToFacet.get(1).mButton.requestFocus();
+            }
+
+            @Override
+            public void onDisconnect() {
+                mOrderToFacet.get(mPreviousFacet).mButton.requestFocus();
+            }
+        });
     }
 
     private <V> void registerSensor(TextView textView, LiveData<V> source) {
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/PhoneFragment.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/PhoneFragment.java
index 31c165f..c546f9d 100644
--- a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/PhoneFragment.java
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/PhoneFragment.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2017 The Android Open Source Project
+ * Copyright (C) 2019 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.
@@ -16,27 +16,62 @@
 package android.car.cluster.sample;
 
 import android.os.Bundle;
+import android.telephony.TelephonyManager;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentActivity;
+import androidx.lifecycle.ViewModelProviders;
+
+import com.android.car.telephony.common.TelecomUtils;
 
 /**
- * A simple {@link Fragment} subclass.
+ * Displays ongoing call information.
  */
 public class PhoneFragment extends Fragment {
+    private View mUserProfileContainerView;
 
+    private PhoneFragmentViewModel mViewModel;
 
     public PhoneFragment() {
         // Required empty public constructor
     }
 
+    @Nullable
     @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container,
-                             Bundle savedInstanceState) {
-        // Inflate the layout for this fragment
-        return inflater.inflate(R.layout.fragment_phone, container, false);
-    }
+    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
+            Bundle savedInstanceState) {
+        FragmentActivity activity = requireActivity();
+        mViewModel = ViewModelProviders.of(activity).get(
+                PhoneFragmentViewModel.class);
 
+        View fragmentView = inflater.inflate(R.layout.fragment_phone, container, false);
+        mUserProfileContainerView = fragmentView.findViewById(R.id.user_profile_container);
+
+        TextView body = mUserProfileContainerView.findViewById(R.id.body);
+        ImageView avatar = mUserProfileContainerView.findViewById(R.id.avatar);
+        TextView nameView = mUserProfileContainerView.findViewById(R.id.title);
+
+        mViewModel.getContactInfo().observe(getViewLifecycleOwner(), (contactInfo) -> {
+            nameView.setText(contactInfo.getDisplayName());
+            TelecomUtils.setContactBitmapAsync(getContext(),
+                    avatar, contactInfo.getContact(), contactInfo.getNumber());
+        });
+        mViewModel.getBody().observe(getViewLifecycleOwner(), body::setText);
+        mViewModel.getState().observe(getViewLifecycleOwner(), (state) -> {
+            if (state == TelephonyManager.CALL_STATE_IDLE) {
+                mUserProfileContainerView.setVisibility(View.GONE);
+            } else {
+                mUserProfileContainerView.setVisibility(View.VISIBLE);
+            }
+        });
+
+        return fragmentView;
+    }
 }
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/PhoneFragmentViewModel.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/PhoneFragmentViewModel.java
new file mode 100644
index 0000000..dd955e1
--- /dev/null
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/PhoneFragmentViewModel.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2019 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 android.car.cluster.sample;
+
+import static androidx.lifecycle.Transformations.map;
+
+import android.app.Application;
+import android.content.Context;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
+
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+
+import com.android.car.telephony.common.Contact;
+import com.android.car.telephony.common.InMemoryPhoneBook;
+import com.android.car.telephony.common.TelecomUtils;
+
+/**
+ * View model for {@link PhoneFragment}
+ */
+public final class PhoneFragmentViewModel extends AndroidViewModel {
+    private MutableLiveData<Long> mConnectTime = new MutableLiveData<>();
+    private MutableLiveData<Integer> mState = new MutableLiveData<>();
+    private MutableLiveData<String> mNumber = new MutableLiveData<>();
+    private LiveData<String> mBody;
+    private LiveData<ContactInfo> mContactInfo;
+
+    private PhoneStateCallback mCallback;
+
+    public PhoneFragmentViewModel(Application application) {
+        super(application);
+
+        TelephonyManager telephonyManager = (TelephonyManager) application.getSystemService(
+                Context.TELEPHONY_SERVICE);
+        telephonyManager.listen(new ClusterPhoneStateListener(),
+                PhoneStateListener.LISTEN_CALL_STATE);
+
+        mBody = new SelfRefreshDescriptionLiveData(getApplication(), mState, mNumber, mConnectTime);
+
+        mContactInfo = map(mNumber, (number) -> {
+            return new ContactInfo(number);
+        });
+    }
+
+    public interface PhoneStateCallback {
+        void onCall();
+
+        void onDisconnect();
+    }
+
+    public LiveData<Integer> getState() {
+        return mState;
+    }
+
+    public LiveData<String> getBody() {
+        return mBody;
+    }
+
+    public LiveData<ContactInfo> getContactInfo() {
+        return mContactInfo;
+    }
+
+    public void setPhoneStateCallback(PhoneStateCallback callback) {
+        mCallback = callback;
+    }
+
+    /**
+     * Listens to phone state changes
+     */
+    private class ClusterPhoneStateListener extends PhoneStateListener {
+        ClusterPhoneStateListener() {
+        }
+
+        @Override
+        public void onCallStateChanged(int state, String incomingNumber) {
+            super.onCallStateChanged(state, incomingNumber);
+
+            mState.setValue(state);
+            mNumber.setValue(incomingNumber);
+
+            if (state == TelephonyManager.CALL_STATE_IDLE) {
+                if (mCallback != null) {
+                    mCallback.onDisconnect();
+                }
+            } else if (state == TelephonyManager.CALL_STATE_RINGING) {
+                if (mCallback != null) {
+                    mCallback.onCall();
+                }
+            } else if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
+                mConnectTime.setValue(System.currentTimeMillis());
+                if (mCallback != null) {
+                    mCallback.onCall();
+                }
+            }
+        }
+    }
+
+    public class ContactInfo {
+        private String mNumber;
+        private String mDisplayName;
+        private Contact mContact;
+
+        public ContactInfo(String number) {
+            mNumber = number;
+            mDisplayName = TelecomUtils.getDisplayNameAndAvatarUri(getApplication(), number).first;
+            mContact = InMemoryPhoneBook.get().lookupContactEntry(number);
+        }
+
+        public String getNumber() {
+            return mNumber;
+        }
+
+        public String getDisplayName() {
+            return mDisplayName;
+        }
+
+        public Contact getContact() {
+            return mContact;
+        }
+    }
+}
diff --git a/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/SelfRefreshDescriptionLiveData.java b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/SelfRefreshDescriptionLiveData.java
new file mode 100644
index 0000000..79122c4
--- /dev/null
+++ b/tests/DirectRenderingClusterSample/src/android/car/cluster/sample/SelfRefreshDescriptionLiveData.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2019 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 android.car.cluster.sample;
+
+import android.content.Context;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MediatorLiveData;
+
+import com.android.car.telephony.common.TelecomUtils;
+
+/**
+ * Emits the description for the body in {@link PhoneFragmentViewModel}.
+ *
+ * This description may be the current duration of the call, call state, call type,
+ * or a combination of them.
+ *
+ * Possible strings:
+ * "Ringing"
+ * "1:05"
+ * "Mobile · Dialing"
+ * "Mobile · 1:05"
+ */
+public class SelfRefreshDescriptionLiveData extends MediatorLiveData<String> {
+    private final LiveData<Long> mConnectTimeLiveData;
+    private final LiveData<String> mNumberLiveData;
+    private final LiveData<Integer> mStateLiveData;
+    private final Context mContext;
+
+    /**
+     * @param stateLiveData       LiveData holding the {@link TelephonyManager} call state
+     * @param numberLiveData      LiveData holding the call number
+     * @param connectTimeLiveData LiveData holding the starting timestamp of the call
+     */
+    public SelfRefreshDescriptionLiveData(Context context,
+            LiveData<Integer> stateLiveData,
+            LiveData<String> numberLiveData,
+            LiveData<Long> connectTimeLiveData) {
+        mContext = context;
+        mNumberLiveData = numberLiveData;
+        mStateLiveData = stateLiveData;
+        mConnectTimeLiveData = connectTimeLiveData;
+
+        HeartBeatLiveData heartBeatLiveData = new HeartBeatLiveData(DateUtils.SECOND_IN_MILLIS);
+
+        addSource(stateLiveData, (trigger) -> updateDescription());
+        addSource(heartBeatLiveData, (trigger) -> updateDescription());
+        addSource(mNumberLiveData, (trigger) -> updateDescription());
+        addSource(mConnectTimeLiveData, (trigger) -> updateDescription());
+    }
+
+    private void updateDescription() {
+        String number = mNumberLiveData.getValue();
+        Integer callState = mStateLiveData.getValue();
+        Long connectTime = mConnectTimeLiveData.getValue();
+        if (callState != null) {
+            String newDescription = getCallInfoText(mContext, callState, number,
+                    connectTime != null ? connectTime : 0);
+
+            String oldDescription = getValue();
+            if (!newDescription.equals(oldDescription)) {
+                setValue(newDescription);
+            }
+        } else {
+            setValue("");
+        }
+    }
+
+    /**
+     * @return A formatted string that has information about the phone call
+     * Possible strings:
+     * "Mobile · Dialing"
+     * "Mobile · 1:05"
+     */
+    private String getCallInfoText(Context context, Integer callState, String number,
+            Long connectTime) {
+        CharSequence label = TelecomUtils.getTypeFromNumber(context, number);
+        String text = "";
+        if (callState == TelephonyManager.CALL_STATE_OFFHOOK) {
+            long duration = connectTime > 0 ? System.currentTimeMillis()
+                    - connectTime : 0;
+            String durationString = DateUtils.formatElapsedTime(duration / 1000);
+            if (!TextUtils.isEmpty(durationString) && !TextUtils.isEmpty(label)) {
+                text = context.getString(R.string.phone_label_with_info, label,
+                        durationString);
+            } else if (!TextUtils.isEmpty(durationString)) {
+                text = durationString;
+            } else if (!TextUtils.isEmpty(label)) {
+                text = (String) label;
+            }
+        } else {
+            String state = callStateToUiString(context, callState);
+            if (!TextUtils.isEmpty(label)) {
+                text = context.getString(R.string.phone_label_with_info, label, state);
+            } else {
+                text = state;
+            }
+        }
+
+        return text;
+    }
+
+    /**
+     * @return A string representation of the call state that can be presented to a user.
+     */
+    private String callStateToUiString(Context context, int state) {
+        switch (state) {
+            case TelephonyManager.CALL_STATE_IDLE:
+                return context.getString(R.string.call_state_call_ended);
+            case TelephonyManager.CALL_STATE_RINGING:
+                return context.getString(R.string.call_state_call_ringing);
+            case TelephonyManager.CALL_STATE_OFFHOOK:
+                return context.getString(R.string.call_state_call_active);
+            default:
+                return "";
+        }
+    }
+}
diff --git a/tests/UxRestrictionsSample/Android.mk b/tests/UxRestrictionsSample/Android.mk
index 3e006b4..3092876 100644
--- a/tests/UxRestrictionsSample/Android.mk
+++ b/tests/UxRestrictionsSample/Android.mk
@@ -42,7 +42,7 @@
 LOCAL_STATIC_JAVA_LIBRARIES += vehicle-hal-support-lib
 
 LOCAL_STATIC_ANDROID_LIBRARIES += \
-    androidx.legacy_legacy-support-v4 \
+    com.google.android.material_material \
     androidx.appcompat_appcompat
 
 LOCAL_JAVA_LIBRARIES += android.car
diff --git a/tests/UxRestrictionsSample/AndroidManifest.xml b/tests/UxRestrictionsSample/AndroidManifest.xml
index bd28e0d..5e1134e 100644
--- a/tests/UxRestrictionsSample/AndroidManifest.xml
+++ b/tests/UxRestrictionsSample/AndroidManifest.xml
@@ -14,11 +14,15 @@
      limitations under the License.
 -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.google.android.car.uxr.sample" android:sharedUserId="android.uid.system">
+          xmlns:tools="http://schemas.android.com/tools"
+          package="com.google.android.car.uxr.sample"
+          android:sharedUserId="android.uid.system">
 
     <uses-permission android:name="android.car.permission.CAR_DRIVING_STATE"/>
 
-    <application android:label="UxRestrictions Sample">
+    <application android:label="UxRestrictions Sample"
+                 android:appComponentFactory="androidx.core.app.CoreComponentFactory"
+                 tools:replace="android:appComponentFactory">
         <activity android:name=".MainActivity" android:theme="@style/AppTheme" android:launchMode="singleTask">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
diff --git a/tests/UxRestrictionsSample/res/layout/fragment_configuration_dialog.xml b/tests/UxRestrictionsSample/res/layout/fragment_configuration_dialog.xml
new file mode 100644
index 0000000..6880052
--- /dev/null
+++ b/tests/UxRestrictionsSample/res/layout/fragment_configuration_dialog.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 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.
+-->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:orientation="vertical"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:gravity="center">
+
+    <TextView
+        android:layout_height="wrap_content"
+        android:layout_width="match_parent"
+        android:text="@string/set_uxr_config_dialog_title"
+        android:gravity="center"/>
+
+    <com.google.android.material.tabs.TabLayout
+        android:id="@+id/tab_layout"
+        android:layout_height="wrap_content"
+        android:layout_width="wrap_content"
+        app:tabMode="fixed"/>
+
+    <androidx.viewpager.widget.ViewPager
+        android:id="@+id/view_pager"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_weight="1"/>
+
+    <Button
+        android:id="@+id/positive_button"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="confirm"/>
+</LinearLayout>
diff --git a/tests/UxRestrictionsSample/res/layout/main_activity.xml b/tests/UxRestrictionsSample/res/layout/main_activity.xml
index 1d21b50..e8634ee 100644
--- a/tests/UxRestrictionsSample/res/layout/main_activity.xml
+++ b/tests/UxRestrictionsSample/res/layout/main_activity.xml
@@ -15,16 +15,10 @@
 -->
 <LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="horizontal"
+    android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
-
-  <LinearLayout
-      android:layout_height="match_parent"
-      android:layout_width="0dp"
-      android:layout_weight="1"
-      android:orientation="vertical">
-
+    <!-- Section: Current Status -->
     <TextView
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
@@ -71,6 +65,7 @@
         android:textSize="@dimen/info_text_size"
         android:textAppearance="?android:textAppearanceLarge"/>
 
+    <!-- Section: Available Actions -->
     <View
         android:layout_width="match_parent"
         android:layout_height="1dp"
@@ -88,43 +83,47 @@
 
     <LinearLayout
         android:layout_width="match_parent"
-        android:layout_height="wrap_content">
-      <Button
-          android:id="@+id/toggle_status"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:layout_marginLeft="@dimen/section_padding"
-          android:padding="@dimen/section_padding"
-          android:text="@string/disable_uxr"
-          android:textAllCaps="false"
-          android:textSize="@dimen/info_text_size"/>
-      <Button
-          android:id="@+id/show_staged_config"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:layout_marginLeft="@dimen/section_padding"
-          android:padding="@dimen/section_padding"
-          android:text="@string/show_staged_config"
-          android:textAllCaps="false"
-          android:textSize="@dimen/info_text_size"/>
-      <Button
-          android:id="@+id/show_prod_config"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:layout_marginLeft="@dimen/section_padding"
-          android:padding="@dimen/section_padding"
-          android:text="@string/show_prod_config"
-          android:textAllCaps="false"
-          android:textSize="@dimen/info_text_size"/>
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
+        <Button
+            android:id="@+id/toggle_status"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="@dimen/section_padding"
+            android:padding="@dimen/section_padding"
+            android:text="@string/disable_uxr"
+            android:textAllCaps="false"
+            android:textSize="@dimen/info_text_size"/>
+        <Button
+            android:id="@+id/show_staged_config"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="@dimen/section_padding"
+            android:padding="@dimen/section_padding"
+            android:text="@string/show_staged_config"
+            android:textAllCaps="false"
+            android:textSize="@dimen/info_text_size"/>
+        <Button
+            android:id="@+id/show_prod_config"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="@dimen/section_padding"
+            android:padding="@dimen/section_padding"
+            android:text="@string/show_prod_config"
+            android:textAllCaps="false"
+            android:textSize="@dimen/info_text_size"/>
+        <Button
+            android:id="@+id/toggle_passenger_mode"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_marginLeft="@dimen/section_padding"
+            android:padding="@dimen/section_padding"
+            android:text="@string/enable_passenger_mode"
+            android:textAllCaps="false"
+            android:textSize="@dimen/info_text_size"/>
     </LinearLayout>
 
-    <View
-        android:layout_width="match_parent"
-        android:layout_height="1dp"
-        android:layout_marginTop="@dimen/section_padding"
-        android:layout_marginBottom="10dp"
-        android:background="@android:color/darker_gray"/>
-
+    <!-- Section: Save UX Restrictions For Next Boot -->
     <View
         android:layout_width="match_parent"
         android:layout_height="1dp"
@@ -140,18 +139,13 @@
         android:textSize="@dimen/header_text_size"
         android:textAppearance="?android:textAppearanceLarge"/>
 
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content">
-      <Button
-          android:id="@+id/save_uxr_config"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:padding="@dimen/section_padding"
-          android:text="@string/save_uxr_config"
-          android:textSize="@dimen/info_text_size"/>
-    </LinearLayout>
-  </LinearLayout>
+    <Button
+        android:id="@+id/save_uxr_config"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:padding="@dimen/section_padding"
+        android:text="@string/save_uxr_config"
+        android:textSize="@dimen/info_text_size"/>
 </LinearLayout>
 
 
diff --git a/tests/UxRestrictionsSample/res/values/strings.xml b/tests/UxRestrictionsSample/res/values/strings.xml
index aae0e0d..c789067 100644
--- a/tests/UxRestrictionsSample/res/values/strings.xml
+++ b/tests/UxRestrictionsSample/res/values/strings.xml
@@ -25,6 +25,8 @@
     <string name="disable_uxr" translatable="false">Disable Ux Restriction Engine</string>
     <string name="show_staged_config" translatable="false">Show Staged Config</string>
     <string name="show_prod_config" translatable="false">Show Production Config</string>
+    <string name="enable_passenger_mode" translatable="false">Enable Passenger Mode</string>
+    <string name="disable_passenger_mode" translatable="false">Disable Passenger Mode</string>
     <string name="save_uxr_config_header" translatable="false"><u>Save UX Restrictions For Next Boot</u></string>
     <string name="save_uxr_config" translatable="false">Save UX Restrictions</string>
     <string name="set_uxr_config_dialog_title" translatable="false">Select restrictions for IDLING/MOVING</string>
@@ -34,4 +36,6 @@
     <string name="no_prod_config" translatable="false">There is no production configuration found</string>
     <string name="staged_config_title" translatable="false">Staged Config</string>
     <string name="prod_config_title" translatable="false">Production Config</string>
+    <string name="tab_baseline" translatable="false">Baseline</string>
+    <string name="tab_passenger" translatable="false">Passenger</string>
 </resources>
diff --git a/tests/UxRestrictionsSample/res/values/styles.xml b/tests/UxRestrictionsSample/res/values/styles.xml
index b752323..4ddef1f 100644
--- a/tests/UxRestrictionsSample/res/values/styles.xml
+++ b/tests/UxRestrictionsSample/res/values/styles.xml
@@ -15,7 +15,7 @@
 -->
 <resources>
 
-    <style name="AppTheme" parent="@style/android:Theme.DeviceDefault.NoActionBar">
+    <style name="AppTheme" parent="Theme.AppCompat.NoActionBar">
         <item name="android:windowBackground">@android:color/black</item>
     </style>
 </resources>
\ No newline at end of file
diff --git a/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/ConfigurationDialogFragment.java b/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/ConfigurationDialogFragment.java
new file mode 100644
index 0000000..ab4a278
--- /dev/null
+++ b/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/ConfigurationDialogFragment.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2019 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.google.android.car.uxr.sample;
+
+import android.car.drivingstate.CarUxRestrictions;
+import android.content.Context;
+import android.os.Bundle;
+import android.util.SparseBooleanArray;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.ListView;
+
+import androidx.fragment.app.DialogFragment;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.fragment.app.FragmentPagerAdapter;
+import androidx.fragment.app.ListFragment;
+import androidx.viewpager.widget.ViewPager;
+
+import com.google.android.material.tabs.TabLayout;
+
+/**
+ * DialogFragment that selects UX restrictions for configuration.
+ *
+ * <p>Supports baseline and passenger mode.
+ */
+public class ConfigurationDialogFragment extends DialogFragment {
+
+    static ConfigurationDialogFragment newInstance() {
+        return new ConfigurationDialogFragment();
+    }
+
+    /**
+     * Callback to be invoked when "confirm" button in the dialog is clicked.
+     */
+    interface OnConfirmListener {
+        /**
+         * Called when "confirm" button in the dialog is clicked.
+         *
+         * @param baseline Restrictions selected for baseline mode. See
+         *                 {@link CarUxRestrictions#getActiveRestrictions()}.
+         * @param passenger Restrictions selected for baseline mode.
+         */
+        void onConfirm(int baseline, int passenger);
+    }
+
+    private Button mPositiveButton;
+    private TabLayout mTabLayout;
+    private ViewPager mViewPager;
+    private UxRestrictionsListFragmentPagerAdapter mAdapter;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        return inflater.inflate(R.layout.fragment_configuration_dialog, container,
+                /* attachToRoot= */ false);
+    }
+
+    @Override
+    public void onViewCreated(View view, Bundle savedInstanceState) {
+        super.onViewCreated(view, savedInstanceState);
+
+        mViewPager = view.findViewById(R.id.view_pager);
+        mViewPager.setAdapter(mAdapter);
+
+        mTabLayout = view.findViewById(R.id.tab_layout);
+        mTabLayout.setupWithViewPager(mViewPager);
+
+        mPositiveButton = view.findViewById(R.id.positive_button);
+        mPositiveButton.setOnClickListener(v -> {
+            ((OnConfirmListener) getActivity()).onConfirm(
+                    mAdapter.mBaselineRestrictions, mAdapter.mPassengerRestrictions);
+            dismiss();
+        });
+    }
+
+    @Override
+    public void onAttach(Context context) {
+        super.onAttach(context);
+        mAdapter = new UxRestrictionsListFragmentPagerAdapter(context, getChildFragmentManager());
+    }
+
+    public static class UxRestrictionsListFragment extends ListFragment {
+
+        public interface OnUxRestrictionsSelectedListener {
+            void onUxRestrictionsSelected(int restrictions);
+        }
+
+        static UxRestrictionsListFragment newInstance() {
+            return new UxRestrictionsListFragment();
+        }
+
+        /**
+         * This field translate a UX restriction value to a string name.
+         *
+         * <p>Order of strings should be fixed. Index of string is based on number of bits shifted
+         * in value of the constants. Namely, "NO_VIDEO" at index 4 maps to value of
+         * {@link android.car.drivingstate.CarUxRestrictions#UX_RESTRICTIONS_NO_VIDEO} (0x1 << 4).
+         */
+        private static final CharSequence[] UX_RESTRICTION_NAMES = new CharSequence[]{
+                "NO_DIALPAD",
+                "NO_FILTERING",
+                "LIMIT_STRING_LENGTH",
+                "NO_KEYBOARD",
+                "NO_VIDEO",
+                "LIMIT_CONTENT",
+                "NO_SETUP",
+                "NO_TEXT_MESSAGE",
+                "NO_VOICE_TRANSCRIPTION",
+        };
+        private OnUxRestrictionsSelectedListener mListener;
+
+        public void setOnUxRestrictionsSelectedListener(OnUxRestrictionsSelectedListener listener) {
+            mListener = listener;
+        }
+
+        private int getSelectedUxRestrictions() {
+            int selectedRestrictions = 0;
+            SparseBooleanArray selected = getListView().getCheckedItemPositions();
+            for (int i = 0; i < UX_RESTRICTION_NAMES.length; i++) {
+                if (selected.get(i)) {
+                    selectedRestrictions += 1 << i;
+                }
+            }
+            return selectedRestrictions;
+        }
+
+        @Override
+        public void onViewCreated(View view, Bundle savedInstanceState) {
+            super.onViewCreated(view, savedInstanceState);
+
+            getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
+            getListView().setOnItemClickListener((parent, v, position, id) -> {
+                if (mListener != null) {
+                    mListener.onUxRestrictionsSelected(getSelectedUxRestrictions());
+                }
+            });
+            setListAdapter(new ArrayAdapter<CharSequence>(
+                    getContext(),
+                    android.R.layout.simple_list_item_multiple_choice,
+                    UX_RESTRICTION_NAMES));
+        }
+    }
+
+    private static class UxRestrictionsListFragmentPagerAdapter extends FragmentPagerAdapter {
+        private final Context mContext;
+        int mBaselineRestrictions;
+        int mPassengerRestrictions;
+
+        private final int[] mPageTitles = new int[]{
+                R.string.tab_baseline, R.string.tab_passenger};
+
+        UxRestrictionsListFragmentPagerAdapter(Context context, FragmentManager fragmentManager) {
+            super(fragmentManager);
+            mContext = context;
+        }
+
+        @Override
+        public int getCount() {
+            return mPageTitles.length;
+        }
+
+        @Override
+        public Fragment getItem(int index) {
+            return UxRestrictionsListFragment.newInstance();
+        }
+
+        @Override
+        public CharSequence getPageTitle(int position) {
+            return mContext.getString(mPageTitles[position]);
+        }
+
+        @Override
+        public Object instantiateItem(ViewGroup container, int position) {
+            UxRestrictionsListFragment fragment = (UxRestrictionsListFragment)
+                    super.instantiateItem(container, position);
+            switch (position) {
+                case 0:
+                    fragment.setOnUxRestrictionsSelectedListener(
+                            restrictions -> mBaselineRestrictions = restrictions);
+                    break;
+                case 1:
+                    fragment.setOnUxRestrictionsSelectedListener(
+                            restrictions -> mPassengerRestrictions = restrictions);
+                    break;
+                default:
+                    throw new IllegalStateException("Unsupported page index " + position);
+            }
+            return fragment;
+        }
+
+    }
+}
diff --git a/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/MainActivity.java b/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/MainActivity.java
index 60268ea..870468e 100644
--- a/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/MainActivity.java
+++ b/tests/UxRestrictionsSample/src/com/google/android/car/uxr/sample/MainActivity.java
@@ -18,8 +18,9 @@
 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_IDLING;
 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_MOVING;
 import static android.car.drivingstate.CarDrivingStateEvent.DRIVING_STATE_PARKED;
+import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_BASELINE;
+import static android.car.drivingstate.CarUxRestrictionsManager.UX_RESTRICTION_MODE_PASSENGER;
 
-import android.app.Activity;
 import android.app.AlertDialog;
 import android.car.Car;
 import android.car.content.pm.CarPackageManager;
@@ -27,85 +28,107 @@
 import android.car.drivingstate.CarDrivingStateManager;
 import android.car.drivingstate.CarUxRestrictions;
 import android.car.drivingstate.CarUxRestrictionsConfiguration;
+import android.car.drivingstate.CarUxRestrictionsConfiguration.DrivingStateRestrictions;
 import android.car.drivingstate.CarUxRestrictionsManager;
-import android.content.ComponentName;
-import android.content.ServiceConnection;
 import android.os.Bundle;
-import android.os.IBinder;
 import android.util.JsonWriter;
-import android.util.Log;
 import android.widget.Button;
 import android.widget.TextView;
 
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.fragment.app.DialogFragment;
+
 import java.io.CharArrayWriter;
 
 /**
  * Sample app that uses components in car support library to demonstrate Car drivingstate UXR
  * status.
  */
-public class MainActivity extends Activity {
-    public static final String TAG = "drivingstate";
+public class MainActivity extends AppCompatActivity
+        implements ConfigurationDialogFragment.OnConfirmListener {
 
-    // Order of elements is based on number of bits shifted in value of the constants.
-    private static final CharSequence[] UX_RESTRICTION_NAMES = new CharSequence[]{
-            "BASELINE",
-            "NO_DIALPAD",
-            "NO_FILTERING",
-            "LIMIT_STRING_LENGTH",
-            "NO_KEYBOARD",
-            "NO_VIDEO",
-            "LIMIT_CONTENT",
-            "NO_SETUP",
-            "NO_TEXT_MESSAGE",
-            "NO_VOICE_TRANSCRIPTION",
-    };
+    public static final String TAG = "UxRDemo";
+
+    private static final String DIALOG_FRAGMENT_TAG = "dialog_fragment_tag";
 
     private Car mCar;
     private CarDrivingStateManager mCarDrivingStateManager;
     private CarUxRestrictionsManager mCarUxRestrictionsManager;
     private CarPackageManager mCarPackageManager;
+
+    private CarUxRestrictionsManager.OnUxRestrictionsChangedListener mUxRChangeListener =
+            this::updateUxRText;
+    private CarDrivingStateManager.CarDrivingStateEventListener mDrvStateChangeListener =
+            this::updateDrivingStateText;
+
     private TextView mDrvStatus;
     private TextView mDistractionOptStatus;
     private TextView mUxrStatus;
     private Button mToggleButton;
-    private Button mSampleMsgButton;
     private Button mSaveUxrConfigButton;
     private Button mShowStagedConfig;
     private Button mShowProdConfig;
+    private Button mTogglePassengerMode;
 
     private boolean mEnableUxR;
 
-    private final ServiceConnection mCarConnectionListener =
-            new ServiceConnection() {
-                @Override
-                public void onServiceConnected(ComponentName name, IBinder iBinder) {
-                    Log.d(TAG, "Connected to " + name.flattenToString());
-                    // Get Driving State & UXR manager
-                    mCarDrivingStateManager = (CarDrivingStateManager) mCar.getCarManager(
-                            Car.CAR_DRIVING_STATE_SERVICE);
-                    mCarUxRestrictionsManager = (CarUxRestrictionsManager) mCar.getCarManager(
-                            Car.CAR_UX_RESTRICTION_SERVICE);
-                    mCarPackageManager = (CarPackageManager) mCar.getCarManager(
-                            Car.PACKAGE_SERVICE);
-                    if (mCarDrivingStateManager != null) {
-                        mCarDrivingStateManager.registerListener(mDrvStateChangeListener);
-                        updateDrivingStateText(
-                                mCarDrivingStateManager.getCurrentCarDrivingState());
-                    }
-                    if (mCarUxRestrictionsManager != null) {
-                        mCarUxRestrictionsManager.registerListener(mUxRChangeListener);
-                        updateUxRText(mCarUxRestrictionsManager.getCurrentCarUxRestrictions());
-                    }
-                }
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
 
-                @Override
-                public void onServiceDisconnected(ComponentName name) {
-                    Log.d(TAG, "Disconnected from " + name.flattenToString());
-                    mCarDrivingStateManager = null;
-                    mCarUxRestrictionsManager = null;
-                    mCarPackageManager = null;
-                }
-            };
+        setContentView(R.layout.main_activity);
+
+        mDrvStatus = findViewById(R.id.driving_state);
+        mDistractionOptStatus = findViewById(R.id.do_status);
+        mUxrStatus = findViewById(R.id.uxr_status);
+
+        mToggleButton = findViewById(R.id.toggle_status);
+        mToggleButton.setOnClickListener(v -> updateToggleUxREnable());
+
+        mSaveUxrConfigButton = findViewById(R.id.save_uxr_config);
+        mSaveUxrConfigButton.setOnClickListener(v -> showConfigurationDialog());
+
+        mShowStagedConfig = findViewById(R.id.show_staged_config);
+        mShowStagedConfig.setOnClickListener(v -> showStagedUxRestrictionsConfig());
+        mShowProdConfig = findViewById(R.id.show_prod_config);
+        mShowProdConfig.setOnClickListener(v -> showProdUxRestrictionsConfig());
+
+        mTogglePassengerMode = findViewById(R.id.toggle_passenger_mode);
+        mTogglePassengerMode.setOnClickListener(v -> togglePassengerMode());
+
+        // Connect to car service
+        mCar = Car.createCar(this);
+
+        mCarDrivingStateManager = (CarDrivingStateManager) mCar.getCarManager(
+                Car.CAR_DRIVING_STATE_SERVICE);
+        mCarUxRestrictionsManager = (CarUxRestrictionsManager) mCar.getCarManager(
+                Car.CAR_UX_RESTRICTION_SERVICE);
+        mCarPackageManager = (CarPackageManager) mCar.getCarManager(
+                Car.PACKAGE_SERVICE);
+        if (mCarDrivingStateManager != null) {
+            mCarDrivingStateManager.registerListener(mDrvStateChangeListener);
+            updateDrivingStateText(
+                    mCarDrivingStateManager.getCurrentCarDrivingState());
+        }
+        if (mCarUxRestrictionsManager != null) {
+            mCarUxRestrictionsManager.registerListener(mUxRChangeListener);
+            updateUxRText(mCarUxRestrictionsManager.getCurrentCarUxRestrictions());
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        if (mCarUxRestrictionsManager != null) {
+            mCarUxRestrictionsManager.unregisterListener();
+        }
+        if (mCarDrivingStateManager != null) {
+            mCarDrivingStateManager.unregisterListener();
+        }
+        if (mCar != null) {
+            mCar.disconnect();
+        }
+    }
 
     private void updateUxRText(CarUxRestrictions restrictions) {
         mDistractionOptStatus.setText(
@@ -159,64 +182,54 @@
         mDrvStatus.requestLayout();
     }
 
-    private CarUxRestrictionsManager.OnUxRestrictionsChangedListener mUxRChangeListener =
-            this::updateUxRText;
+    private void togglePassengerMode() {
+        if (mCarUxRestrictionsManager == null) {
+            return;
+        }
 
+        int mode = mCarUxRestrictionsManager.getRestrictionMode();
+        switch (mode) {
+            case UX_RESTRICTION_MODE_BASELINE:
+                mCarUxRestrictionsManager.setRestrictionMode(UX_RESTRICTION_MODE_PASSENGER);
+                mTogglePassengerMode.setText(R.string.disable_passenger_mode);
+                break;
+            case UX_RESTRICTION_MODE_PASSENGER:
+                mCarUxRestrictionsManager.setRestrictionMode(UX_RESTRICTION_MODE_BASELINE);
+                mTogglePassengerMode.setText(R.string.enable_passenger_mode);
+                break;
+            default:
+                throw new IllegalStateException("Unrecognized restriction mode " + mode);
+        }
+    }
 
-    private CarDrivingStateManager.CarDrivingStateEventListener mDrvStateChangeListener =
-            this::updateDrivingStateText;
+    private void showConfigurationDialog() {
+        DialogFragment dialogFragment = ConfigurationDialogFragment.newInstance();
+        dialogFragment.show(getSupportFragmentManager(), DIALOG_FRAGMENT_TAG);
+    }
 
     @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
+    public void onConfirm(int baseline, int passenger) {
 
-        setContentView(R.layout.main_activity);
-
-        mDrvStatus = findViewById(R.id.driving_state);
-        mDistractionOptStatus = findViewById(R.id.do_status);
-        mUxrStatus = findViewById(R.id.uxr_status);
-        mToggleButton = findViewById(R.id.toggle_status);
-
-        mSaveUxrConfigButton = findViewById(R.id.save_uxr_config);
-        mSaveUxrConfigButton.setOnClickListener(v -> saveUxrConfig());
-
-        mShowStagedConfig = findViewById(R.id.show_staged_config);
-        mShowStagedConfig.setOnClickListener(v -> showStagedUxRestrictionsConfig());
-        mShowProdConfig = findViewById(R.id.show_prod_config);
-        mShowProdConfig.setOnClickListener(v -> showProdUxRestrictionsConfig());
-        mToggleButton.setOnClickListener(v -> updateToggleUxREnable());
-
-        // Connect to car service
-        mCar = Car.createCar(this, mCarConnectionListener);
-        mCar.connect();
-    }
-
-    private void saveUxrConfig() {
-        // Pop up a dialog to build the IDLING restrictions.
-        boolean[] selected = new boolean[UX_RESTRICTION_NAMES.length];
-        new AlertDialog.Builder(this)
-                .setTitle(R.string.set_uxr_config_dialog_title)
-                .setMultiChoiceItems(UX_RESTRICTION_NAMES, null,
-                        (dialog, which, isChecked) -> selected[which] = isChecked)
-                .setPositiveButton(R.string.set_uxr_config_dialog_positive_button,
-                        (dialog, id) -> setUxRestrictionsConfig(selected))
-                .setNegativeButton(R.string.set_uxr_config_dialog_negative_button, null)
-                .show();
-    }
-
-    private void setUxRestrictionsConfig(boolean[] selected) {
-        int selectedRestrictions = 0;
-        // Iteration starts at 1 because 0 is BASELINE (no restrictions).
-        for (int i = 1; i < selected.length; i++) {
-            if (selected[i]) {
-                selectedRestrictions += 1 << (i - 1);
-            }
-        }
-        boolean reqOpt = selectedRestrictions != 0;
         CarUxRestrictionsConfiguration config = new CarUxRestrictionsConfiguration.Builder()
                 .setUxRestrictions(DRIVING_STATE_PARKED, false, 0)
-                .setUxRestrictions(DRIVING_STATE_IDLING, reqOpt, selectedRestrictions)
-                .setUxRestrictions(DRIVING_STATE_MOVING, reqOpt, selectedRestrictions)
+                .setUxRestrictions(DRIVING_STATE_MOVING,
+                        new DrivingStateRestrictions()
+                                .setDistractionOptimizationRequired(baseline != 0)
+                                .setRestrictions(baseline))
+                .setUxRestrictions(DRIVING_STATE_MOVING,
+                        new DrivingStateRestrictions()
+                                .setMode(CarUxRestrictionsManager.UX_RESTRICTION_MODE_PASSENGER)
+                                .setDistractionOptimizationRequired(passenger != 0)
+                                .setRestrictions(passenger))
+                .setUxRestrictions(DRIVING_STATE_IDLING,
+                        new DrivingStateRestrictions()
+                                .setDistractionOptimizationRequired(baseline != 0)
+                                .setRestrictions(baseline))
+                .setUxRestrictions(DRIVING_STATE_IDLING,
+                        new DrivingStateRestrictions()
+                                .setMode(CarUxRestrictionsManager.UX_RESTRICTION_MODE_PASSENGER)
+                                .setDistractionOptimizationRequired(passenger != 0)
+                                .setRestrictions(passenger))
                 .build();
 
         mCarUxRestrictionsManager.saveUxRestrictionsConfigurationForNextBoot(config);
@@ -247,8 +260,7 @@
 
     private void showProdUxRestrictionsConfig() {
         try {
-            CarUxRestrictionsConfiguration prodConfig =
-                    mCarUxRestrictionsManager.getConfig();
+            CarUxRestrictionsConfiguration prodConfig = mCarUxRestrictionsManager.getConfig();
             if (prodConfig == null) {
                 new AlertDialog.Builder(this)
                         .setMessage(R.string.no_prod_config)
@@ -267,19 +279,4 @@
             e.printStackTrace();
         }
     }
-
-    @Override
-    protected void onDestroy() {
-        super.onDestroy();
-        if (mCarUxRestrictionsManager != null) {
-            mCarUxRestrictionsManager.unregisterListener();
-        }
-        if (mCarDrivingStateManager != null) {
-            mCarDrivingStateManager.unregisterListener();
-        }
-        if (mCar != null) {
-            mCar.disconnect();
-        }
-    }
 }
-
diff --git a/tests/carservice_test/src/com/android/car/SystemActivityMonitoringServiceTest.java b/tests/carservice_test/src/com/android/car/SystemActivityMonitoringServiceTest.java
index 906192a..4899246 100644
--- a/tests/carservice_test/src/com/android/car/SystemActivityMonitoringServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/SystemActivityMonitoringServiceTest.java
@@ -22,7 +22,6 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.os.Bundle;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.MediumTest;
@@ -142,8 +141,8 @@
     public static class ActivityThatFinishesImmediately extends Activity {
 
         @Override
-        protected void onCreate(Bundle savedInstanceState) {
-            super.onCreate(savedInstanceState);
+        protected void onResume() {
+            super.onResume();
             finish();
         }
     }
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
index ebeef0d..c81f887 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
@@ -57,8 +57,8 @@
  *
  * The following mocks are used:
  * <ol>
- *   <li> {@link Context} provides system services and resources.
- *   <li> {@link CarUserManagerHelper} provides user info and actions.
+ * <li> {@link Context} provides system services and resources.
+ * <li> {@link CarUserManagerHelper} provides user info and actions.
  * <ol/>
  */
 @RunWith(AndroidJUnit4.class)
@@ -134,14 +134,16 @@
     }
 
     /**
-     * Test that the {@link CarUserService} disable modify account for user 0 upon user 0 unlock.
+     * Test that the {@link CarUserService} does set the disable modify account permission for
+     * user 0 upon user 0 unlock when user 0 is headless.
      */
     @Test
-    public void testDisableModifyAccountsForSystemUserOnFirstRun() {
+    public void testDisableModifyAccountsForHeadlessSystemUserOnFirstRun() {
         // Mock system user.
         UserInfo systemUser = new UserInfo();
         systemUser.id = UserHandle.USER_SYSTEM;
         doReturn(systemUser).when(mCarUserManagerHelper).getSystemUserInfo();
+        doReturn(true).when(mCarUserManagerHelper).isHeadlessSystemUser();
 
         mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
 
@@ -150,6 +152,24 @@
     }
 
     /**
+     * Test that the {@link CarUserService} does not set the disable modify account permission for
+     * user 0 upon user 0 unlock when user 0 is not headless.
+     */
+    @Test
+    public void testDisableModifyAccountsForRegularSystemUserOnFirstRun() {
+        // Mock system user.
+        UserInfo systemUser = new UserInfo();
+        systemUser.id = UserHandle.USER_SYSTEM;
+        doReturn(systemUser).when(mCarUserManagerHelper).getSystemUserInfo();
+        doReturn(false).when(mCarUserManagerHelper).isHeadlessSystemUser();
+
+        mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
+
+        verify(mCarUserManagerHelper, never())
+                .setUserRestriction(systemUser, UserManager.DISALLOW_MODIFY_ACCOUNTS, true);
+    }
+
+    /**
      * Test that the {@link CarUserService} does not set restrictions on user 0 if they have already
      * been set.
      */
@@ -168,10 +188,12 @@
     }
 
     /**
-     * Test that the {@link CarUserService} disable location service for user 0 upon first run.
+     * Test that the {@link CarUserService} disables the location service for headless user 0 upon
+     * first run.
      */
     @Test
-    public void testDisableLocationForSystemUserOnFirstRun() {
+    public void testDisableLocationForHeadlessSystemUserOnFirstRun() {
+        doReturn(true).when(mCarUserManagerHelper).isHeadlessSystemUser();
         mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
 
         verify(mLocationManager).setLocationEnabledForUser(
@@ -179,6 +201,19 @@
     }
 
     /**
+     * Test that the {@link CarUserService} does not disable the location service for regular user 0
+     * upon first run.
+     */
+    @Test
+    public void testDisableLocationForRegularSystemUserOnFirstRun() {
+        doReturn(false).when(mCarUserManagerHelper).isHeadlessSystemUser();
+        mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
+
+        verify(mLocationManager, never()).setLocationEnabledForUser(
+                /* enabled= */ false, UserHandle.of(UserHandle.USER_SYSTEM));
+    }
+
+    /**
      * Test that the {@link CarUserService} updates last active user on user switch intent.
      */
     @Test
@@ -263,7 +298,7 @@
         // user 2 background, ignore in restart list
         mCarUserService.setUserLockStatus(user2, true);
         mCarUserService.setUserLockStatus(user1, false);
-        assertEquals(new Integer[]{ user1 },
+        assertEquals(new Integer[]{user1},
                 mCarUserService.getBackgroundUsersToRestart().toArray());
 
         doReturn(user3).when(mCarUserManagerHelper).getCurrentForegroundUserId();
@@ -313,10 +348,10 @@
         doReturn(true).when(mMockedIActivityManager).startUserInBackground(user2);
         doReturn(true).when(mMockedIActivityManager).unlockUser(user2,
                 null, null, null);
-        assertEquals(new Integer[] { user2 },
+        assertEquals(new Integer[]{user2},
                 mCarUserService.startAllBackgroundUsers().toArray());
         mCarUserService.setUserLockStatus(user2, true);
-        assertEquals(new Integer[] { user3, user2 },
+        assertEquals(new Integer[]{user3, user2},
                 mCarUserService.getBackgroundUsersToRestart().toArray());
 
         doReturn(ActivityManager.USER_OP_SUCCESS).when(mMockedIActivityManager).stopUser(user2,
@@ -324,10 +359,10 @@
         // should not stop the current fg user
         assertFalse(mCarUserService.stopBackgroundUser(user3));
         assertTrue(mCarUserService.stopBackgroundUser(user2));
-        assertEquals(new Integer[] { user3, user2 },
+        assertEquals(new Integer[]{user3, user2},
                 mCarUserService.getBackgroundUsersToRestart().toArray());
         mCarUserService.setUserLockStatus(user2, false);
-        assertEquals(new Integer[] { user3, user2 },
+        assertEquals(new Integer[]{user3, user2},
                 mCarUserService.getBackgroundUsersToRestart().toArray());
     }