Merge "Send global type property event in register" into pi-dev
diff --git a/car-lib/src/android/car/hardware/property/CarPropertyManager.java b/car-lib/src/android/car/hardware/property/CarPropertyManager.java
index ff8c4db..373c23a 100644
--- a/car-lib/src/android/car/hardware/property/CarPropertyManager.java
+++ b/car-lib/src/android/car/hardware/property/CarPropertyManager.java
@@ -178,8 +178,12 @@
      */
     public void unregisterListener(CarPropertyEventListener listener) {
         synchronized (mActivePropertyListener) {
+            int [] propertyIds = new int[mActivePropertyListener.size()];
             for (int i = 0; i < mActivePropertyListener.size(); i++) {
-                doUnregisterListenerLocked(listener, mActivePropertyListener.keyAt(i));
+                propertyIds[i] = mActivePropertyListener.keyAt(i);
+            }
+            for (int prop : propertyIds) {
+                doUnregisterListenerLocked(listener, prop);
             }
         }
     }
diff --git a/car-lib/src/android/car/user/CarUserManagerHelper.java b/car-lib/src/android/car/user/CarUserManagerHelper.java
index c588547..ce50943 100644
--- a/car-lib/src/android/car/user/CarUserManagerHelper.java
+++ b/car-lib/src/android/car/user/CarUserManagerHelper.java
@@ -28,6 +28,7 @@
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.os.Bundle;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -62,6 +63,18 @@
     private static final Set<String> DEFAULT_NON_ADMIN_RESTRICTIONS = Sets.newArraySet(
             UserManager.DISALLOW_FACTORY_RESET
     );
+    /**
+     * Default set of restrictions for Guest users.
+     */
+    private static final Set<String> DEFAULT_GUEST_RESTRICTIONS = Sets.newArraySet(
+            UserManager.DISALLOW_FACTORY_RESET,
+            UserManager.DISALLOW_REMOVE_USER,
+            UserManager.DISALLOW_MODIFY_ACCOUNTS,
+            UserManager.DISALLOW_OUTGOING_CALLS,
+            UserManager.DISALLOW_SMS,
+            UserManager.DISALLOW_INSTALL_APPS,
+            UserManager.DISALLOW_UNINSTALL_APPS
+    );
 
     private final Context mContext;
     private final UserManager mUserManager;
@@ -184,7 +197,7 @@
 
     /**
      * Get user id for the initial user to boot into. This is only applicable for headless
-     * user 0 model.
+     * system user model.
      *
      * <p>If failed to retrieve the id stored in global settings or the retrieved id does not
      * exist on device, then return the user with smallest user id.
@@ -216,6 +229,19 @@
     }
 
     /**
+     * Sets default guest restrictions that will be applied every time a Guest user is created.
+     *
+     * <p> Restrictions are written to disk and persistent across boots.
+     */
+    public void initDefaultGuestRestrictions() {
+        Bundle defaultGuestRestrictions = new Bundle();
+        for (String restriction : DEFAULT_GUEST_RESTRICTIONS) {
+            defaultGuestRestrictions.putBoolean(restriction, true);
+        }
+        mUserManager.setDefaultGuestRestrictions(defaultGuestRestrictions);
+    }
+
+    /**
      * Returns {@code true} if the system is in the headless user 0 model.
      *
      * @return {@boolean true} if headless system user.
@@ -339,6 +365,24 @@
     }
 
     /**
+     * Gets all users that are not guests.
+     *
+     * @return List of {@code UserInfo} for all users who are not guest users.
+     */
+    public List<UserInfo> getAllUsersExceptGuests() {
+        List<UserInfo> users = getAllUsers();
+
+        for (Iterator<UserInfo> iterator = users.iterator(); iterator.hasNext(); ) {
+            UserInfo userInfo = iterator.next();
+            if (userInfo.isGuest()) {
+                // Remove guests.
+                iterator.remove();
+            }
+        }
+        return users;
+    }
+
+    /**
      * Get all the users except the one with userId passed in.
      *
      * @param userId of the user not to be returned.
@@ -376,6 +420,62 @@
         return users;
     }
 
+    /**
+     * Maximum number of users allowed on the device. This includes real users, managed profiles
+     * and restricted users, but excludes guests.
+     *
+     * <p> It excludes system user in headless system user model.
+     *
+     * @return Maximum number of users that can be present on the device.
+     */
+    public int getMaxSupportedUsers() {
+        if (isHeadlessSystemUser()) {
+            return UserManager.getMaxSupportedUsers() - 1;
+        }
+        return UserManager.getMaxSupportedUsers();
+    }
+
+    /**
+     * Get the maximum number of real (non-guest, non-managed profile) users that can be created on
+     * the device. This is a dynamic value and it decreases with the increase of the number of
+     * managed profiles on the device.
+     *
+     * <p> It excludes system user in headless system user model.
+     *
+     * @return Maximum number of real users that can be created.
+     */
+    public int getMaxSupportedRealUsers() {
+        return getMaxSupportedUsers() - getManagedProfilesCount();
+    }
+
+    /**
+     * Returns true if the maximum number of users on the device has been reached, false otherwise.
+     */
+    public boolean isUserLimitReached() {
+        int countNonGuestUsers = getAllUsersExceptGuests().size();
+        int maxSupportedUsers = getMaxSupportedUsers();
+
+        if (countNonGuestUsers > maxSupportedUsers) {
+            Log.e(TAG, "There are more users on the device than allowed.");
+            return true;
+        }
+
+        return getAllUsersExceptGuests().size() == maxSupportedUsers;
+    }
+
+    private int getManagedProfilesCount() {
+        List<UserInfo> users = getAllUsers();
+
+        // Count all users that are managed profiles of another user.
+        int managedProfilesCount = 0;
+        for (UserInfo user : users) {
+            if (user.isManagedProfile()) {
+                managedProfilesCount++;
+            }
+        }
+        return managedProfilesCount;
+    }
+
     // User information accessors
 
     /**
diff --git a/service/src/com/android/car/CarInputService.java b/service/src/com/android/car/CarInputService.java
index 1f67259..2b97221 100644
--- a/service/src/com/android/car/CarInputService.java
+++ b/service/src/com/android/car/CarInputService.java
@@ -16,7 +16,9 @@
 package com.android.car;
 
 import static android.hardware.input.InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
+import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_ASSIST_GESTURE;
 
+import android.app.ActivityManager;
 import android.car.input.CarInputHandlingService;
 import android.car.input.CarInputHandlingService.InputFilter;
 import android.car.input.ICarInputListener;
@@ -30,19 +32,18 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.Parcel;
-import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.CallLog.Calls;
-import android.speech.RecognizerIntent;
 import android.telecom.TelecomManager;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.KeyEvent;
 
 import com.android.car.hal.InputHalService;
-import com.android.car.hal.VehicleHal;
+import com.android.internal.app.AssistUtils;
+import com.android.internal.app.IVoiceInteractionSessionShowCallback;
 
 import java.io.PrintWriter;
 import java.util.HashMap;
@@ -83,12 +84,30 @@
         }
     }
 
+    private IVoiceInteractionSessionShowCallback mShowCallback =
+            new IVoiceInteractionSessionShowCallback.Stub() {
+        @Override
+        public void onFailed() {
+            Log.w(CarLog.TAG_INPUT, "Failed to show VoiceInteractionSession");
+        }
+
+        @Override
+        public void onShown() {
+            if (DBG) {
+                Log.d(CarLog.TAG_INPUT, "IVoiceInteractionSessionShowCallback onShown()");
+            }
+        }
+    };
+
     private static final boolean DBG = false;
+    private static final String EXTRA_CAR_PUSH_TO_TALK =
+            "com.android.car.input.EXTRA_CAR_PUSH_TO_TALK";
 
     private final Context mContext;
     private final InputHalService mInputHalService;
     private final TelecomManager mTelecomManager;
     private final InputManager mInputManager;
+    private final AssistUtils mAssistUtils;
 
     private KeyEventListener mVoiceAssistantKeyListener;
     private KeyEventListener mLongVoiceAssistantKeyListener;
@@ -153,6 +172,7 @@
         mInputHalService = inputHalService;
         mTelecomManager = context.getSystemService(TelecomManager.class);
         mInputManager = context.getSystemService(InputManager.class);
+        mAssistUtils = new AssistUtils(context);
     }
 
     private synchronized void setHandledKeys(InputFilter[] handledKeys) {
@@ -324,10 +344,18 @@
     }
 
     private void launchDefaultVoiceAssistantHandler() {
-        Log.i(CarLog.TAG_INPUT, "voice key, launch default intent");
-        Intent voiceIntent =
-                new Intent(RecognizerIntent.ACTION_VOICE_SEARCH_HANDS_FREE);
-        mContext.startActivityAsUser(voiceIntent, null, UserHandle.CURRENT_OR_SELF);
+        Log.i(CarLog.TAG_INPUT, "voice key, invoke AssistUtils");
+
+        if (mAssistUtils.getAssistComponentForUser(ActivityManager.getCurrentUser()) == null) {
+            Log.w(CarLog.TAG_INPUT, "Unable to retrieve assist component for current user");
+            return;
+        }
+
+        final Bundle args = new Bundle();
+        args.putBoolean(EXTRA_CAR_PUSH_TO_TALK, true);
+
+        mAssistUtils.showSessionForActiveService(args,
+                SHOW_SOURCE_ASSIST_GESTURE, mShowCallback, null /*activityToken*/);
     }
 
     private void handleInstrumentClusterKey(KeyEvent event) {
diff --git a/service/src/com/android/car/CarNightService.java b/service/src/com/android/car/CarNightService.java
index e43ed3d..dcf75b5 100644
--- a/service/src/com/android/car/CarNightService.java
+++ b/service/src/com/android/car/CarNightService.java
@@ -72,23 +72,27 @@
             CarPropertyValue value = event.getCarPropertyValue();
             if (value.getPropertyId() == VehicleProperty.NIGHT_MODE) {
                 boolean nightMode = (Boolean) value.getValue();
-                if (nightMode) {
-                    mNightSetting = UiModeManager.MODE_NIGHT_YES;
-                    if (DBG)  Log.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent NIGHT");
-                } else {
-                    mNightSetting = UiModeManager.MODE_NIGHT_NO;
-                    if (DBG)  Log.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent DAY");
-                }
-                if (mUiModeManager != null && (mForcedMode == FORCED_SENSOR_MODE)) {
-                    mUiModeManager.setNightMode(mNightSetting);
-                    if (DBG)  Log.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent APPLIED");
-                } else {
-                    if (DBG)  Log.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent IGNORED");
-                }
+                setNightMode(nightMode);
             }
         }
     }
 
+    private synchronized void setNightMode(boolean nightMode) {
+        if (nightMode) {
+            mNightSetting = UiModeManager.MODE_NIGHT_YES;
+            if (DBG)  Log.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent NIGHT");
+        } else {
+            mNightSetting = UiModeManager.MODE_NIGHT_NO;
+            if (DBG)  Log.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent DAY");
+        }
+        if (mUiModeManager != null && (mForcedMode == FORCED_SENSOR_MODE)) {
+            mUiModeManager.setNightMode(mNightSetting);
+            if (DBG)  Log.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent APPLIED");
+        } else {
+            if (DBG)  Log.d(CarLog.TAG_SENSOR, "CAR dayNight handleSensorEvent IGNORED");
+        }
+    }
+
     public synchronized int forceDayNightMode(@DayNightSensorMode int mode) {
         if (mUiModeManager == null) {
             return -1;
@@ -131,6 +135,15 @@
         }
         mCarPropertyService.registerListener(VehicleProperty.NIGHT_MODE, 0,
                 mICarPropertyEventListener);
+        CarPropertyValue propertyValue = mCarPropertyService.getProperty(
+                VehicleProperty.NIGHT_MODE, 0);
+        if (propertyValue != null && propertyValue.getTimestamp() != 0) {
+            setNightMode((Boolean) propertyValue.getValue());
+        } else {
+            Log.w(CarLog.TAG_SENSOR, "Failed to get value of NIGHT_MODE");
+            // Initial in Night Mode
+            setNightMode(true);
+        }
     }
 
     @Override
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index 3898e74..c63a2fd 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -93,6 +93,7 @@
         if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(intent.getAction())) {
             if (mCarUserManagerHelper.getAllUsers().size() == 0) {
                 setSystemUserRestrictions();
+                mCarUserManagerHelper.initDefaultGuestRestrictions();
                 // On very first boot, create an admin user and switch to that user.
                 UserInfo admin = mCarUserManagerHelper.createNewAdminUser(OWNER_NAME);
                 mCarUserManagerHelper.switchToUser(admin);
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index bc89ad4..6e8e80f 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -42,6 +42,8 @@
     <uses-permission android:name="android.permission.READ_SMS"/>
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+    <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
+    <uses-permission android:name="android.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS" />
     <uses-permission android:name="android.permission.INJECT_EVENTS" />
 
     <application android:label="@string/app_title"
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/connectivity_fragment.xml b/tests/EmbeddedKitchenSinkApp/res/layout/connectivity_fragment.xml
new file mode 100644
index 0000000..15422b1
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/connectivity_fragment.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+
+    <LinearLayout
+            android:orientation="horizontal"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+        <ListView
+                android:id="@+id/networks"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content">
+        </ListView>
+    </LinearLayout>
+    <LinearLayout
+            android:orientation="horizontal"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_margin="4dp">
+        <Button android:id="@+id/networksRefresh"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Refresh"/>
+        <Button android:id="@+id/networkRequestOemPaid"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Request OEM-paid"/>
+        <Button android:id="@+id/networkRequestEth1"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Request eth1"/>
+        <Button android:id="@+id/networkReleaseNetwork"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:text="Release Request"/>
+    </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/list_item.xml b/tests/EmbeddedKitchenSinkApp/res/layout/list_item.xml
new file mode 100644
index 0000000..f517913
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/list_item.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+          android:id="@android:id/text1"
+          android:paddingTop="2dip"
+          android:paddingBottom="3dip"
+          android:layout_width="fill_parent"
+          android:layout_height="wrap_content"
+          android:textSize="24sp" />
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
index 7837f2e..caca03a 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
@@ -23,7 +23,9 @@
 import android.car.hardware.property.CarPropertyManager;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.os.AsyncTask;
 import android.os.Bundle;
+import android.os.Handler;
 import android.support.car.Car;
 import android.support.car.CarAppFocusManager;
 import android.support.car.CarConnectionCallback;
@@ -42,6 +44,7 @@
 import com.google.android.car.kitchensink.bluetooth.BluetoothHeadsetFragment;
 import com.google.android.car.kitchensink.bluetooth.MapMceTestFragment;
 import com.google.android.car.kitchensink.cluster.InstrumentClusterFragment;
+import com.google.android.car.kitchensink.connectivity.ConnectivityFragment;
 import com.google.android.car.kitchensink.cube.CubesTestFragment;
 import com.google.android.car.kitchensink.diagnostic.DiagnosticTestFragment;
 import com.google.android.car.kitchensink.displayinfo.DisplayInfoFragment;
@@ -167,6 +170,7 @@
                 startActivity(intent);
             });
             add("activity view", ActivityViewTestFragment.class);
+            add("connectivity", ConnectivityFragment.class);
             add("quit", KitchenSinkActivity.this::finish);
         }
 
@@ -183,6 +187,7 @@
     private CarPropertyManager mPropertyManager;
     private CarSensorManager mSensorManager;
     private CarAppFocusManager mCarAppFocusManager;
+    private Object mPropertyManagerReady = new Object();
 
     public CarHvacManager getHvacManager() {
         return mHvacManager;
@@ -212,12 +217,20 @@
         setMainContent(R.layout.kitchen_content);
         // Connection to Car Service does not work for non-automotive yet.
         if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
-            mCarApi = Car.createCar(this, mCarConnectionCallback);
-            mCarApi.connect();
+            initCarApi();
         }
         Log.i(TAG, "onCreate");
     }
 
+    private void initCarApi() {
+        if (mCarApi != null && mCarApi.isConnected()) {
+            mCarApi.disconnect();
+            mCarApi = null;
+        }
+        mCarApi = Car.createCar(this, mCarConnectionCallback);
+        mCarApi.connect();
+    }
+
     @Override
     protected void onStart() {
         super.onStart();
@@ -268,18 +281,22 @@
         @Override
         public void onConnected(Car car) {
             Log.d(TAG, "Connected to Car Service");
-            try {
-                mHvacManager = (CarHvacManager) mCarApi.getCarManager(android.car.Car.HVAC_SERVICE);
-                mPowerManager = (CarPowerManager) mCarApi.getCarManager(
-                    android.car.Car.POWER_SERVICE);
-                mPropertyManager = (CarPropertyManager) mCarApi.getCarManager(
-                    android.car.Car.PROPERTY_SERVICE);
-                mSensorManager = (CarSensorManager) mCarApi.getCarManager(
-                    android.car.Car.SENSOR_SERVICE);
-                mCarAppFocusManager =
-                        (CarAppFocusManager) mCarApi.getCarManager(Car.APP_FOCUS_SERVICE);
-            } catch (CarNotConnectedException e) {
-                Log.e(TAG, "Car is not connected!", e);
+            synchronized (mPropertyManagerReady) {
+                try {
+                    mHvacManager = (CarHvacManager) mCarApi.getCarManager(
+                            android.car.Car.HVAC_SERVICE);
+                    mPowerManager = (CarPowerManager) mCarApi.getCarManager(
+                            android.car.Car.POWER_SERVICE);
+                    mPropertyManager = (CarPropertyManager) mCarApi.getCarManager(
+                            android.car.Car.PROPERTY_SERVICE);
+                    mSensorManager = (CarSensorManager) mCarApi.getCarManager(
+                            android.car.Car.SENSOR_SERVICE);
+                    mCarAppFocusManager =
+                            (CarAppFocusManager) mCarApi.getCarManager(Car.APP_FOCUS_SERVICE);
+                    mPropertyManagerReady.notifyAll();
+                } catch (CarNotConnectedException e) {
+                    Log.e(TAG, "Car is not connected!", e);
+                }
             }
         }
 
@@ -322,4 +339,29 @@
             getDrawerController().closeDrawer();
         }
     }
+
+    // Use AsyncTask to refresh Car*Manager after car service connected
+    public void requestRefreshManager(final Runnable r, final Handler h) {
+        final AsyncTask<Void, Void, Void> task = new AsyncTask<Void, Void, Void>() {
+            @Override
+            protected Void doInBackground(Void... unused) {
+                synchronized (mPropertyManagerReady) {
+                    while (!mCarApi.isConnected()) {
+                        try {
+                            mPropertyManagerReady.wait();
+                        } catch (InterruptedException e) {
+                            return null;
+                        }
+                    }
+                }
+                return null;
+            }
+
+            @Override
+            protected void onPostExecute(Void unused) {
+                h.post(r);
+            }
+        };
+        task.execute();
+    }
 }
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/connectivity/ConnectivityFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/connectivity/ConnectivityFragment.java
new file mode 100644
index 0000000..0ffa6bf
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/connectivity/ConnectivityFragment.java
@@ -0,0 +1,154 @@
+/*
+ * 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 com.google.android.car.kitchensink.connectivity;
+
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.net.ConnectivityManager;
+import android.net.ConnectivityManager.NetworkCallback;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkInfo;
+import android.net.NetworkRequest;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v4.app.Fragment;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.Toast;
+
+import com.google.android.car.kitchensink.R;
+
+import java.util.ArrayList;
+
+@SuppressLint("SetTextI18n")
+public class ConnectivityFragment extends Fragment {
+    private static final String TAG = ConnectivityFragment.class.getSimpleName();
+
+    private final Handler mHandler = new Handler();
+    private final ArrayList<String> mNetworks = new ArrayList<>();
+
+    private ConnectivityManager mConnectivityManager;
+    private ArrayAdapter<String> mNetworksAdapter;
+
+    private final NetworkCallback mNetworkCallback = new NetworkCallback() {
+        @Override
+        public void onAvailable(Network network) {
+            showToast("onAvailable, netId: " + network);
+            refreshNetworks();
+        }
+
+        @Override
+        public void onLost(Network network) {
+            showToast("onLost, netId: " + network);
+            refreshNetworks();
+        }
+    };
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mConnectivityManager = getActivity().getSystemService(ConnectivityManager.class);
+
+        mConnectivityManager.addDefaultNetworkActiveListener(() -> refreshNetworks());
+    }
+
+    @Nullable
+    @Override
+    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+            @Nullable Bundle savedInstanceState) {
+        View view = inflater.inflate(R.layout.connectivity_fragment, container, false);
+
+        ListView networksView = view.findViewById(R.id.networks);
+        mNetworksAdapter = new ArrayAdapter<>(getActivity(), R.layout.list_item, mNetworks);
+        networksView.setAdapter(mNetworksAdapter);
+
+        setClickAction(view, R.id.networksRefresh, this::refreshNetworks);
+        setClickAction(view, R.id.networkRequestOemPaid, this::requestOemPaid);
+        setClickAction(view, R.id.networkRequestEth1, this::requestEth1);
+        setClickAction(view, R.id.networkReleaseNetwork, this::releaseNetworkRequest);
+
+        return view;
+    }
+
+    private void releaseNetworkRequest() {
+        mConnectivityManager.unregisterNetworkCallback(mNetworkCallback);
+        showToast("Release request sent");
+    }
+
+    private void requestEth1() {
+        NetworkRequest request = new NetworkRequest.Builder()
+                .clearCapabilities()
+                .addTransportType(NetworkCapabilities.TRANSPORT_ETHERNET)
+                .setNetworkSpecifier("eth1")
+                .build();
+        mConnectivityManager.requestNetwork(request, mNetworkCallback, mHandler);
+    }
+
+    private void requestOemPaid() {
+        NetworkRequest request = new NetworkRequest.Builder()
+                .addCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID)
+                .build();
+
+        mConnectivityManager.requestNetwork(request, mNetworkCallback, mHandler);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        refreshNetworks();
+    }
+
+    private void setClickAction(View view, int id, Runnable action) {
+        view.findViewById(id).setOnClickListener(v -> action.run());
+    }
+
+    private void refreshNetworks() {
+        mNetworks.clear();
+
+        for (Network network : mConnectivityManager.getAllNetworks()) {
+            boolean isDefault = sameNetworkId(network, mConnectivityManager.getActiveNetwork());
+            NetworkCapabilities nc = mConnectivityManager.getNetworkCapabilities(network);
+            boolean isOemPaid = nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_OEM_PAID);
+            boolean isInternet = nc.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
+
+            NetworkInfo networkInfo = mConnectivityManager.getNetworkInfo(network);
+
+            mNetworks.add("netId: " + network.netId
+                    + (isInternet ? " [INTERNET]" : "")
+                    + (isDefault ? " [DEFAULT]" : "")
+                    + (isOemPaid ? " [OEM-paid]" : "") + nc + " " + networkInfo);
+        }
+
+        mNetworksAdapter.notifyDataSetChanged();
+    }
+
+    private void showToast(String text) {
+        Log.d(TAG, "showToast: " + text);
+        Toast.makeText(getContext(), text, Toast.LENGTH_LONG).show();
+    }
+
+    private static boolean sameNetworkId(Network net1, Network net2) {
+        return net1 != null && net2 != null && net1.netId == net2.netId;
+
+    }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/hvac/HvacTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/hvac/HvacTestFragment.java
index a1f8e1d..c7b80e8 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/hvac/HvacTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/hvac/HvacTestFragment.java
@@ -25,6 +25,7 @@
 import android.hardware.automotive.vehicle.V2_0.VehicleAreaSeat;
 import android.hardware.automotive.vehicle.V2_0.VehicleAreaWindow;
 import android.os.Bundle;
+import android.os.Handler;
 import android.support.v4.app.Fragment;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -76,6 +77,8 @@
     private int mZoneForSetTempP;
     private int mZoneForFanSpeed;
     private int mZoneForFanPosition;
+    private List<CarPropertyConfig> mCarPropertyConfigs;
+    private View mHvacView;
 
     private final CarHvacManager.CarHvacEventCallback mHvacCallback =
             new CarHvacManager.CarHvacEventCallback () {
@@ -171,13 +174,9 @@
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
-        mCarHvacManager = ((KitchenSinkActivity)getActivity()).getHvacManager();
+
         super.onCreate(savedInstanceState);
-        try {
-            mCarHvacManager.registerCallback(mHvacCallback);
-        } catch (CarNotConnectedException e) {
-            Log.e(TAG, "Car is not connected!");
-        }
+
     }
 
     @Override
@@ -188,77 +187,85 @@
 
     @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstance) {
-        View v = inflater.inflate(R.layout.hvac_test, container, false);
+        mHvacView = inflater.inflate(R.layout.hvac_test, container, false);
+        final Runnable r = () -> {
+            mCarHvacManager = ((KitchenSinkActivity) getActivity()).getHvacManager();
+            try {
+                mCarHvacManager.registerCallback(mHvacCallback);
+            } catch (CarNotConnectedException e) {
+                Log.e(TAG, "Car is not connected!");
+            }
+            try {
+                mCarPropertyConfigs = mCarHvacManager.getPropertyList();
+            } catch (CarNotConnectedException e) {
+                Log.e(TAG, "Failed to get list of properties", e);
+                mCarPropertyConfigs = new ArrayList<>();
+            }
+            for (CarPropertyConfig prop : mCarPropertyConfigs) {
+                int propId = prop.getPropertyId();
 
-        List<CarPropertyConfig> props;
-        try {
-            props = mCarHvacManager.getPropertyList();
-        } catch (CarNotConnectedException e) {
-            Log.e(TAG, "Failed to get list of properties", e);
-            props = new ArrayList<>();
-        }
+                if (DBG) {
+                    Log.d(TAG, prop.toString());
+                }
 
-        for(CarPropertyConfig prop : props) {
-            int propId = prop.getPropertyId();
-
-            if(DBG) {
-                Log.d(TAG, prop.toString());
+                switch(propId) {
+                    case CarHvacManager.ID_OUTSIDE_AIR_TEMP:
+                        configureOutsideTemp(mHvacView, prop);
+                        break;
+                    case CarHvacManager.ID_ZONED_DUAL_ZONE_ON:
+                        configureDualOn(mHvacView, prop);
+                        break;
+                    case CarHvacManager.ID_ZONED_AC_ON:
+                        configureAcOn(mHvacView, prop);
+                        break;
+                    case CarHvacManager.ID_ZONED_FAN_DIRECTION:
+                        configureFanPosition(mHvacView, prop);
+                        break;
+                    case CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT:
+                        configureFanSpeed(mHvacView, prop);
+                        break;
+                    case CarHvacManager.ID_ZONED_TEMP_SETPOINT:
+                        configureTempSetpoint(mHvacView, prop);
+                        break;
+                    case CarHvacManager.ID_ZONED_AUTOMATIC_MODE_ON:
+                        configureAutoModeOn(mHvacView, prop);
+                        break;
+                    case CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON:
+                        configureRecircOn(mHvacView, prop);
+                        break;
+                    case CarHvacManager.ID_ZONED_MAX_AC_ON:
+                        configureMaxAcOn(mHvacView, prop);
+                        break;
+                    case CarHvacManager.ID_ZONED_MAX_DEFROST_ON:
+                        configureMaxDefrostOn(mHvacView, prop);
+                        break;
+                    case CarHvacManager.ID_WINDOW_DEFROSTER_ON:
+                        configureDefrosterOn(mHvacView, prop);
+                        break;
+                    default:
+                        Log.w(TAG, "propertyId " + propId + " is not handled");
+                        break;
+                }
             }
 
-            switch(propId) {
-                case CarHvacManager.ID_OUTSIDE_AIR_TEMP:
-                    configureOutsideTemp(v, prop);
-                    break;
-                case CarHvacManager.ID_ZONED_DUAL_ZONE_ON:
-                    configureDualOn(v, prop);
-                    break;
-                case CarHvacManager.ID_ZONED_AC_ON:
-                    configureAcOn(v, prop);
-                    break;
-                case CarHvacManager.ID_ZONED_FAN_DIRECTION:
-                    configureFanPosition(v, prop);
-                    break;
-                case CarHvacManager.ID_ZONED_FAN_SPEED_SETPOINT:
-                    configureFanSpeed(v, prop);
-                    break;
-                case CarHvacManager.ID_ZONED_TEMP_SETPOINT:
-                    configureTempSetpoint(v, prop);
-                    break;
-                case CarHvacManager.ID_ZONED_AUTOMATIC_MODE_ON:
-                    configureAutoModeOn(v, prop);
-                    break;
-                case CarHvacManager.ID_ZONED_AIR_RECIRCULATION_ON:
-                    configureRecircOn(v, prop);
-                    break;
-                case CarHvacManager.ID_ZONED_MAX_AC_ON:
-                    configureMaxAcOn(v, prop);
-                    break;
-                case CarHvacManager.ID_ZONED_MAX_DEFROST_ON:
-                    configureMaxDefrostOn(v, prop);
-                    break;
-                case CarHvacManager.ID_WINDOW_DEFROSTER_ON:
-                    configureDefrosterOn(v, prop);
-                    break;
-                default:
-                    Log.w(TAG, "propertyId " + propId + " is not handled");
-                    break;
-            }
-        }
+            mTvFanSpeed = (TextView) mHvacView.findViewById(R.id.tvFanSpeed);
+            mTvFanSpeed.setText(String.valueOf(mCurFanSpeed));
+            mTvDTemp = (TextView) mHvacView.findViewById(R.id.tvDTemp);
+            mTvDTemp.setText(String.valueOf(mCurDTemp));
+            mTvPTemp = (TextView) mHvacView.findViewById(R.id.tvPTemp);
+            mTvPTemp.setText(String.valueOf(mCurPTemp));
+            mTvOutsideTemp = (TextView) mHvacView.findViewById(R.id.tvOutsideTemp);
+            mTvOutsideTemp.setText("N/A");
+        };
 
-        mTvFanSpeed = (TextView) v.findViewById(R.id.tvFanSpeed);
-        mTvFanSpeed.setText(String.valueOf(mCurFanSpeed));
-        mTvDTemp = (TextView) v.findViewById(R.id.tvDTemp);
-        mTvDTemp.setText(String.valueOf(mCurDTemp));
-        mTvPTemp = (TextView) v.findViewById(R.id.tvPTemp);
-        mTvPTemp.setText(String.valueOf(mCurPTemp));
-        mTvOutsideTemp = (TextView) v.findViewById(R.id.tvOutsideTemp);
-        mTvOutsideTemp.setText("N/A");
+        ((KitchenSinkActivity) getActivity()).requestRefreshManager(r,
+                new Handler(getContext().getMainLooper()));
 
         if(DBG) {
             Log.d(TAG, "Starting HvacTestFragment");
         }
 
-        return v;
+        return mHvacView;
     }
 
     private void configureOutsideTemp(View v, CarPropertyConfig prop) {
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/power/PowerTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/power/PowerTestFragment.java
index fed1fbd..9a6c2b9 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/power/PowerTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/power/PowerTestFragment.java
@@ -20,6 +20,7 @@
 import android.car.hardware.power.CarPowerManager;
 import android.content.Context;
 import android.os.Bundle;
+import android.os.Handler;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.support.v4.app.Fragment;
@@ -58,16 +59,20 @@
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
-        mCarPowerManager = ((KitchenSinkActivity)getActivity()).getPowerManager();
-        mExecutor = new ThreadPerTaskExecutor();
+        final Runnable r = () -> {
+            mCarPowerManager = ((KitchenSinkActivity) getActivity()).getPowerManager();
+            mExecutor = new ThreadPerTaskExecutor();
+            try {
+                mCarPowerManager.setListener(mPowerListener, mExecutor);
+            } catch (CarNotConnectedException e) {
+                Log.e(TAG, "Car is not connected!");
+            } catch (IllegalStateException e) {
+                Log.e(TAG, "CarPowerManager listener was not cleared");
+            }
+        };
+        ((KitchenSinkActivity) getActivity()).requestRefreshManager(r,
+                new Handler(getContext().getMainLooper()));
         super.onCreate(savedInstanceState);
-        try {
-            mCarPowerManager.setListener(mPowerListener, mExecutor);
-        } catch (CarNotConnectedException e) {
-            Log.e(TAG, "Car is not connected!");
-        } catch (IllegalStateException e) {
-            Log.e(TAG, "CarPowerManager listener was not cleared");
-        }
     }
 
     @Override
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/property/PropertyTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/property/PropertyTestFragment.java
index fc6621a..ff1c402 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/property/PropertyTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/property/PropertyTestFragment.java
@@ -26,6 +26,7 @@
 import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
 import android.hardware.automotive.vehicle.V2_0.VehiclePropertyType;
 import android.os.Bundle;
+import android.os.Handler;
 import android.support.v4.app.Fragment;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -85,20 +86,23 @@
         mPropertyId = view.findViewById(R.id.sPropertyId);
         mScrollView = view.findViewById(R.id.svEventLog);
         mSetValue = view.findViewById(R.id.etSetPropertyValue);
+        mActivity = (KitchenSinkActivity) getActivity();
 
-        populateConfigList();
-        mListView.setAdapter(new PropertyListAdapter(mPropInfo, mMgr, mEventLog, mScrollView,
-                                                     mActivity));
+        final Runnable r = () -> {
+            mMgr = mActivity.getPropertyManager();
+            populateConfigList();
+            mListView.setAdapter(new PropertyListAdapter(mPropInfo, mMgr, mEventLog, mScrollView,
+                    mActivity));
 
-        // Configure dropdown menu for propertyId spinner
-        ArrayAdapter<PropertyInfo> adapter =
-                new ArrayAdapter<PropertyInfo>(mActivity, android.R.layout.simple_spinner_item,
-                                               mPropInfo);
-        adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
-        mPropertyId.setAdapter(adapter);
-        mPropertyId.setOnItemSelectedListener(this);
-
-
+            // Configure dropdown menu for propertyId spinner
+            ArrayAdapter<PropertyInfo> adapter =
+                    new ArrayAdapter<PropertyInfo>(mActivity, android.R.layout.simple_spinner_item,
+                            mPropInfo);
+            adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
+            mPropertyId.setAdapter(adapter);
+            mPropertyId.setOnItemSelectedListener(this);
+        };
+        mActivity.requestRefreshManager(r, new Handler(getContext().getMainLooper()));
 
         // Configure listeners for buttons
         Button b = view.findViewById(R.id.bGetProperty);
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/sensor/SensorsTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/sensor/SensorsTestFragment.java
index a6cc5ac..1440ff0 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/sensor/SensorsTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/sensor/SensorsTestFragment.java
@@ -99,17 +99,19 @@
 
         View view = inflater.inflate(R.layout.sensors, container, false);
         mActivity = (KitchenSinkActivity) getHost();
-
         mSensorInfo = (TextView) view.findViewById(R.id.sensor_info);
         mNaString = getContext().getString(R.string.sensor_na);
-
         return view;
     }
 
     @Override
     public void onResume() {
         super.onResume();
-        initPermissions();
+        final Runnable r = () -> {
+            initPermissions();
+        };
+        ((KitchenSinkActivity) getActivity()).requestRefreshManager(r,
+                new Handler(getContext().getMainLooper()));
     }
 
     @Override
diff --git a/tests/carservice_unit_test/src/com/android/car/CarUserManagerHelperTest.java b/tests/carservice_unit_test/src/com/android/car/CarUserManagerHelperTest.java
index 8fc1eea..7f53da5 100644
--- a/tests/carservice_unit_test/src/com/android/car/CarUserManagerHelperTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/CarUserManagerHelperTest.java
@@ -34,6 +34,7 @@
 import android.content.pm.UserInfo;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.SystemProperties;
 import android.os.UserHandle;
@@ -51,7 +52,6 @@
 import org.mockito.MockitoAnnotations;
 
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -128,15 +128,8 @@
         UserInfo otherUser2 = createUserInfoForId(11);
         UserInfo otherUser3 = createUserInfoForId(12);
 
-        List<UserInfo> testUsers = new ArrayList<>();
-        testUsers.add(mSystemUser);
-        testUsers.add(otherUser1);
-        testUsers.add(otherUser2);
-        testUsers.add(otherUser3);
+        mockGetUsers(mSystemUser, otherUser1, otherUser2, otherUser3);
 
-        doReturn(testUsers).when(mUserManager).getUsers(true);
-
-        assertThat(mCarUserManagerHelper.getAllUsers()).hasSize(3);
         assertThat(mCarUserManagerHelper.getAllUsers())
                 .containsExactly(otherUser1, otherUser2, otherUser3);
     }
@@ -147,15 +140,9 @@
         UserInfo user1 = createUserInfoForId(mForegroundUserId + 1);
         UserInfo user2 = createUserInfoForId(mForegroundUserId + 2);
 
-        List<UserInfo> testUsers = Arrays.asList(mForegroundUser, user1, user2);
-
-        doReturn(new ArrayList<>(testUsers)).when(mUserManager).getUsers(true);
-
-        // Should return all 3 users.
-        assertThat(mCarUserManagerHelper.getAllUsers()).hasSize(3);
+        mockGetUsers(mForegroundUser, user1, user2);
 
         // Should return all non-foreground users.
-        assertThat(mCarUserManagerHelper.getAllSwitchableUsers()).hasSize(2);
         assertThat(mCarUserManagerHelper.getAllSwitchableUsers()).containsExactly(user1, user2);
     }
 
@@ -170,19 +157,41 @@
         UserInfo user4 = new UserInfo(
               /* id= */mForegroundUserId + 3, /* name = */ "user4", UserInfo.FLAG_EPHEMERAL);
 
-        List<UserInfo> testUsers = Arrays.asList(user1, user2, user3, user4);
-
-        doReturn(new ArrayList<>(testUsers)).when(mUserManager).getUsers(true);
-
-        // Should return all 4 users.
-        assertThat(mCarUserManagerHelper.getAllUsers()).hasSize(4);
+        mockGetUsers(user1, user2, user3, user4);
 
         // Should return all non-ephemeral users.
-        assertThat(mCarUserManagerHelper.getAllPersistentUsers()).hasSize(2);
         assertThat(mCarUserManagerHelper.getAllPersistentUsers()).containsExactly(user1, user2);
     }
 
     @Test
+    public void testGetAllAdminUsers() {
+        // Create two admin, and two non-admin users.
+        UserInfo user1 = new UserInfo(/* id= */ 10, /* name = */ "user10", UserInfo.FLAG_ADMIN);
+        UserInfo user2 = createUserInfoForId(11);
+        UserInfo user3 = createUserInfoForId(12);
+        UserInfo user4 = new UserInfo(/* id= */ 13, /* name = */ "user13", UserInfo.FLAG_ADMIN);
+
+        mockGetUsers(user1, user2, user3, user4);
+
+        // Should return only admin users.
+        assertThat(mCarUserManagerHelper.getAllAdminUsers()).containsExactly(user1, user4);
+    }
+
+    @Test
+    public void testGetAllUsersExceptGuests() {
+        // Create two users and a guest user.
+        UserInfo user1 = createUserInfoForId(10);
+        UserInfo user2 = createUserInfoForId(12);
+        UserInfo user3 = new UserInfo(/* id= */ 13, /* name = */ "user13", UserInfo.FLAG_GUEST);
+
+        mockGetUsers(user1, user2, user3);
+
+        // Should not return guests.
+        assertThat(mCarUserManagerHelper.getAllUsersExceptGuests())
+                .containsExactly(user1, user2);
+    }
+
+    @Test
     public void testUserCanBeRemoved() {
         UserInfo testInfo = new UserInfo();
 
@@ -256,6 +265,99 @@
     }
 
     @Test
+    public void testGetMaxSupportedUsers() {
+        SystemProperties.set("fw.max_users", "11");
+
+        assertThat(mCarUserManagerHelper.getMaxSupportedUsers()).isEqualTo(11);
+
+        // In headless user 0 model, we want to exclude the system user.
+        SystemProperties.set("android.car.systemuser.headless", "true");
+        assertThat(mCarUserManagerHelper.getMaxSupportedUsers()).isEqualTo(10);
+    }
+
+    @Test
+    public void testGetMaxSupportedRealUsers() {
+        SystemProperties.set("fw.max_users", "7");
+
+        // Create three managed profiles, and two normal users.
+        UserInfo user1 = createUserInfoForId(10);
+        UserInfo user2 =
+                new UserInfo(/* id= */ 11, /* name = */ "user11", UserInfo.FLAG_MANAGED_PROFILE);
+        UserInfo user3 =
+                new UserInfo(/* id= */ 12, /* name = */ "user12", UserInfo.FLAG_MANAGED_PROFILE);
+        UserInfo user4 = createUserInfoForId(13);
+        UserInfo user5 =
+                new UserInfo(/* id= */ 14, /* name = */ "user14", UserInfo.FLAG_MANAGED_PROFILE);
+
+        mockGetUsers(user1, user2, user3, user4, user5);
+
+        // Max users - # managed profiles.
+        assertThat(mCarUserManagerHelper.getMaxSupportedRealUsers()).isEqualTo(4);
+    }
+
+    @Test
+    public void testIsUserLimitReached() {
+        UserInfo user1 = createUserInfoForId(10);
+        UserInfo user2 =
+                new UserInfo(/* id= */ 11, /* name = */ "user11", UserInfo.FLAG_MANAGED_PROFILE);
+        UserInfo user3 =
+                new UserInfo(/* id= */ 12, /* name = */ "user12", UserInfo.FLAG_MANAGED_PROFILE);
+        UserInfo user4 = createUserInfoForId(13);
+
+        mockGetUsers(user1, user2, user3, user4);
+
+        SystemProperties.set("fw.max_users", "5");
+        assertThat(mCarUserManagerHelper.isUserLimitReached()).isFalse();
+
+        SystemProperties.set("fw.max_users", "4");
+        assertThat(mCarUserManagerHelper.isUserLimitReached()).isTrue();
+    }
+
+    @Test
+    public void testHeadlessSystemUser_IsUserLimitReached() {
+        SystemProperties.set("android.car.systemuser.headless", "true");
+        UserInfo user1 = createUserInfoForId(10);
+        UserInfo user2 =
+                new UserInfo(/* id= */ 11, /* name = */ "user11", UserInfo.FLAG_MANAGED_PROFILE);
+        UserInfo user3 =
+                new UserInfo(/* id= */ 12, /* name = */ "user12", UserInfo.FLAG_MANAGED_PROFILE);
+        UserInfo user4 = createUserInfoForId(13);
+
+        mockGetUsers(mSystemUser, user1, user2, user3, user4);
+
+        SystemProperties.set("fw.max_users", "6");
+        assertThat(mCarUserManagerHelper.isUserLimitReached()).isFalse();
+
+        SystemProperties.set("fw.max_users", "5");
+        assertThat(mCarUserManagerHelper.isUserLimitReached()).isTrue();
+    }
+
+    @Test
+    public void testIsUserLimitReachedIgnoresGuests() {
+        SystemProperties.set("fw.max_users", "5");
+
+        UserInfo user1 = createUserInfoForId(10);
+        UserInfo user2 =
+                new UserInfo(/* id= */ 11, /* name = */ "user11", UserInfo.FLAG_MANAGED_PROFILE);
+        UserInfo user3 =
+                new UserInfo(/* id= */ 12, /* name = */ "user12", UserInfo.FLAG_MANAGED_PROFILE);
+        UserInfo user4 = createUserInfoForId(13);
+        UserInfo user5 = new UserInfo(/* id= */ 14, /* name = */ "user14", UserInfo.FLAG_GUEST);
+        UserInfo user6 = createUserInfoForId(15);
+
+        mockGetUsers(user1, user2, user3, user4);
+        assertThat(mCarUserManagerHelper.isUserLimitReached()).isFalse();
+
+        // Add guest user. Verify it doesn't affect the limit.
+        mockGetUsers(user1, user2, user3, user4, user5);
+        assertThat(mCarUserManagerHelper.isUserLimitReached()).isFalse();
+
+        // Add normal user. Limit is reached
+        mockGetUsers(user1, user2, user3, user4, user5, user6);
+        assertThat(mCarUserManagerHelper.isUserLimitReached()).isTrue();
+    }
+
+    @Test
     public void testCreateNewAdminUser() {
         // Make sure current user is admin, since only admins can create other admins.
         doReturn(true).when(mUserManager).isAdminUser();
@@ -366,10 +468,7 @@
         assertThat(mCarUserManagerHelper.removeUser(mSystemUser, mGuestUserName)).isFalse();
 
         UserInfo adminInfo = new UserInfo(/* id= */10, "admin", UserInfo.FLAG_ADMIN);
-        List<UserInfo> users = new ArrayList<UserInfo>();
-        users.add(adminInfo);
-
-        doReturn(users).when(mUserManager).getUsers(true);
+        mockGetUsers(adminInfo);
 
         assertThat(mCarUserManagerHelper.removeUser(adminInfo, mGuestUserName))
             .isEqualTo(false);
@@ -468,6 +567,26 @@
     }
 
     @Test
+    public void testDefaultGuestRestrictions() {
+        int guestRestrictionsExpectedCount = 7;
+
+        ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
+        mCarUserManagerHelper.initDefaultGuestRestrictions();
+
+        verify(mUserManager).setDefaultGuestRestrictions(bundleCaptor.capture());
+        Bundle guestRestrictions = bundleCaptor.getValue();
+
+        assertThat(guestRestrictions.keySet()).hasSize(guestRestrictionsExpectedCount);
+        assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_FACTORY_RESET)).isTrue();
+        assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_REMOVE_USER)).isTrue();
+        assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS)).isTrue();
+        assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_OUTGOING_CALLS)).isTrue();
+        assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_SMS)).isTrue();
+        assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_INSTALL_APPS)).isTrue();
+        assertThat(guestRestrictions.getBoolean(UserManager.DISALLOW_UNINSTALL_APPS)).isTrue();
+    }
+
+    @Test
     public void testAssigningAdminPrivilegesRemovesNonAdminRestrictions() {
         int testUserId = 30;
         boolean restrictionEnabled = false;
@@ -631,15 +750,9 @@
         UserInfo otherUser2 = createUserInfoForId(lastActiveUserId - 1);
         UserInfo otherUser3 = createUserInfoForId(lastActiveUserId);
 
-        List<UserInfo> testUsers = new ArrayList<>();
-        testUsers.add(mSystemUser);
-        testUsers.add(otherUser1);
-        testUsers.add(otherUser2);
-        testUsers.add(otherUser3);
-
         mCarUserManagerHelper.setLastActiveUser(
                 lastActiveUserId, /* skipGlobalSettings= */ true);
-        doReturn(testUsers).when(mUserManager).getUsers(true);
+        mockGetUsers(mSystemUser, otherUser1, otherUser2, otherUser3);
 
         assertThat(mCarUserManagerHelper.getInitialUser()).isEqualTo(lastActiveUserId);
     }
@@ -652,14 +765,9 @@
         UserInfo otherUser1 = createUserInfoForId(lastActiveUserId - 2);
         UserInfo otherUser2 = createUserInfoForId(lastActiveUserId - 1);
 
-        List<UserInfo> testUsers = new ArrayList<>();
-        testUsers.add(mSystemUser);
-        testUsers.add(otherUser1);
-        testUsers.add(otherUser2);
-
         mCarUserManagerHelper.setLastActiveUser(
                 lastActiveUserId, /* skipGlobalSettings= */ true);
-        doReturn(testUsers).when(mUserManager).getUsers(true);
+        mockGetUsers(mSystemUser, otherUser1, otherUser2);
 
         assertThat(mCarUserManagerHelper.getInitialUser()).isEqualTo(lastActiveUserId - 2);
     }
@@ -669,4 +777,12 @@
         userInfo.id = id;
         return userInfo;
     }
+
+    private void mockGetUsers(UserInfo... users) {
+        List<UserInfo> testUsers = new ArrayList<>();
+        for (UserInfo user: users) {
+            testUsers.add(user);
+        }
+        doReturn(testUsers).when(mUserManager).getUsers(true);
+    }
 }
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 710b847..48d447b 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
@@ -32,9 +32,6 @@
 import android.os.UserManager;
 import android.support.test.runner.AndroidJUnit4;
 
-import java.util.ArrayList;
-import java.util.List;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -42,6 +39,9 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * This class contains unit tests for the {@link CarUserService}.
  *
@@ -71,12 +71,14 @@
      * Initialize all of the objects with the @Mock annotation.
      */
     @Before
-    public void setUp() throws Exception {
+    public void setUpMocks() throws Exception {
         MockitoAnnotations.initMocks(this);
         doReturn(mApplicationContext).when(mMockContext).getApplicationContext();
         doReturn(mLocationManager).when(mMockContext).getSystemService(Context.LOCATION_SERVICE);
 
         mCarUserService = new CarUserService(mMockContext, mCarUserManagerHelper);
+
+        doReturn(new ArrayList<>()).when(mCarUserManagerHelper).getAllUsers();
     }
 
     /**
@@ -109,13 +111,7 @@
      */
     @Test
     public void testStartsSecondaryAdminUserOnFirstRun() {
-        List<UserInfo> users = new ArrayList<>();
-
-        int adminUserId = 10;
-        UserInfo admin = new UserInfo(adminUserId, CarUserService.OWNER_NAME, UserInfo.FLAG_ADMIN);
-
-        doReturn(users).when(mCarUserManagerHelper).getAllUsers();
-        doReturn(admin).when(mCarUserManagerHelper).createNewAdminUser(CarUserService.OWNER_NAME);
+        UserInfo admin = mockAdmin(/* adminId= */ 10);
 
         mCarUserService.onReceive(mMockContext,
                 new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
@@ -129,16 +125,12 @@
      */
     @Test
     public void testDisableModifyAccountsForSystemUserOnFirstRun() {
-        List<UserInfo> users = new ArrayList<>();
-
+        // Mock system user.
         UserInfo systemUser = new UserInfo();
         systemUser.id = UserHandle.USER_SYSTEM;
-        int adminUserId = 10;
-        UserInfo admin = new UserInfo(adminUserId, CarUserService.OWNER_NAME, UserInfo.FLAG_ADMIN);
-
-        doReturn(users).when(mCarUserManagerHelper).getAllUsers();
         doReturn(systemUser).when(mCarUserManagerHelper).getSystemUserInfo();
-        doReturn(admin).when(mCarUserManagerHelper).createNewAdminUser(CarUserService.OWNER_NAME);
+
+        mockAdmin(10);
 
         mCarUserService.onReceive(mMockContext,
                 new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
@@ -152,22 +144,13 @@
      */
     @Test
     public void testDisableLocationForSystemUserOnFirstRun() {
-        List<UserInfo> users = new ArrayList<>();
-
-        UserInfo systemUser = new UserInfo();
-        systemUser.id = UserHandle.USER_SYSTEM;
-        int adminUserId = 10;
-        UserInfo admin = new UserInfo(adminUserId, CarUserService.OWNER_NAME, UserInfo.FLAG_ADMIN);
-
-        doReturn(users).when(mCarUserManagerHelper).getAllUsers();
-        doReturn(systemUser).when(mCarUserManagerHelper).getSystemUserInfo();
-        doReturn(admin).when(mCarUserManagerHelper).createNewAdminUser(CarUserService.OWNER_NAME);
+        mockAdmin(/* adminId= */ 10);
 
         mCarUserService.onReceive(mMockContext,
                 new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
 
-        verify(mLocationManager)
-                .setLocationEnabledForUser(/* enabled= */ false, UserHandle.of(systemUser.id));
+        verify(mLocationManager).setLocationEnabledForUser(
+                /* enabled= */ false, UserHandle.of(UserHandle.USER_SYSTEM));
     }
 
     /**
@@ -176,21 +159,13 @@
      */
     @Test
     public void testUpdateLastActiveUserOnFirstRun() {
-        List<UserInfo> users = new ArrayList<>();
-
-        UserInfo systemUser = new UserInfo();
-        systemUser.id = UserHandle.USER_SYSTEM;
-        int adminUserId = 10;
-        UserInfo admin = new UserInfo(adminUserId, CarUserService.OWNER_NAME, UserInfo.FLAG_ADMIN);
-
-        doReturn(users).when(mCarUserManagerHelper).getAllUsers();
-        doReturn(admin).when(mCarUserManagerHelper).createNewAdminUser(CarUserService.OWNER_NAME);
+        UserInfo admin = mockAdmin(/* adminId= */ 10);
 
         mCarUserService.onReceive(mMockContext,
                 new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
 
         verify(mCarUserManagerHelper)
-                .setLastActiveUser(adminUserId, /* skipGlobalSetting= */ false);
+                .setLastActiveUser(admin.id, /* skipGlobalSetting= */ false);
     }
 
     /**
@@ -236,4 +211,23 @@
         verify(mCarUserManagerHelper).setLastActiveUser(
                 lastActiveUserId, /* skipGlobalSetting= */ false);
     }
+
+    /**
+     * Test that the {@link CarUserService} sets default guest restrictions on first boot.
+     */
+    @Test
+    public void testInitializeGuestRestrictionsOnFirstRun() {
+        mockAdmin(/* adminId= */ 10);
+
+        mCarUserService.onReceive(mMockContext,
+                new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED));
+
+        verify(mCarUserManagerHelper).initDefaultGuestRestrictions();
+    }
+
+    private UserInfo mockAdmin(int adminId) {
+        UserInfo admin = new UserInfo(adminId, CarUserService.OWNER_NAME, UserInfo.FLAG_ADMIN);
+        doReturn(admin).when(mCarUserManagerHelper).createNewAdminUser(CarUserService.OWNER_NAME);
+        return admin;
+    }
 }
diff --git a/tests/robotests/src/com/android/car/users/CarUserManagerHelperRoboTest.java b/tests/robotests/src/com/android/car/users/CarUserManagerHelperRoboTest.java
index 9c39445..fcfd6dc 100644
--- a/tests/robotests/src/com/android/car/users/CarUserManagerHelperRoboTest.java
+++ b/tests/robotests/src/com/android/car/users/CarUserManagerHelperRoboTest.java
@@ -122,7 +122,7 @@
     }
 
     @Test
-    public void testGetAllUsersExcludesForegroundUser() {
+    public void testGetAllUsersExceptForegroundUser() {
         ShadowActivityManager.setCurrentUser(11);
         ShadowUserManager userManager = ShadowUserManager.getShadow();