Merge changes I9a4e1eee,I3d086741 into rvc-dev

* changes:
  Fix flaky ActivityBlockingActivityTest.
  Ignore invisible stacks in ABA
diff --git a/evs/apps/default/ConfigManager.h b/evs/apps/default/ConfigManager.h
index b3d283e..6acd83d 100644
--- a/evs/apps/default/ConfigManager.h
+++ b/evs/apps/default/ConfigManager.h
@@ -89,6 +89,8 @@
     }
     const std::vector<DisplayInfo>& getDisplays() const { return mDisplays; };
     const DisplayInfo& getActiveDisplay() const { return mDisplays[mActiveDisplayId]; };
+    void  useExternalMemory(bool flag) { mUseExternalMemory = flag; }
+    bool  getUseExternalMemory() const { return mUseExternalMemory; }
 
 private:
     // Camera information
@@ -98,6 +100,9 @@
     std::vector<DisplayInfo> mDisplays;
     int mActiveDisplayId;
 
+    // Memory management
+    bool mUseExternalMemory;
+
     // Car body information (assumes front wheel steering and origin at center of rear axel)
     // Note that units aren't specified and don't matter as long as all length units are consistent
     // within the JSON file from which we parse.  That is, if everything is in meters, that's fine.
diff --git a/evs/apps/default/EvsStateControl.cpp b/evs/apps/default/EvsStateControl.cpp
index b963551..e45fe2f 100644
--- a/evs/apps/default/EvsStateControl.cpp
+++ b/evs/apps/default/EvsStateControl.cpp
@@ -303,7 +303,8 @@
         if (mCameraList[desiredState].size() == 1) {
             // We have a camera assigned to this state for direct view.
             mDesiredRenderer = std::make_unique<RenderDirectView>(mEvs,
-                                                                  mCameraDescList[desiredState][0]);
+                                                                  mCameraDescList[desiredState][0],
+                                                                  mConfig);
             if (!mDesiredRenderer) {
                 LOG(ERROR) << "Failed to construct direct renderer.  Skipping state change.";
                 return false;
diff --git a/evs/apps/default/RenderDirectView.cpp b/evs/apps/default/RenderDirectView.cpp
index 4a8db70..2938521 100644
--- a/evs/apps/default/RenderDirectView.cpp
+++ b/evs/apps/default/RenderDirectView.cpp
@@ -42,9 +42,11 @@
 
 
 RenderDirectView::RenderDirectView(sp<IEvsEnumerator> enumerator,
-                                   const CameraDesc& camDesc) :
+                                   const CameraDesc& camDesc,
+                                   const ConfigManager& config) :
     mEnumerator(enumerator),
-    mCameraDesc(camDesc) {
+    mCameraDesc(camDesc),
+    mConfig(config) {
     /* Nothing to do */
 }
 
@@ -113,7 +115,8 @@
     mTexture.reset(createVideoTexture(mEnumerator,
                                       mCameraDesc.v1.cameraId.c_str(),
                                       foundCfg ? std::move(targetCfg) : nullptr,
-                                      sDisplay));
+                                      sDisplay,
+                                      mConfig.getUseExternalMemory()));
     if (!mTexture) {
         LOG(ERROR) << "Failed to set up video texture for " << mCameraDesc.v1.cameraId;
 // TODO:  For production use, we may actually want to fail in this case, but not yet...
diff --git a/evs/apps/default/RenderDirectView.h b/evs/apps/default/RenderDirectView.h
index 3514e05..65a94e2 100644
--- a/evs/apps/default/RenderDirectView.h
+++ b/evs/apps/default/RenderDirectView.h
@@ -35,7 +35,8 @@
 class RenderDirectView: public RenderBase {
 public:
     RenderDirectView(sp<IEvsEnumerator> enumerator,
-                     const CameraDesc& camDesc);
+                     const CameraDesc& camDesc,
+                     const ConfigManager& config);
 
     virtual bool activate() override;
     virtual void deactivate() override;
@@ -46,6 +47,7 @@
     sp<IEvsEnumerator>              mEnumerator;
     ConfigManager::CameraInfo       mCameraInfo;
     CameraDesc                      mCameraDesc;
+    const ConfigManager&            mConfig;
 
     std::unique_ptr<VideoTex>       mTexture;
 
diff --git a/evs/apps/default/StreamHandler.cpp b/evs/apps/default/StreamHandler.cpp
index 328f454..b1cfd1f 100644
--- a/evs/apps/default/StreamHandler.cpp
+++ b/evs/apps/default/StreamHandler.cpp
@@ -21,16 +21,73 @@
 
 #include <android-base/logging.h>
 #include <cutils/native_handle.h>
+#include <ui/GraphicBufferAllocator.h>
 
 using ::android::hardware::automotive::evs::V1_0::EvsResult;
 
 
-StreamHandler::StreamHandler(android::sp <IEvsCamera> pCamera) :
-    mCamera(pCamera)
-{
-    // We rely on the camera having at least two buffers available since we'll hold one and
-    // expect the camera to be able to capture a new image in the background.
-    pCamera->setMaxFramesInFlight(2);
+buffer_handle_t memHandle = nullptr;
+StreamHandler::StreamHandler(android::sp <IEvsCamera> pCamera,
+                             uint32_t numBuffers,
+                             bool useOwnBuffers,
+                             int32_t width,
+                             int32_t height)
+    : mCamera(pCamera),
+      mUseOwnBuffers(useOwnBuffers) {
+    if (!useOwnBuffers) {
+        // We rely on the camera having at least two buffers available since we'll hold one and
+        // expect the camera to be able to capture a new image in the background.
+        pCamera->setMaxFramesInFlight(numBuffers);
+    } else {
+        mOwnBuffers.resize(numBuffers);
+
+        // Acquire the graphics buffer allocator
+        android::GraphicBufferAllocator &alloc(android::GraphicBufferAllocator::get());
+        const auto usage = GRALLOC_USAGE_HW_TEXTURE |
+                           GRALLOC_USAGE_SW_READ_RARELY |
+                           GRALLOC_USAGE_SW_WRITE_OFTEN;
+        const auto format = HAL_PIXEL_FORMAT_RGBA_8888;
+        for (auto i = 0; i < numBuffers; ++i) {
+            unsigned pixelsPerLine;
+            android::status_t result = alloc.allocate(width,
+                                                      height,
+                                                      format,
+                                                      1,
+                                                      usage,
+                                                      &memHandle,
+                                                      &pixelsPerLine,
+                                                      0,
+                                                      "EvsApp");
+            if (result != android::NO_ERROR) {
+                LOG(ERROR) << __FUNCTION__ << " failed to allocate memory.";
+            } else {
+                BufferDesc_1_1 buf;
+                AHardwareBuffer_Desc* pDesc =
+                    reinterpret_cast<AHardwareBuffer_Desc *>(&buf.buffer.description);
+                pDesc->width = 640;
+                pDesc->height = 360;
+                pDesc->layers = 1;
+                pDesc->format = HAL_PIXEL_FORMAT_RGBA_8888;
+                pDesc->usage = GRALLOC_USAGE_HW_TEXTURE |
+                               GRALLOC_USAGE_SW_READ_RARELY |
+                               GRALLOC_USAGE_SW_WRITE_OFTEN;
+                pDesc->stride = pixelsPerLine;
+                buf.buffer.nativeHandle = memHandle;
+                buf.bufferId = i;   // Unique number to identify this buffer
+                mOwnBuffers[i] = buf;
+            }
+        }
+
+        int delta = 0;
+        EvsResult result = EvsResult::OK;
+        pCamera->importExternalBuffers(mOwnBuffers,
+                                       [&](auto _result, auto _delta) {
+                                           result = _result;
+                                           delta = _delta;
+                                       });
+
+        LOG(INFO) << delta << " buffers are imported by EVS.";
+    }
 }
 
 
@@ -42,6 +99,15 @@
     // At this point, the receiver thread is no longer running, so we can safely drop
     // our remote object references so they can be freed
     mCamera = nullptr;
+
+    if (mUseOwnBuffers) {
+        android::GraphicBufferAllocator &alloc(android::GraphicBufferAllocator::get());
+        for (auto& b : mOwnBuffers) {
+            alloc.free(b.buffer.nativeHandle);
+        }
+
+        mOwnBuffers.resize(0);
+    }
 }
 
 
diff --git a/evs/apps/default/StreamHandler.h b/evs/apps/default/StreamHandler.h
index cb7a288..f877c78 100644
--- a/evs/apps/default/StreamHandler.h
+++ b/evs/apps/default/StreamHandler.h
@@ -48,7 +48,11 @@
 public:
     virtual ~StreamHandler() { shutdown(); };
 
-    StreamHandler(android::sp <IEvsCamera> pCamera);
+    StreamHandler(android::sp <IEvsCamera> pCamera,
+                  uint32_t numBuffers = 2,
+                  bool useOwnBuffers = false,
+                  int32_t width = 640,
+                  int32_t height = 360);
     void shutdown();
 
     bool startStream();
@@ -83,6 +87,8 @@
     BufferDesc                  mBuffers[2];
     int                         mHeldBuffer = -1;   // Index of the one currently held by the client
     int                         mReadyBuffer = -1;  // Index of the newest available buffer
+    hidl_vec<BufferDesc_1_1>    mOwnBuffers;
+    bool                        mUseOwnBuffers;
 };
 
 
diff --git a/evs/apps/default/VideoTex.cpp b/evs/apps/default/VideoTex.cpp
index c9fc895..94e734a 100644
--- a/evs/apps/default/VideoTex.cpp
+++ b/evs/apps/default/VideoTex.cpp
@@ -136,7 +136,8 @@
 VideoTex* createVideoTexture(sp<IEvsEnumerator> pEnum,
                              const char* evsCameraId,
                              std::unique_ptr<Stream> streamCfg,
-                             EGLDisplay glDisplay) {
+                             EGLDisplay glDisplay,
+                             bool useExternalMemory) {
     // Set up the camera to feed this texture
     sp<IEvsCamera> pCamera = nullptr;
     if (streamCfg != nullptr) {
@@ -153,7 +154,11 @@
     }
 
     // Initialize the stream that will help us update this texture's contents
-    sp<StreamHandler> pStreamHandler = new StreamHandler(pCamera);
+    sp<StreamHandler> pStreamHandler = new StreamHandler(pCamera,
+                                                         2,     // number of buffers
+                                                         useExternalMemory,
+                                                         streamCfg->width,
+                                                         streamCfg->height);
     if (pStreamHandler.get() == nullptr) {
         LOG(ERROR) << "Failed to allocate FrameHandler";
         return nullptr;
diff --git a/evs/apps/default/VideoTex.h b/evs/apps/default/VideoTex.h
index 00e9faa..d884faa 100644
--- a/evs/apps/default/VideoTex.h
+++ b/evs/apps/default/VideoTex.h
@@ -37,7 +37,8 @@
     friend VideoTex* createVideoTexture(sp<IEvsEnumerator> pEnum,
                                         const char *evsCameraId,
                                         std::unique_ptr<Stream> streamCfg,
-                                        EGLDisplay glDisplay);
+                                        EGLDisplay glDisplay,
+                                        bool useExternalMemory);
 
 public:
     VideoTex() = delete;
@@ -64,6 +65,7 @@
 VideoTex* createVideoTexture(sp<IEvsEnumerator> pEnum,
                              const char * deviceName,
                              std::unique_ptr<Stream> streamCfg,
-                             EGLDisplay glDisplay);
+                             EGLDisplay glDisplay,
+                             bool useExternalMemory = false);
 
 #endif // VIDEOTEX_H
diff --git a/evs/apps/default/evs_app.cpp b/evs/apps/default/evs_app.cpp
index 65519c7..a968990 100644
--- a/evs/apps/default/evs_app.cpp
+++ b/evs/apps/default/evs_app.cpp
@@ -76,6 +76,7 @@
     bool printHelp = false;
     const char* evsServiceName = "default";
     int displayId = 1;
+    bool useExternalMemory = false;
     for (int i=1; i< argc; i++) {
         if (strcmp(argv[i], "--test") == 0) {
             useVehicleHal = false;
@@ -87,6 +88,8 @@
             printHelp = true;
         } else if (strcmp(argv[i], "--display") == 0) {
             displayId = std::stoi(argv[++i]);
+        } else if (strcmp(argv[i], "--extmem") == 0) {
+            useExternalMemory = true;
         } else {
             printf("Ignoring unrecognized command line arg '%s'\n", argv[i]);
             printHelp = true;
@@ -98,6 +101,7 @@
         printf("  --hw      Bypass EvsManager by connecting directly to EvsEnumeratorHw\n");
         printf("  --mock    Connect directly to EvsEnumeratorHw-Mock\n");
         printf("  --display Specify the display to use\n");
+        printf("  --extmem  Application allocates buffers to capture camera frames\n");
     }
 
     // Load our configuration information
@@ -135,6 +139,7 @@
         return 1;
     }
     config.setActiveDisplayId(displayId);
+    config.useExternalMemory(useExternalMemory);
 
     // Connect to the Vehicle HAL so we can monitor state
     sp<IVehicle> pVnet;
diff --git a/evs/manager/1.1/HalCamera.cpp b/evs/manager/1.1/HalCamera.cpp
index 902366e..fef7e9e 100644
--- a/evs/manager/1.1/HalCamera.cpp
+++ b/evs/manager/1.1/HalCamera.cpp
@@ -153,6 +153,58 @@
 }
 
 
+bool HalCamera::changeFramesInFlight(const hidl_vec<BufferDesc_1_1>& buffers,
+                                     int* delta) {
+    // Return immediately if a list is empty.
+    if (buffers.size() < 1) {
+        LOG(DEBUG) << "No external buffers to add.";
+        return true;
+    }
+
+    // Walk all our clients and count their currently required frames
+    auto bufferCount = 0;
+    for (auto&& client :  mClients) {
+        sp<VirtualCamera> virtCam = client.promote();
+        if (virtCam != nullptr) {
+            bufferCount += virtCam->getAllowedBuffers();
+        }
+    }
+
+    EvsResult status = EvsResult::OK;
+    // Ask the hardware for the resulting buffer count
+    mHwCamera->importExternalBuffers(buffers,
+                                     [&](auto result, auto added) {
+                                         status = result;
+                                         *delta = added;
+                                     });
+    if (status != EvsResult::OK) {
+        LOG(ERROR) << "Failed to add external capture buffers.";
+        return false;
+    }
+
+    bufferCount += *delta;
+
+    // Update the size of our array of outstanding frame records
+    std::vector<FrameRecord> newRecords;
+    newRecords.reserve(bufferCount);
+
+    // Copy and compact the old records that are still active
+    for (const auto& rec : mFrames) {
+        if (rec.refCount > 0) {
+            newRecords.emplace_back(rec);
+        }
+    }
+
+    if (newRecords.size() > (unsigned)bufferCount) {
+        LOG(WARNING) << "We found more frames in use than requested.";
+    }
+
+    mFrames.swap(newRecords);
+
+    return true;
+}
+
+
 UniqueFence HalCamera::requestNewFrame(sp<VirtualCamera> client,
                                        const int64_t lastTimestamp) {
     if (!mSyncSupported) {
diff --git a/evs/manager/1.1/HalCamera.h b/evs/manager/1.1/HalCamera.h
index b888add..970dc16 100644
--- a/evs/manager/1.1/HalCamera.h
+++ b/evs/manager/1.1/HalCamera.h
@@ -79,6 +79,8 @@
     std::string         getId()             { return mId; }
     Stream&             getStreamConfig()   { return mStreamConfig; }
     bool                changeFramesInFlight(int delta);
+    bool                changeFramesInFlight(const hardware::hidl_vec<BufferDesc_1_1>& buffers,
+                                             int* delta);
     UniqueFence         requestNewFrame(sp<VirtualCamera> virtualCamera,
                                         const int64_t timestamp);
 
diff --git a/evs/manager/1.1/VirtualCamera.cpp b/evs/manager/1.1/VirtualCamera.cpp
index 6e203aa..e9d7b3f 100644
--- a/evs/manager/1.1/VirtualCamera.cpp
+++ b/evs/manager/1.1/VirtualCamera.cpp
@@ -865,6 +865,34 @@
 }
 
 
+Return<void>
+VirtualCamera::importExternalBuffers(const hidl_vec<BufferDesc_1_1>& buffers,
+                                     importExternalBuffers_cb _hidl_cb) {
+    if (mHalCamera.size() > 1) {
+        LOG(WARNING) << "Logical camera device does not support " << __FUNCTION__;
+        _hidl_cb(EvsResult::UNDERLYING_SERVICE_ERROR, 0);
+        return {};
+    }
+
+    auto pHwCamera = mHalCamera.begin()->second.promote();
+    if (pHwCamera == nullptr) {
+        LOG(WARNING) << "Camera device " << mHalCamera.begin()->first << " is not alive.";
+        _hidl_cb(EvsResult::UNDERLYING_SERVICE_ERROR, 0);
+        return {};
+    }
+
+    int delta = 0;
+    if (!pHwCamera->changeFramesInFlight(buffers, &delta)) {
+        LOG(ERROR) << "Failed to add extenral capture buffers.";
+        _hidl_cb(EvsResult::UNDERLYING_SERVICE_ERROR, 0);
+        return {};
+    }
+
+    mFramesAllowed += delta;
+    _hidl_cb(EvsResult::OK, delta);
+    return {};
+}
+
 } // namespace implementation
 } // namespace V1_1
 } // namespace evs
diff --git a/evs/manager/1.1/VirtualCamera.h b/evs/manager/1.1/VirtualCamera.h
index 63744d9..764d6c5 100644
--- a/evs/manager/1.1/VirtualCamera.h
+++ b/evs/manager/1.1/VirtualCamera.h
@@ -105,7 +105,8 @@
                                           const hidl_vec<uint8_t>& opaqueValue) override;
     Return<void>      getExtendedInfo_1_1(uint32_t opaqueIdentifier,
                                           getExtendedInfo_1_1_cb _hidl_cb) override;
-
+    Return<void>      importExternalBuffers(const hidl_vec<BufferDesc_1_1>& buffers,
+                                            importExternalBuffers_cb _hidl_cb) override;
 
 
 private:
diff --git a/evs/sampleDriver/EvsV4lCamera.cpp b/evs/sampleDriver/EvsV4lCamera.cpp
index eefad2a..fdb8937 100644
--- a/evs/sampleDriver/EvsV4lCamera.cpp
+++ b/evs/sampleDriver/EvsV4lCamera.cpp
@@ -449,6 +449,84 @@
 }
 
 
+Return<void>
+EvsV4lCamera::importExternalBuffers(const hidl_vec<BufferDesc_1_1>& buffers,
+                                    importExternalBuffers_cb _hidl_cb) {
+    LOG(DEBUG) << __FUNCTION__;
+
+    // If we've been displaced by another owner of the camera, then we can't do anything else
+    if (!mVideo.isOpen()) {
+        LOG(WARNING) << "Ignoring a request add external buffers "
+                     << "when camera has been lost.";
+        _hidl_cb(EvsResult::UNDERLYING_SERVICE_ERROR, mFramesAllowed);
+        return {};
+    }
+
+    auto numBuffersToAdd = buffers.size();
+    if (numBuffersToAdd < 1) {
+        LOG(DEBUG) << "No buffers to add.";
+        _hidl_cb(EvsResult::OK, mFramesAllowed);
+        return {};
+    }
+
+    {
+        std::scoped_lock<std::mutex> lock(mAccessLock);
+
+        if (numBuffersToAdd > (MAX_BUFFERS_IN_FLIGHT - mFramesAllowed)) {
+            numBuffersToAdd -= (MAX_BUFFERS_IN_FLIGHT - mFramesAllowed);
+            LOG(WARNING) << "Exceed the limit on number of buffers.  "
+                         << numBuffersToAdd << " buffers will be added only.";
+        }
+
+        GraphicBufferMapper& mapper = GraphicBufferMapper::get();
+        const auto before = mFramesAllowed;
+        for (auto i = 0; i < numBuffersToAdd; ++i) {
+            // TODO: reject if external buffer is configured differently.
+            auto& b = buffers[i];
+            const AHardwareBuffer_Desc* pDesc =
+                reinterpret_cast<const AHardwareBuffer_Desc *>(&b.buffer.description);
+
+            // Import a buffer to add
+            buffer_handle_t memHandle = nullptr;
+            status_t result = mapper.importBuffer(b.buffer.nativeHandle,
+                                                  pDesc->width,
+                                                  pDesc->height,
+                                                  1,
+                                                  pDesc->format,
+                                                  pDesc->usage,
+                                                  pDesc->stride,
+                                                  &memHandle);
+            if (result != android::NO_ERROR || !memHandle) {
+                LOG(WARNING) << "Failed to import a buffer " << b.bufferId;
+                continue;
+            }
+
+            auto stored = false;
+            for (auto&& rec : mBuffers) {
+                if (rec.handle == nullptr) {
+                    // Use this existing entry
+                    rec.handle = memHandle;
+                    rec.inUse = false;
+
+                    stored = true;
+                    break;
+                }
+            }
+
+            if (!stored) {
+                // Add a BufferRecord wrapping this handle to our set of available buffers
+                mBuffers.emplace_back(memHandle);
+            }
+
+            ++mFramesAllowed;
+        }
+
+        _hidl_cb(EvsResult::OK, mFramesAllowed - before);
+        return {};
+    }
+}
+
+
 EvsResult EvsV4lCamera::doneWithFrame_impl(const uint32_t bufferId,
                                            const buffer_handle_t memHandle) {
     std::lock_guard <std::mutex> lock(mAccessLock);
@@ -649,7 +727,7 @@
     }
 
     if (!readyForFrame) {
-        // We need to return the vide buffer so it can capture a new frame
+        // We need to return the video buffer so it can capture a new frame
         mVideo.markFrameConsumed();
     } else {
         // Assemble the buffer description we'll transmit below
@@ -675,10 +753,11 @@
         // causes SEGV_MAPPER.
         void *targetPixels = nullptr;
         GraphicBufferMapper &mapper = GraphicBufferMapper::get();
-        status_t result = mapper.lock(bufDesc_1_1.buffer.nativeHandle,
-                    GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_NEVER,
-                    android::Rect(pDesc->width, pDesc->height),
-                    (void **)&targetPixels);
+        status_t result =
+            mapper.lock(bufDesc_1_1.buffer.nativeHandle,
+                        GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_NEVER,
+                        android::Rect(pDesc->width, pDesc->height),
+                        (void **)&targetPixels);
 
         // If we failed to lock the pixel buffer, we're about to crash, but log it first
         if (!targetPixels) {
@@ -740,6 +819,7 @@
             // Since we didn't actually deliver it, mark the frame as available
             std::lock_guard<std::mutex> lock(mAccessLock);
             mBuffers[idx].inUse = false;
+
             mFramesInUse--;
         }
     }
diff --git a/evs/sampleDriver/EvsV4lCamera.h b/evs/sampleDriver/EvsV4lCamera.h
index 3dc9f06..c5b15a9 100644
--- a/evs/sampleDriver/EvsV4lCamera.h
+++ b/evs/sampleDriver/EvsV4lCamera.h
@@ -24,8 +24,9 @@
 #include <android/hardware/camera/device/3.2/ICameraDevice.h>
 #include <ui/GraphicBuffer.h>
 
-#include <thread>
 #include <functional>
+#include <thread>
+#include <set>
 
 #include "VideoCapture.h"
 #include "ConfigManager.h"
@@ -85,6 +86,8 @@
                                           const hidl_vec<uint8_t>& opaqueValue) override;
     Return<void>      getExtendedInfo_1_1(uint32_t opaqueIdentifier,
                                           getExtendedInfo_1_1_cb _hidl_cb) override;
+    Return<void>      importExternalBuffers(const hidl_vec<BufferDesc_1_1>& buffers,
+                                            importExternalBuffers_cb _hidl_cb) override;
 
     static sp<EvsV4lCamera> Create(const char *deviceName);
     static sp<EvsV4lCamera> Create(const char *deviceName,
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index eec31b3..047ce18 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -24,6 +24,7 @@
 import android.car.ICar;
 import android.car.cluster.renderer.IInstrumentClusterNavigation;
 import android.car.user.CarUserManager;
+import android.car.user.CarUserManager.UserLifecycleEvent;
 import android.car.userlib.CarUserManagerHelper;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -352,24 +353,15 @@
     public void onUserLifecycleEvent(int eventType, long timestampMs, int fromUserId,
             int toUserId) {
         assertCallingFromSystemProcess();
-        Log.i(TAG, "onUserLifecycleEvent(" + CarUserManager.lifecycleEventTypeToString(eventType)
-                + ", " + toUserId + ")");
-        mUserMetrics.onEvent(eventType, timestampMs, fromUserId, toUserId);
+        Log.i(TAG, "onUserLifecycleEvent("
+                + CarUserManager.lifecycleEventTypeToString(eventType) + ", " + toUserId + ")");
+        mCarUserService.onUserLifecycleEvent(new UserLifecycleEvent(eventType, toUserId));
         if (eventType == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING) {
-            setUserLockStatus(toUserId);
-        } else if (eventType == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING) {
-            onSwitchUser(toUserId);
+            // TODO(b/145689885): CarMediaService should implement UserLifecycleListener,
+            //     eliminiating the need for this check.
+            mCarMediaService.setUserLockStatus(toUserId, /* unlocked= */ true);
         }
-    }
-
-    private void setUserLockStatus(int userId) {
-        mCarUserService.setUserLockStatus(userId, /* unlocked= */ true);
-        mCarMediaService.setUserLockStatus(userId, /* unlocked= */ true);
-    }
-
-    private void onSwitchUser(int userId) {
-        Log.i(TAG, "Foreground user switched to " + userId);
-        mCarUserService.onSwitchUser(userId);
+        mUserMetrics.onEvent(eventType, timestampMs, fromUserId, toUserId);
     }
 
     @Override
diff --git a/service/src/com/android/car/stats/CarStatsService.java b/service/src/com/android/car/stats/CarStatsService.java
index 41b7013..20b451c 100644
--- a/service/src/com/android/car/stats/CarStatsService.java
+++ b/service/src/com/android/car/stats/CarStatsService.java
@@ -159,6 +159,7 @@
             StatsEvent e = StatsEvent.newBuilder()
                     .setAtomId(atomTag)
                     .writeInt(entry.getUid())
+                    .addBooleanAnnotation(CarStatsLog.ANNOTATION_ID_IS_UID, true)
 
                     .writeInt(entry.getLayerType())
                     .writeInt(entry.getLayerChannel())
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index ec10818..cfd3364 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -693,26 +693,11 @@
         }
     }
 
-    /**
-     * Sets user lock/unlocking status. This is coming from system server through ICar binder call.
-     *
-     * @param userId User id whoes lock status is changed.
-     * @param unlocked Unlocked (={@code true}) or locked (={@code false}).
-     *
-     * @deprecated TODO(b/151895715): method to be folded into onUserLifecycleEvent
-     */
-    @Deprecated
-    public void setUserLockStatus(@UserIdInt int userId, boolean unlocked) {
-        TimingsTraceLog t = new TimingsTraceLog(TAG_USER,
-                Trace.TRACE_TAG_SYSTEM_SERVER);
-        notifyUserLifecycleListeners(t,
+    private void unlockUser(@UserIdInt int userId) {
+        TimingsTraceLog t = new TimingsTraceLog(TAG_USER, Trace.TRACE_TAG_SYSTEM_SERVER);
+        notifyUserLifecycleListeners(
                 new UserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING, userId));
-
-        if (!unlocked) { // nothing else to do when it is locked back.
-            return;
-        }
-
-        t.traceBegin("setUserLockStatus-UnlockTasks-" + userId);
+        t.traceBegin("UnlockTasks-" + userId);
         ArrayList<Runnable> tasks = null;
         synchronized (mLockUser) {
             if (userId == UserHandle.USER_SYSTEM) {
@@ -720,7 +705,7 @@
                     updateDefaultUserRestriction();
                     tasks = new ArrayList<>(mUser0UnlockTasks);
                     mUser0UnlockTasks.clear();
-                    mUser0Unlocked = unlocked;
+                    mUser0Unlocked = true;
                 }
             } else { // none user0
                 Integer user = userId;
@@ -836,13 +821,82 @@
     }
 
     /**
-     * Called when new foreground user started to boot.
-     *
-     * @param userId User id of new user.
-     * @deprecated TODO(b/151895715): method to be folded into onUserLifecycleEvent
+     * Notifies all registered {@link UserLifecycleListener} with the event passed as argument.
      */
-    @Deprecated
-    public void onSwitchUser(@UserIdInt int userId) {
+    public void onUserLifecycleEvent(UserLifecycleEvent event) {
+        int userId = event.getUserId();
+        if (event.getEventType() == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING) {
+            onSwitchUser(userId);
+        } else if (event.getEventType() == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING) {
+            unlockUser(userId);
+        }
+
+        // TODO(b/144120654): right now just the app listeners are running in the background so the
+        // CTS tests pass (as otherwise they might fail if a car service callback takes too long),
+        // but once we refactor the car service callback into lifecycle listeners, we should use a
+        // proper thread management (like a Threadpool / executor);
+
+        // Notify all user listeners
+        notifyUserLifecycleListeners(event);
+
+        // Notify all app listeners
+        notifyAppLifecycleListeners(event);
+    }
+
+    private void notifyAppLifecycleListeners(UserLifecycleEvent event) {
+        int listenersSize = mLifecycleListeners.size();
+        if (listenersSize == 0) {
+            Log.i(TAG_USER, "No app listener to be notified");
+            return;
+        }
+        new Thread(() -> {
+            // Must use a different TimingsTraceLog because it's another thread
+            TimingsTraceLog t = new TimingsTraceLog(TAG_USER, Trace.TRACE_TAG_SYSTEM_SERVER);
+            Log.i(TAG_USER, "Notifying " + listenersSize + " app listeners");
+            int userId = event.getUserId();
+            for (int i = 0; i < listenersSize; i++) {
+                int uid = mLifecycleListeners.keyAt(i);
+                IResultReceiver listener = mLifecycleListeners.valueAt(i);
+                t.traceBegin("notify-" + event.getEventType() + "-app-listener-" + uid);
+                Bundle data = new Bundle();
+                data.putInt(CarUserManager.BUNDLE_PARAM_ACTION, event.getEventType());
+                // TODO(b/144120654): should pass currentId from CarServiceHelperService so it
+                // can set BUNDLE_PARAM_PREVIOUS_USER_ID (and unit test it)
+                if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
+                    Log.d(TAG_USER, "Notifying listener for uid " + uid);
+                }
+                try {
+                    listener.send(userId, data);
+                } catch (RemoteException e) {
+                    Log.e(TAG_USER, "Error calling lifecycle listener", e);
+                } finally {
+                    t.traceEnd();
+                }
+            }
+        }, "SwitchUser-" + event.getUserId() + "-Listeners").start();
+    }
+
+    private void notifyUserLifecycleListeners(UserLifecycleEvent event) {
+        TimingsTraceLog t = new TimingsTraceLog(TAG_USER, Trace.TRACE_TAG_SYSTEM_SERVER);
+        if (mUserLifecycleListeners.isEmpty()) {
+            Log.i(TAG_USER, "Not notifying internal UserLifecycleListeners");
+            return;
+        }
+        t.traceBegin("notifyInternalUserLifecycleListeners");
+        for (UserLifecycleListener listener : mUserLifecycleListeners) {
+            t.traceBegin("notify-" + event.getEventType() + "-listener-" + listener);
+            try {
+                listener.onEvent(event);
+            } catch (RuntimeException e) {
+                Log.e(TAG_USER,
+                        "Exception raised when invoking onEvent for " + listener, e);
+            }
+            t.traceEnd();
+        }
+        t.traceEnd();
+    }
+
+    private void onSwitchUser(@UserIdInt int userId) {
         Log.i(TAG_USER, "onSwitchUser() callback for user " + userId);
         TimingsTraceLog t = new TimingsTraceLog(TAG_USER, Trace.TRACE_TAG_SYSTEM_SERVER);
         t.traceBegin("onSwitchUser-" + userId);
@@ -857,63 +911,6 @@
             setupPassengerUser();
             startFirstPassenger(userId);
         }
-
-        // TODO(b/144120654): right now just the app listeners are running in the background so the
-        // CTS tests pass (as otherwise they might fail if a car service callback takes too long),
-        // but once we refactor the car service callback into lifecycle listeners, we should use a
-        // proper thread management (like a Threadpool / executor);
-
-        int listenersSize = mLifecycleListeners.size();
-        if (listenersSize == 0) {
-            Log.i(TAG_USER, "Not notifying app listeners");
-        } else {
-            new Thread(() -> {
-                // Must use a different TimingsTraceLog because it's another thread
-                TimingsTraceLog t2 = new TimingsTraceLog(TAG_USER, Trace.TRACE_TAG_SYSTEM_SERVER);
-                Log.i(TAG_USER, "Notifying " + listenersSize + " listeners");
-                for (int i = 0; i < listenersSize; i++) {
-                    int uid = mLifecycleListeners.keyAt(i);
-                    IResultReceiver listener = mLifecycleListeners.valueAt(i);
-                    t2.traceBegin("notify-listener-" + uid);
-                    Bundle data = new Bundle();
-                    data.putInt(CarUserManager.BUNDLE_PARAM_ACTION,
-                            CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
-                    // TODO(b/144120654): should pass currentId from CarServiceHelperService so it
-                    // can set BUNDLE_PARAM_PREVIOUS_USER_ID (and unit test it)
-                    if (Log.isLoggable(TAG_USER, Log.DEBUG)) {
-                        Log.d(TAG_USER, "Notifying listener for uid " + uid);
-                    }
-                    try {
-                        listener.send(userId, data);
-                    } catch (RemoteException e) {
-                        Log.e(TAG_USER, "Error calling lifecycle listener", e);
-                    } finally {
-                        t2.traceEnd();
-                    }
-                }
-
-            }, "SwitchUser-" + userId + "-Listeners").start();
-        }
-
-        // TODO(b/145689885): not passing `from` parameter until it gets properly replaced
-        //     the expected Binder call.
-        notifyUserLifecycleListeners(t,
-                new UserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING, userId));
-    }
-
-    private void notifyUserLifecycleListeners(TimingsTraceLog t, UserLifecycleEvent event) {
-        t.traceBegin("notifyInternalUserLifecycleListeners");
-        for (UserLifecycleListener listener : mUserLifecycleListeners) {
-            t.traceBegin("onEvent-" + listener.getClass().getSimpleName());
-            try {
-                listener.onEvent(event);
-            } catch (RuntimeException e) {
-                Log.e(TAG_USER,
-                        "Exception raised when invoking onEvent for " + listener, e);
-            }
-            t.traceEnd();
-        }
-        t.traceEnd();
     }
 
     /**
@@ -1186,4 +1183,4 @@
         List<OccupantZoneInfo> zoneInfos = getOccupantZones(occupantType);
         return (zoneInfos.size() > 0) ? zoneInfos.get(0).zoneId : OccupantZoneInfo.INVALID_ZONE_ID;
     }
-}
\ No newline at end of file
+}
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/sms_received.xml b/tests/EmbeddedKitchenSinkApp/res/layout/sms_received.xml
index 0a329af..5c3bd12 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/sms_received.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/sms_received.xml
@@ -79,14 +79,30 @@
             android:id="@+id/sms_tel_num"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:text="+1-650-000-0000                    "
-            android:inputType="text" />
+            android:text="+1-650-000-0000, comma separated"
+            android:inputType="text"/>
+    </LinearLayout>
+    <LinearLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content">
         <Button
             android:id="@+id/sms_new_message"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_gravity="center"
             android:text="@string/sms_new_message_button"/>
+        <Button
+            android:id="@+id/mms_new_message"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:text="@string/mms_new_message_button"/>
+        <Button
+            android:id="@+id/reset_message_counter"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center"
+            android:text="@string/reset_message_counter_button"/>
     </LinearLayout>
 
     <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content"
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
index da2d341..00d73d6 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -257,7 +257,9 @@
     <string name="sms_connect" translatable="false">Connect</string>
     <string name="sms_disconnect" translatable="false">Disconnect</string>
     <string name="sms_send_message" translatable="false">Send</string>
-    <string name="sms_new_message_button" translatable="false">Send new message</string>
+    <string name="sms_new_message_button" translatable="false">Send new message (Short)</string>
+    <string name="mms_new_message_button" translatable="false">Send new message (Long)</string>
+    <string name="reset_message_counter_button" translatable="false">Reset message counter</string>
     <string name="sms_notifications" translatable="false">Toggle Notifications</string>
     <string name="sms_listing_button" translatable="false">List SMS</string>
     <string name="sms_listing_label_folder" translatable="false">Folder</string>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java
index 2f2e9fe..d73f6e3 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java
@@ -50,12 +50,53 @@
 import com.google.android.car.kitchensink.R;
 
 import java.util.Date;
+import java.util.HashSet;
 import java.util.List;
 
 @TargetApi(Build.VERSION_CODES.LOLLIPOP)
 public class MapMceTestFragment extends Fragment {
-    static final String MESSAGE_TO_SEND = "Im Busy Driving";
-    static final String NEW_MESSAGE_TO_SEND = "This is new msg";
+    static final String REPLY_MESSAGE_TO_SEND = "I am currently driving.";
+    static final String NEW_MESSAGE_TO_SEND_SHORT = "This is a new message.";
+    static final String NEW_MESSAGE_TO_SEND_LONG = "Lorem ipsum dolor sit amet, consectetur "
+            + "adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna "
+            + "aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi "
+            + "ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in "
+            + "voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
+            + "occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim "
+            + "id est laborum.\n\nCurabitur pretium tincidunt lacus. Nulla gravida orci a odio. "
+            + "Nullam varius, turpis et commodo pharetra, est eros bibendum elit, nec luctus "
+            + "magna felis sollicitudin mauris. Integer in mauris eu nibh euismod gravida. Duis "
+            + "ac tellus et risus vulputate vehicula. Donec lobortis risus a elit. Etiam tempor. "
+            + "Ut ullamcorper, ligula eu tempor congue, eros est euismod turpis, id tincidunt "
+            + "sapien risus a quam. Maecenas fermentum consequat mi. Donec fermentum. "
+            + "Pellentesque malesuada nulla a mi. Duis sapien sem, aliquet nec, commodo eget, "
+            + "consequat quis, neque. Aliquam faucibus, elit ut dictum aliquet, felis nisl "
+            + "adipiscing sapien, sed malesuada diam lacus eget erat. Cras mollis scelerisque "
+            + "nunc. Nullam arcu. Aliquam consequat. Curabitur augue lorem, dapibus quis, "
+            + "laoreet et, pretium ac, nisi. Aenean magna nisl, mollis quis, molestie eu, "
+            + "feugiat in, orci. In hac habitasse platea dictumst.\n\nLorem ipsum dolor sit "
+            + "amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et "
+            + "dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco "
+            + "laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in "
+            + "reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. "
+            + "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia "
+            + "deserunt mollit anim id est laborum.\n\nCurabitur pretium tincidunt lacus. Nulla "
+            + "gravida orci a odio. Nullam varius, turpis et commodo pharetra, est eros bibendum "
+            + "elit, nec luctus magna felis sollicitudin mauris. Integer in mauris eu nibh "
+            + "euismod gravida. Duis ac tellus et risus vulputate vehicula. Donec lobortis risus "
+            + "a elit. Etiam tempor. Ut ullamcorper, ligula eu tempor congue, eros est euismod "
+            + "turpis, id tincidunt sapien risus a quam. Maecenas fermentum consequat mi. Donec "
+            + "fermentum. Pellentesque malesuada nulla a mi. Duis sapien sem, aliquet nec, "
+            + "commodo eget, consequat quis, neque. Aliquam faucibus, elit ut dictum aliquet, "
+            + "felis nisl adipiscing sapien, sed malesuada diam lacus eget erat. Cras mollis "
+            + "scelerisque nunc. Nullam arcu. Aliquam consequat. Curabitur augue lorem, dapibus "
+            + "quis, laoreet et, pretium ac, nisi. Aenean magna nisl, mollis quis, molestie eu, "
+            + "feugiat in, orci. In hac habitasse platea dictumst.";
+    private static final int SEND_NEW_SMS_SHORT = 1;
+    private static final int SEND_NEW_SMS_LONG = 2;
+    private static final int SEND_NEW_MMS_SHORT = 3;
+    private static final int SEND_NEW_MMS_LONG = 4;
+    private int mSendNewMsgCounter = 0;
     private static final String TAG = "CAR.BLUETOOTH.KS";
     private static final int SEND_SMS_PERMISSIONS_REQUEST = 1;
     BluetoothMapClient mMapProfile;
@@ -86,7 +127,9 @@
         Button reply = (Button) v.findViewById(R.id.reply);
         Button checkMessages = (Button) v.findViewById(R.id.check_messages);
         mBluetoothDevice = (TextView) v.findViewById(R.id.bluetoothDevice);
-        Button sendNewMsg = (Button) v.findViewById(R.id.sms_new_message);
+        Button sendNewMsgShort = (Button) v.findViewById(R.id.sms_new_message);
+        Button sendNewMsgLong = (Button) v.findViewById(R.id.mms_new_message);
+        Button resetSendNewMsgCounter = (Button) v.findViewById(R.id.reset_message_counter);
         mSmsTelNum = (EditText) v.findViewById(R.id.sms_tel_num);
         mOriginator = (EditText) v.findViewById(R.id.messageOriginator);
         mOriginatorDisplayName = (TextView) v.findViewById(R.id.messageOriginatorDisplayName);
@@ -114,18 +157,29 @@
             @Override
             public void onClick(View view) {
                 sendMessage(new Uri[]{Uri.parse(mOriginator.getText().toString())},
-                        MESSAGE_TO_SEND);
+                        REPLY_MESSAGE_TO_SEND);
             }
         });
 
-        sendNewMsg.setOnClickListener(new View.OnClickListener() {
+        sendNewMsgShort.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View view) {
-                String s = mSmsTelNum.getText().toString();
-                Toast.makeText(getContext(), "sending msg to " + s, Toast.LENGTH_SHORT).show();
-                Uri.Builder builder = new Uri.Builder();
-                Uri uri = builder.appendPath(s).scheme(PhoneAccount.SCHEME_TEL).build();
-                sendMessage(new Uri[]{uri}, NEW_MESSAGE_TO_SEND);
+                sendNewMsgOnClick(SEND_NEW_SMS_SHORT);
+            }
+        });
+
+        sendNewMsgLong.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                sendNewMsgOnClick(SEND_NEW_MMS_LONG);
+            }
+        });
+
+        resetSendNewMsgCounter.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                mSendNewMsgCounter = 0;
+                Toast.makeText(getContext(), "Counter reset to zero.", Toast.LENGTH_SHORT).show();
             }
         });
 
@@ -212,6 +266,28 @@
         }
     }
 
+    private void sendNewMsgOnClick(int msgType) {
+        String messageToSend = "";
+        switch (msgType) {
+            case SEND_NEW_SMS_SHORT:
+                messageToSend = NEW_MESSAGE_TO_SEND_SHORT;
+                break;
+            case SEND_NEW_MMS_LONG:
+                messageToSend = NEW_MESSAGE_TO_SEND_LONG;
+                break;
+        }
+        String s = mSmsTelNum.getText().toString();
+        Toast.makeText(getContext(), "sending msg to " + s, Toast.LENGTH_SHORT).show();
+        HashSet<Uri> uris = new HashSet<Uri>();
+        Uri.Builder builder = new Uri.Builder();
+        for (String telNum : s.split(",")) {
+            uris.add(builder.path(telNum).scheme(PhoneAccount.SCHEME_TEL).build());
+        }
+        sendMessage(uris.toArray(new Uri[uris.size()]), Integer.toString(mSendNewMsgCounter)
+                + ":  " + messageToSend);
+        mSendNewMsgCounter += 1;
+    }
+
     private int getUploadingFeatureValue() {
         synchronized (mLock) {
             BluetoothDevice remoteDevice;
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/packageinfo/PackageInfoFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/packageinfo/PackageInfoFragment.java
index a43c510..039cc8c 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/packageinfo/PackageInfoFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/packageinfo/PackageInfoFragment.java
@@ -59,6 +59,7 @@
     private static final boolean DEBUG = true;
     private static final int PACKAGE_FLAGS = PackageManager.GET_META_DATA
             | PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES
+            | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS
             | PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES;
     private static final List<String> IMPORTANT_PERMISSIONS = Arrays.asList(
             "android.permission.INTERACT_ACROSS_USERS",
@@ -96,10 +97,7 @@
     private void refreshPackages() {
         List<PackageInfo> packages = new ArrayList<PackageInfo>();
         try {
-            packages = mPackageManager.getInstalledPackagesAsUser(PackageManager.GET_META_DATA
-                    | PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES
-                    | PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES,
-                    mUserToShow.id);
+            packages = mPackageManager.getInstalledPackagesAsUser(PACKAGE_FLAGS, mUserToShow.id);
             if (DEBUG) {
                 Log.d(TAG, "total packages found: " + packages.size());
             }
diff --git a/tests/OccupantAwarenessUxRestriction/Android.bp b/tests/OccupantAwarenessUxRestriction/Android.bp
deleted file mode 100644
index 0ee599a..0000000
--- a/tests/OccupantAwarenessUxRestriction/Android.bp
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (C) 2020 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//      http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-android_app {
-    name: "OccupantAwarenessUxRestriction",
-    platform_apis: true,
-
-    srcs: ["src/**/*.java"],
-
-    resource_dirs: ["res"],
-
-    libs: [
-        "android.car",
-    ],
-
-    static_libs: [
-        "vehicle-hal-support-lib",
-        "car-experimental-api-static-lib",
-        "com.google.android.material_material",
-        "androidx.appcompat_appcompat",
-        "androidx-constraintlayout_constraintlayout",
-    ],
-
-    certificate: "platform",
-
-    optimize: {
-        enabled: false,
-    },
-
-    privileged: true,
-
-    dex_preopt: {
-        enabled: false,
-    },
-
-    product_variables: {
-        pdk: {
-            enabled: false,
-        },
-    },
-}
diff --git a/tests/OccupantAwarenessUxRestriction/AndroidManifest.xml b/tests/OccupantAwarenessUxRestriction/AndroidManifest.xml
deleted file mode 100644
index 3e90224..0000000
--- a/tests/OccupantAwarenessUxRestriction/AndroidManifest.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="com.google.android.car.uxr.oas"
-    android:sharedUserId="android.uid.system">
-
-    <uses-sdk android:minSdkVersion="28"/>
-    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
-    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
-    <uses-permission android:name="android.car.permission.PERMISSION_READ_CAR_OCCUPANT_AWARENESS_STATE"/>
-    <application
-        android:allowBackup="true"
-        android:icon="@mipmap/adam_icon">
-        <activity
-            android:name=".LaunchpadActivity"
-            android:theme="@android:style/Theme.Black.NoTitleBar"
-            android:label="UxRestrictionLauncher"
-            android:exported="true">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-      <service
-        android:name=".OccupantAwarenessUxRestriction"
-        android:exported="true"
-        android:enabled="true">
-      </service>
-      <service
-        android:name=".AttentionProvider"
-        android:exported="true">
-      </service>
-    </application>
-</manifest>
diff --git a/tests/OccupantAwarenessUxRestriction/README.md b/tests/OccupantAwarenessUxRestriction/README.md
deleted file mode 100644
index 9e1a1d2..0000000
--- a/tests/OccupantAwarenessUxRestriction/README.md
+++ /dev/null
@@ -1,16 +0,0 @@
-# The application monitors Occupant Awareness detections and based on attention
-
-# value of driver, it restricts user interaction.
-
-# Start the application using command -
-
-``` shell
-adb shell am start
-com.google.android.car.uxr.sample/com.google.android.car.uxr.sample.LaunchpadActivity
-```
-
-# Stop the application using command -
-
-``` shell
-adb shell am force-stop com.google.android.car.uxr.sample
-```
diff --git a/tests/OccupantAwarenessUxRestriction/res/drawable/attention_buffer_progress_bar.xml b/tests/OccupantAwarenessUxRestriction/res/drawable/attention_buffer_progress_bar.xml
deleted file mode 100644
index e7a8fd0..0000000
--- a/tests/OccupantAwarenessUxRestriction/res/drawable/attention_buffer_progress_bar.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-<item android:id="@android:id/progress">
-    <clip>
-        <shape>
-            <gradient
-                android:startColor="#33FF33"
-                android:endColor="#008000"
-                android:angle="270"/>
-        </shape>
-    </clip>
-</item>
-</layer-list>
diff --git a/tests/OccupantAwarenessUxRestriction/res/drawable/speedbump_progress_bar_cyan.xml b/tests/OccupantAwarenessUxRestriction/res/drawable/speedbump_progress_bar_cyan.xml
deleted file mode 100644
index 0f5f079..0000000
--- a/tests/OccupantAwarenessUxRestriction/res/drawable/speedbump_progress_bar_cyan.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-<item android:id="@android:id/background">
-    <shape>
-        <corners android:radius="80dip"/>
-        <solid android:color="#779d9e9d"/>
-    </shape>
-</item>
-<item
-    android:id="@android:id/progress">
-    <clip>
-        <shape>
-            <corners android:radius="80dip"/>
-            <solid android:color="#7707b4f9"/>
-        </shape>
-    </clip>
-</item>
-</layer-list>
diff --git a/tests/OccupantAwarenessUxRestriction/res/drawable/speedbump_progress_bar_green.xml b/tests/OccupantAwarenessUxRestriction/res/drawable/speedbump_progress_bar_green.xml
deleted file mode 100644
index 965a4a2..0000000
--- a/tests/OccupantAwarenessUxRestriction/res/drawable/speedbump_progress_bar_green.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
-<item android:id="@android:id/background">
-    <shape>
-        <corners android:radius="80dip"/>
-        <solid android:color="#779d9e9d"/>
-    </shape>
-</item>
-<item
-    android:id="@android:id/progress">
-    <clip>
-        <shape>
-            <corners android:radius="80dip"/>
-            <solid android:color="#7733FF33"/>
-        </shape>
-    </clip>
-</item>
-</layer-list>
diff --git a/tests/OccupantAwarenessUxRestriction/res/layout-land/activity_launchpad.xml b/tests/OccupantAwarenessUxRestriction/res/layout-land/activity_launchpad.xml
deleted file mode 100644
index 1185067..0000000
--- a/tests/OccupantAwarenessUxRestriction/res/layout-land/activity_launchpad.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    tools:context=".LaunchpadActivity">
-</androidx.constraintlayout.widget.ConstraintLayout>
diff --git a/tests/OccupantAwarenessUxRestriction/res/layout-land/view_ux_restriction.xml b/tests/OccupantAwarenessUxRestriction/res/layout-land/view_ux_restriction.xml
deleted file mode 100644
index 09dae9c..0000000
--- a/tests/OccupantAwarenessUxRestriction/res/layout-land/view_ux_restriction.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-     android:layout_width="match_parent"
-     android:layout_height="0dp"
-     android:orientation="vertical"
-     android:background="#D9000000"
-     android:clickable="false"
-     android:focusable="false">
-     <ProgressBar
-        android:id="@+id/pbDriverAttentionBuffer"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        style="?android:attr/progressBarStyleHorizontal"
-        android:progressDrawable="@drawable/attention_buffer_progress_bar"
-        android:max="100"/>
-     <RelativeLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_horizontal">
-        <ProgressBar
-          android:id="@+id/pbSpeedBump"
-          android:layout_width="600dp"
-          android:layout_height="80dp"
-          android:layout_marginTop="400dp"
-          style="?android:attr/progressBarStyleHorizontal"
-          android:progressDrawable="@drawable/speedbump_progress_bar_cyan"/>
-        <TextView
-          android:text="Focus on the road"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:textColor="#000000"
-          android:layout_alignLeft="@+id/pbSpeedBump"
-          android:layout_alignRight="@+id/pbSpeedBump"
-          android:layout_alignTop="@+id/pbSpeedBump"
-          android:layout_alignBottom="@+id/pbSpeedBump"
-          android:gravity="center"/>
-     </RelativeLayout>
-</LinearLayout>
diff --git a/tests/OccupantAwarenessUxRestriction/res/layout-port/activity_launchpad.xml b/tests/OccupantAwarenessUxRestriction/res/layout-port/activity_launchpad.xml
deleted file mode 100644
index efbef43..0000000
--- a/tests/OccupantAwarenessUxRestriction/res/layout-port/activity_launchpad.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    tools:context=".LaunchpadActivity">
-</android.support.constraint.ConstraintLayout>
diff --git a/tests/OccupantAwarenessUxRestriction/res/layout-port/view_ux_restriction.xml b/tests/OccupantAwarenessUxRestriction/res/layout-port/view_ux_restriction.xml
deleted file mode 100644
index 09dae9c..0000000
--- a/tests/OccupantAwarenessUxRestriction/res/layout-port/view_ux_restriction.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-     android:layout_width="match_parent"
-     android:layout_height="0dp"
-     android:orientation="vertical"
-     android:background="#D9000000"
-     android:clickable="false"
-     android:focusable="false">
-     <ProgressBar
-        android:id="@+id/pbDriverAttentionBuffer"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        style="?android:attr/progressBarStyleHorizontal"
-        android:progressDrawable="@drawable/attention_buffer_progress_bar"
-        android:max="100"/>
-     <RelativeLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center_horizontal">
-        <ProgressBar
-          android:id="@+id/pbSpeedBump"
-          android:layout_width="600dp"
-          android:layout_height="80dp"
-          android:layout_marginTop="400dp"
-          style="?android:attr/progressBarStyleHorizontal"
-          android:progressDrawable="@drawable/speedbump_progress_bar_cyan"/>
-        <TextView
-          android:text="Focus on the road"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:textColor="#000000"
-          android:layout_alignLeft="@+id/pbSpeedBump"
-          android:layout_alignRight="@+id/pbSpeedBump"
-          android:layout_alignTop="@+id/pbSpeedBump"
-          android:layout_alignBottom="@+id/pbSpeedBump"
-          android:gravity="center"/>
-     </RelativeLayout>
-</LinearLayout>
diff --git a/tests/OccupantAwarenessUxRestriction/res/mipmap-xxhdpi/adam_icon.png b/tests/OccupantAwarenessUxRestriction/res/mipmap-xxhdpi/adam_icon.png
deleted file mode 100644
index 85e28ca..0000000
--- a/tests/OccupantAwarenessUxRestriction/res/mipmap-xxhdpi/adam_icon.png
+++ /dev/null
Binary files differ
diff --git a/tests/OccupantAwarenessUxRestriction/res/xml/device_filter.xml b/tests/OccupantAwarenessUxRestriction/res/xml/device_filter.xml
deleted file mode 100644
index 42cf9d5..0000000
--- a/tests/OccupantAwarenessUxRestriction/res/xml/device_filter.xml
+++ /dev/null
@@ -1,21 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-  ~ Copyright (C) 2020 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-
-<resource>
-    <!-- Intel realsense D435 camera in hex is 8086:0b07. The values below are in decimal -->
-    <usb-device vendor-id="32902" product-id="2823"/>
-</resource>
diff --git a/tests/OccupantAwarenessUxRestriction/src/com/google/android/car/uxr/oas/AttentionProvider.java b/tests/OccupantAwarenessUxRestriction/src/com/google/android/car/uxr/oas/AttentionProvider.java
deleted file mode 100644
index 54d69c2..0000000
--- a/tests/OccupantAwarenessUxRestriction/src/com/google/android/car/uxr/oas/AttentionProvider.java
+++ /dev/null
@@ -1,141 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.car.uxr.oas;
-
-import android.app.Notification;
-import android.app.NotificationChannel;
-import android.app.NotificationManager;
-import android.app.Service;
-import android.car.Car;
-import android.car.occupantawareness.OccupantAwarenessDetection;
-import android.car.occupantawareness.OccupantAwarenessManager;
-import android.car.occupantawareness.SystemStatusEvent;
-import android.content.Context;
-import android.content.Intent;
-import android.os.IBinder;
-import android.util.Log;
-
-public final class AttentionProvider extends Service {
-    private static final String TAG = AttentionProvider.class.getSimpleName();
-    private static final String NOTIFICATION_CHANNEL_ID = "adam_notification_channel_0";
-    private static final CharSequence NOTIFICATION_CHANNEL_NAME = "adam_channel_name";
-    private static final String NOTIFICATION_TITLE = "Android Automotive OS";
-    private static final String NOTIFICATION_TEXT = "ADAM Service";
-    private static final int NOTIFICATION_IMPORTANCE = NotificationManager.IMPORTANCE_NONE;
-    private static final int NOTIFICATION_ID = 101;
-    private static boolean sIsServiceRunning = false;
-
-    private OccupantAwarenessManager mOasManager;
-    private Car mCar;
-
-    @Override
-    public void onCreate() {
-        Log.i(TAG, "onCreate()");
-
-        Notification notification;
-        NotificationChannel channel =
-                new NotificationChannel(
-                        NOTIFICATION_CHANNEL_ID,
-                        NOTIFICATION_CHANNEL_NAME, NOTIFICATION_IMPORTANCE);
-
-        NotificationManager notificationManager =
-                (NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
-        notificationManager.createNotificationChannel(channel);
-
-        notification =
-            new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
-                .setSmallIcon(R.mipmap.adam_icon)
-                .setCategory(Notification.CATEGORY_SERVICE)
-                .setContentTitle(NOTIFICATION_TITLE)
-                .setContentText(NOTIFICATION_TEXT)
-                .build();
-
-        startForeground(NOTIFICATION_ID, notification);
-
-
-        mCar = Car.createCar(this);
-        mOasManager = (OccupantAwarenessManager) mCar.getCarManager(Car.OCCUPANT_AWARENESS_SERVICE);
-
-        if (mOasManager == null) {
-            Log.e(TAG, "Occupant Awareness Manager is NULL. Check that occupant awareness "
-                       + "feature is enabled");
-        } else {
-            Log.i(TAG, "Connected to Occupant Awareness Manager");
-
-            int caps =
-                    mOasManager.getCapabilityForRole(
-                            OccupantAwarenessDetection.VEHICLE_OCCUPANT_DRIVER);
-            Log.i(TAG, "Driver capabilities: " + caps);
-
-            Log.i(TAG, "Registering callback");
-            mOasManager.registerChangeCallback(new CallbackHandler());
-
-            Log.i(TAG, "Setup complete!");
-        }
-
-        UxRestriction.getInstance().enableUxRestriction(this);
-        sIsServiceRunning = true;
-    }
-
-    public static synchronized Boolean getIsServiceRunning() {
-        return sIsServiceRunning;
-    }
-
-    String systemToText(int statusCode) {
-        switch (statusCode) {
-            case SystemStatusEvent.SYSTEM_STATUS_READY:
-                return "ready";
-            case SystemStatusEvent.SYSTEM_STATUS_NOT_SUPPORTED:
-                return "not supported";
-            case SystemStatusEvent.SYSTEM_STATUS_NOT_READY:
-                return "not ready";
-            case SystemStatusEvent.SYSTEM_STATUS_SYSTEM_FAILURE:
-                return "failure";
-            default:
-                return "unknown code";
-        }
-    }
-
-    @Override
-    public IBinder onBind(Intent intent) {
-        // We don't bind to anything.
-        return null;
-    }
-
-    @Override
-    public void onDestroy() {
-        sIsServiceRunning = false;
-        super.onDestroy();
-    }
-
-    private final class CallbackHandler extends OccupantAwarenessManager.ChangeCallback {
-        @Override
-        public void onDetectionEvent(OccupantAwarenessDetection event) {
-            Log.i(TAG, "Got occupant awareness detection: " + event);
-            if (event.driverMonitoringDetection != null) {
-                UxRestriction.getInstance().setAttentionBufferValue(
-                        (int) (event.driverMonitoringDetection.gazeDurationMillis / 60));
-            }
-        }
-
-        @Override
-        public void onSystemStateChanged(SystemStatusEvent systemStatus) {
-            Log.i(TAG, "Got occupant awareness system status: " + systemStatus + " ("
-                        + systemToText(systemStatus.systemStatus) + ").");
-        }
-    }
-}
diff --git a/tests/OccupantAwarenessUxRestriction/src/com/google/android/car/uxr/oas/LaunchpadActivity.java b/tests/OccupantAwarenessUxRestriction/src/com/google/android/car/uxr/oas/LaunchpadActivity.java
deleted file mode 100644
index d4592cb..0000000
--- a/tests/OccupantAwarenessUxRestriction/src/com/google/android/car/uxr/oas/LaunchpadActivity.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.car.uxr.oas;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-
-/**
- * A blank activity whose sole purpose is to start the ADAM service(s), for ease of manual testing.
- */
-public class LaunchpadActivity extends Activity {
-    private static final String TAG = LaunchpadActivity.class.getSimpleName();
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        Log.i(TAG, "onCreate()");
-        super.onCreate(savedInstanceState);
-        // setContentView(R.layout.activity_launchpad);
-        this.startForegroundService(new Intent(this, AttentionProvider.class));
-        onPause();
-    }
-
-    @Override
-    protected void onDestroy() {
-        Log.i(TAG, "onDestroy()");
-        super.onDestroy();
-    }
-}
diff --git a/tests/OccupantAwarenessUxRestriction/src/com/google/android/car/uxr/oas/UxRestriction.java b/tests/OccupantAwarenessUxRestriction/src/com/google/android/car/uxr/oas/UxRestriction.java
deleted file mode 100644
index 9e660eb..0000000
--- a/tests/OccupantAwarenessUxRestriction/src/com/google/android/car/uxr/oas/UxRestriction.java
+++ /dev/null
@@ -1,154 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.google.android.car.uxr.oas;
-
-import android.content.Context;
-import android.graphics.PixelFormat;
-import android.graphics.drawable.Drawable;
-import android.os.Handler;
-import android.util.Log;
-import android.view.Gravity;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams;
-import android.widget.ProgressBar;
-
-public final class UxRestriction {
-
-    private static UxRestriction sUxRestriction;
-
-    private static final String TAG = UxRestriction.class.getSimpleName();
-    private static final int UXR_OVERLAY_VIEW_MIN_HEIGHT = 10;
-    private static final int ATTENTION_BUFFER_THRESHOLD_RESTRICT_TOUCH = 0;
-    private static final int ATTENTION_BUFFER_THRESHOLD_ENABLE_TOUCH = 40;
-    private static final int SPEEDBUMP_THRESHOLD_TURN_GREEN =
-            (int) (ATTENTION_BUFFER_THRESHOLD_ENABLE_TOUCH * 0.7);
-    private static final int ATTENTION_BUFFER_ENABLED_BACKGROUND_COLOR = 0xffff0000;
-    private static final int ATTENTION_BUFFER_DISABLED_BACKGROUND_COLOR = 0xff111111;
-    private static final int ATTENTION_BUFFER_DEFAULT_VALUE = 50;
-    private static final Handler sUiHandler = new Handler();
-
-    private boolean mIsTouchRestricted;
-    private WindowManager mWindowManager;
-    private View mUxrOverlayView;
-    private ProgressBar mPbDriverAttentionBuffer;
-    private ProgressBar mPbSpeedBump;
-    private LayoutParams mLayoutParamsEnableTouch;
-    private LayoutParams mLayoutParamsDisableTouch;
-
-    private Drawable mDrawableSpeedBumpCyan;
-    private Drawable mDrawableSpeedBumpGreen;
-
-    private UxRestriction() {
-        // private constructor to prevent direct instantiation of this class.
-    }
-
-    public static UxRestriction getInstance() {
-        if (sUxRestriction == null) {
-            sUxRestriction = new UxRestriction();
-        }
-        return sUxRestriction;
-    }
-
-    public void enableUxRestriction(Context context) {
-        Log.i(TAG, "onCreate()");
-        mUxrOverlayView = LayoutInflater.from(context).inflate(R.layout.view_ux_restriction, null);
-        mPbDriverAttentionBuffer = mUxrOverlayView.findViewById(R.id.pbDriverAttentionBuffer);
-        mPbDriverAttentionBuffer.setBackgroundColor(ATTENTION_BUFFER_ENABLED_BACKGROUND_COLOR);
-
-        mPbSpeedBump = mUxrOverlayView.findViewById(R.id.pbSpeedBump);
-        mPbSpeedBump.setMax(ATTENTION_BUFFER_THRESHOLD_ENABLE_TOUCH);
-        mDrawableSpeedBumpCyan =
-            context.getResources().getDrawable(R.drawable.speedbump_progress_bar_cyan);
-        mDrawableSpeedBumpGreen =
-            context.getResources().getDrawable(R.drawable.speedbump_progress_bar_green);
-
-        mLayoutParamsEnableTouch =
-            new LayoutParams(
-                WindowManager.LayoutParams.MATCH_PARENT,
-                UXR_OVERLAY_VIEW_MIN_HEIGHT,
-                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
-                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
-                PixelFormat.TRANSLUCENT);
-        mLayoutParamsEnableTouch.gravity = Gravity.TOP | Gravity.RIGHT;
-
-        mLayoutParamsDisableTouch =
-            new LayoutParams(
-                WindowManager.LayoutParams.MATCH_PARENT,
-                WindowManager.LayoutParams.MATCH_PARENT,
-                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
-                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
-                PixelFormat.TRANSLUCENT);
-        mLayoutParamsDisableTouch.gravity = Gravity.TOP | Gravity.RIGHT;
-
-        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
-        mWindowManager.addView(mUxrOverlayView, mLayoutParamsEnableTouch);
-
-        setAttentionBufferValue(ATTENTION_BUFFER_DEFAULT_VALUE);
-    }
-
-    private void runOnUiThread(Runnable runnable) {
-        sUiHandler.post(runnable);
-    }
-
-    private void restrictTouch() {
-        if (!mIsTouchRestricted) {
-            mIsTouchRestricted = true;
-            runOnUiThread(() -> mWindowManager.updateViewLayout(
-                    mUxrOverlayView, mLayoutParamsDisableTouch));
-            Log.i(TAG, "Touch disabled.");
-        }
-    }
-
-    private void enableTouch() {
-        if (mIsTouchRestricted) {
-            mIsTouchRestricted = false;
-            runOnUiThread(() -> mWindowManager.updateViewLayout(
-                    mUxrOverlayView, mLayoutParamsEnableTouch));
-            Log.i(TAG, "Touch enabled.");
-        }
-    }
-
-    public void setAttentionBufferValue(int value) {
-        mPbDriverAttentionBuffer.post(() -> mPbDriverAttentionBuffer.setProgress(value));
-        if (value <= ATTENTION_BUFFER_THRESHOLD_RESTRICT_TOUCH) {
-            restrictTouch();
-        } else if (value > ATTENTION_BUFFER_THRESHOLD_ENABLE_TOUCH) {
-            enableTouch();
-        }
-
-        if (value <= ATTENTION_BUFFER_THRESHOLD_ENABLE_TOUCH) {
-            mPbSpeedBump.post(() -> mPbSpeedBump.setProgress(value));
-            if (value > SPEEDBUMP_THRESHOLD_TURN_GREEN) {
-                mPbSpeedBump.post(() -> mPbSpeedBump.setProgressDrawable(mDrawableSpeedBumpGreen));
-            } else {
-                mPbSpeedBump.post(() -> mPbSpeedBump.setProgressDrawable(mDrawableSpeedBumpCyan));
-            }
-        }
-    }
-
-    public void disableUxRestriction() {
-        if (mUxrOverlayView != null) {
-            mWindowManager.removeView(mUxrOverlayView);
-        }
-    }
-
-    public void destroy() {
-        disableUxRestriction();
-    }
-}
diff --git a/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceControllerTest.java b/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceControllerTest.java
index 01e22b7..450ae93 100644
--- a/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceControllerTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceControllerTest.java
@@ -28,6 +28,8 @@
 
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
+import android.car.user.CarUserManager;
+import android.car.user.CarUserManager.UserLifecycleEvent;
 import android.car.userlib.CarUserManagerHelper;
 import android.content.ComponentName;
 import android.content.Context;
@@ -146,8 +148,9 @@
 
         // Unlock system user
         mockUserUnlock(UserHandle.USER_SYSTEM);
-        runOnMainThreadAndWaitForIdle(() -> mCarUserService.setUserLockStatus(
-                UserHandle.USER_SYSTEM, true));
+        runOnMainThreadAndWaitForIdle(() -> mCarUserService.onUserLifecycleEvent(
+                new UserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING,
+                        UserHandle.USER_SYSTEM)));
 
         mContext.assertStartedService(SERVICE_START_SYSTEM_UNLOCKED);
         mContext.verifyNoMoreServiceLaunches();
@@ -161,7 +164,10 @@
 
         // Switch user to foreground
         mockGetCurrentUser(FG_USER_ID);
-        runOnMainThreadAndWaitForIdle(() -> mCarUserService.onSwitchUser(FG_USER_ID));
+        when(ActivityManager.getCurrentUser()).thenReturn(FG_USER_ID);
+        runOnMainThreadAndWaitForIdle(() -> mCarUserService.onUserLifecycleEvent(
+                new UserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
+                        FG_USER_ID)));
 
         // Expect only services with ASAP trigger to be started
         mContext.assertBoundService(SERVICE_BIND_ALL_USERS_ASAP);
@@ -169,7 +175,9 @@
 
         // Unlock foreground user
         mockUserUnlock(FG_USER_ID);
-        runOnMainThreadAndWaitForIdle(() -> mCarUserService.setUserLockStatus(FG_USER_ID, true));
+        runOnMainThreadAndWaitForIdle(() -> mCarUserService.onUserLifecycleEvent(
+                new UserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING,
+                        FG_USER_ID)));
 
         mContext.assertBoundService(SERVICE_BIND_FG_USER_UNLOCKED);
         mContext.verifyNoMoreServiceLaunches();
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 eebcd29..36136f5 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
@@ -206,7 +206,7 @@
     @Test
     public void testDoesNotSetSystemUserRestrictions_IfRestrictionsAlreadySet() {
         putSettingsInt(CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET, 1);
-        mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
+        sendUserUnlockingEvent(UserHandle.USER_SYSTEM);
         verify(mMockedUserManager, never())
                 .setUserRestriction(
                         UserManager.DISALLOW_MODIFY_ACCOUNTS,
@@ -233,7 +233,7 @@
 
         // Act
         int anyNewUserId = 11;
-        mCarUserService.onSwitchUser(anyNewUserId);
+        onUserSwitching(anyNewUserId);
 
         // Verify
         verifyListenerOnEventInvoked(anyNewUserId,
@@ -254,7 +254,7 @@
 
         // Act
         int anyNewUserId = 11;
-        mCarUserService.onSwitchUser(anyNewUserId);
+        onUserSwitching(anyNewUserId);
 
         // Verify
         verifyListenerOnEventInvoked(anyNewUserId,
@@ -274,7 +274,7 @@
      */
     @Test
     public void testDisableLocationForHeadlessSystemUserOnFirstRun() {
-        mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
+        sendUserUnlockingEvent(UserHandle.USER_SYSTEM);
         verify(mLocationManager).setLocationEnabledForUser(
                 /* enabled= */ false, UserHandle.of(UserHandle.USER_SYSTEM));
     }
@@ -289,7 +289,7 @@
                 NO_USER_INFO_FLAGS);
         doReturn(persistentUser).when(mMockedUserManager).getUserInfo(lastActiveUserId);
 
-        mCarUserService.onSwitchUser(lastActiveUserId);
+        onUserSwitching(lastActiveUserId);
 
         verify(mMockedCarUserManagerHelper).setLastActiveUser(lastActiveUserId);
     }
@@ -299,7 +299,7 @@
      */
     @Test
     public void testInitializeGuestRestrictions_IfNotAlreadySet() {
-        mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
+        sendUserUnlockingEvent(UserHandle.USER_SYSTEM);
         assertThat(getSettingsInt(CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET)).isEqualTo(1);
     }
 
@@ -309,14 +309,14 @@
     @Test
     public void test_DoesNotInitializeGuestRestrictions_IfAlreadySet() {
         putSettingsInt(CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET, 1);
-        mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
+        sendUserUnlockingEvent(UserHandle.USER_SYSTEM);
         verify(mMockedUserManager, never()).setDefaultGuestRestrictions(any(Bundle.class));
     }
 
     @Test
     public void testRunOnUser0UnlockImmediate() {
         mUser0TaskExecuted = false;
-        mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
+        sendUserUnlockingEvent(UserHandle.USER_SYSTEM);
         mCarUserService.runOnUser0Unlock(() -> {
             mUser0TaskExecuted = true;
         });
@@ -330,7 +330,7 @@
             mUser0TaskExecuted = true;
         });
         assertFalse(mUser0TaskExecuted);
-        mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
+        sendUserUnlockingEvent(UserHandle.USER_SYSTEM);
         assertTrue(mUser0TaskExecuted);
     }
 
@@ -359,35 +359,31 @@
         doReturn(user5Info).when(mMockedUserManager).getUserInfo(user5);
 
         doReturn(user1).when(() -> ActivityManager.getCurrentUser());
-        mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
+        sendUserUnlockingEvent(UserHandle.USER_SYSTEM);
         // user 0 should never go to that list.
         assertTrue(mCarUserService.getBackgroundUsersToRestart().isEmpty());
 
-        mCarUserService.setUserLockStatus(user1, true);
+        sendUserUnlockingEvent(user1);
         assertEquals(new Integer[]{user1},
                 mCarUserService.getBackgroundUsersToRestart().toArray());
 
         // user 2 background, ignore in restart list
-        mCarUserService.setUserLockStatus(user2, true);
-        mCarUserService.setUserLockStatus(user1, false);
+        sendUserUnlockingEvent(user2);
         assertEquals(new Integer[]{user1},
                 mCarUserService.getBackgroundUsersToRestart().toArray());
 
         doReturn(user3).when(() -> ActivityManager.getCurrentUser());
-        mCarUserService.setUserLockStatus(user3, true);
-        mCarUserService.setUserLockStatus(user2, false);
+        sendUserUnlockingEvent(user3);
         assertEquals(new Integer[]{user3, user1},
                 mCarUserService.getBackgroundUsersToRestart().toArray());
 
         doReturn(user4Guest).when(() -> ActivityManager.getCurrentUser());
-        mCarUserService.setUserLockStatus(user4Guest, true);
-        mCarUserService.setUserLockStatus(user3, false);
+        sendUserUnlockingEvent(user4Guest);
         assertEquals(new Integer[]{user3, user1},
                 mCarUserService.getBackgroundUsersToRestart().toArray());
 
         doReturn(user5).when(() -> ActivityManager.getCurrentUser());
-        mCarUserService.setUserLockStatus(user5, true);
-        mCarUserService.setUserLockStatus(user4Guest, false);
+        sendUserUnlockingEvent(user5);
         assertEquals(new Integer[]{user5, user3},
                 mCarUserService.getBackgroundUsersToRestart().toArray());
     }
@@ -410,14 +406,13 @@
         doReturn(user3Info).when(mMockedUserManager).getUserInfo(user3);
 
         doReturn(user1).when(() -> ActivityManager.getCurrentUser());
-        mCarUserService.setUserLockStatus(UserHandle.USER_SYSTEM, true);
-        mCarUserService.setUserLockStatus(user1, true);
+        sendUserUnlockingEvent(UserHandle.USER_SYSTEM);
+        sendUserUnlockingEvent(user1);
         doReturn(user2).when(() -> ActivityManager.getCurrentUser());
-        mCarUserService.setUserLockStatus(user2, true);
-        mCarUserService.setUserLockStatus(user1, false);
+        sendUserUnlockingEvent(user2);
+        sendUserUnlockingEvent(user1);
         doReturn(user3).when(() -> ActivityManager.getCurrentUser());
-        mCarUserService.setUserLockStatus(user3, true);
-        mCarUserService.setUserLockStatus(user2, false);
+        sendUserUnlockingEvent(user3);
 
         assertEquals(new Integer[]{user3, user2},
                 mCarUserService.getBackgroundUsersToRestart().toArray());
@@ -427,7 +422,7 @@
                 null, null, null);
         assertEquals(new Integer[]{user2},
                 mCarUserService.startAllBackgroundUsers().toArray());
-        mCarUserService.setUserLockStatus(user2, true);
+        sendUserUnlockingEvent(user2);
         assertEquals(new Integer[]{user3, user2},
                 mCarUserService.getBackgroundUsersToRestart().toArray());
 
@@ -438,7 +433,6 @@
         assertTrue(mCarUserService.stopBackgroundUser(user2));
         assertEquals(new Integer[]{user3, user2},
                 mCarUserService.getBackgroundUsersToRestart().toArray());
-        mCarUserService.setUserLockStatus(user2, false);
         assertEquals(new Integer[]{user3, user2},
                 mCarUserService.getBackgroundUsersToRestart().toArray());
     }
@@ -965,28 +959,28 @@
         int actualAction = resultData.getInt(CarUserService.BUNDLE_INITIAL_INFO_ACTION);
         assertWithMessage("wrong request type on bundle extra %s",
                 CarUserService.BUNDLE_INITIAL_INFO_ACTION).that(actualAction)
-            .isEqualTo(expectedAction);
+                .isEqualTo(expectedAction);
     }
 
     private void assertSwitchUserStatus(@NonNull Bundle resultData, int expectedStatus) {
         int actualStatus = resultData.getInt(CarUserManager.BUNDLE_USER_SWITCH_STATUS);
         assertWithMessage("wrong status on bundle extra %s",
                 CarUserManager.BUNDLE_USER_SWITCH_STATUS).that(actualStatus)
-                        .isEqualTo(expectedStatus);
+                .isEqualTo(expectedStatus);
     }
 
     private void assertSwitchUserMessageType(@NonNull Bundle resultData, int expectedType) {
         int actualType = resultData.getInt(CarUserManager.BUNDLE_USER_SWITCH_MSG_TYPE);
         assertWithMessage("wrong message type on bundle extra %s",
                 CarUserManager.BUNDLE_USER_SWITCH_MSG_TYPE).that(actualType)
-                        .isEqualTo(expectedType);
+                .isEqualTo(expectedType);
     }
 
     private void assertSwitchUserErrorMessage(@NonNull Bundle resultData, String expectedMsg) {
         String actualMsg = resultData.getString(CarUserManager.BUNDLE_USER_SWITCH_ERROR_MSG);
         assertWithMessage("wrong error message on bundle extra %s",
                 CarUserManager.BUNDLE_USER_SWITCH_ERROR_MSG).that(actualMsg)
-                        .isEqualTo(expectedMsg);
+                .isEqualTo(expectedMsg);
     }
 
     static final class FakeCarOccupantZoneService {
@@ -1031,7 +1025,6 @@
         }
     }
 
-
     // TODO(b/148403316): Refactor to use common fake settings provider
     private void mockSettingsGlobal() {
         when(Settings.Global.putInt(any(), eq(CarSettings.Global.DEFAULT_USER_RESTRICTIONS_SET),
@@ -1056,6 +1049,16 @@
                 key, /* default= */ 0);
     }
 
+    private void sendUserUnlockingEvent(int userId) {
+        mCarUserService.onUserLifecycleEvent(new UserLifecycleEvent(
+                CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING, userId));
+    }
+
+    private void onUserSwitching(int userId) {
+        mCarUserService.onUserLifecycleEvent(new UserLifecycleEvent(
+                CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING, userId));
+    }
+
     // TODO(b/149099817): move stuff below to common code
 
     /**
diff --git a/watchdog/sepolicy/private/carwatchdog.te b/watchdog/sepolicy/private/carwatchdog.te
index 2e7cc9d..28473b0 100644
--- a/watchdog/sepolicy/private/carwatchdog.te
+++ b/watchdog/sepolicy/private/carwatchdog.te
@@ -1,5 +1,6 @@
 # Car watchdog server
 typeattribute carwatchdogd coredomain;
+typeattribute carwatchdogd mlstrustedsubject;
 
 type carwatchdogd_exec, exec_type, file_type, system_file_type;
 
@@ -8,6 +9,9 @@
 binder_use(carwatchdogd)
 binder_service(carwatchdogd)
 
+# Scan through /proc/pid for all processes
+r_dir_file(carwatchdogd, domain)
+
 # Read /proc/uid_io/stats
 allow carwatchdogd proc_uid_io_stats:file r_file_perms;
 
diff --git a/watchdog/server/Android.bp b/watchdog/server/Android.bp
index 8f5c452..ee301db 100644
--- a/watchdog/server/Android.bp
+++ b/watchdog/server/Android.bp
@@ -56,6 +56,9 @@
         "src/ProcStat.cpp",
         "src/UidIoStats.cpp",
     ],
+    whole_static_libs: [
+        "libwatchdog_properties",
+    ],
     export_include_dirs: [
         "src",
     ],
diff --git a/watchdog/server/carwatchdogd.rc b/watchdog/server/carwatchdogd.rc
index 3220b90..b7027d7 100644
--- a/watchdog/server/carwatchdogd.rc
+++ b/watchdog/server/carwatchdogd.rc
@@ -12,7 +12,21 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+on early-init
+    # Number of top stats per category
+    setprop ro.carwatchdog.top_n_stats_per_category 5
+
+    # Number of top stats per sub-category
+    setprop ro.carwatchdog.top_n_stats_per_subcategory 3
+
+    # Below intervals are in seconds
+    setprop ro.carwatchdog.boottime_collection_interval 1
+    setprop ro.carwatchdog.periodic_collection_interval 10
+
+    # Cache size for the periodically collected records
+    setprop ro.carwatchdog.periodic_collection_buffer_size 180
+
 service carwatchdogd /system/bin/carwatchdogd
     class core
     user system
-    group system
+    group system readproc
diff --git a/watchdog/server/src/IoPerfCollection.cpp b/watchdog/server/src/IoPerfCollection.cpp
index bae5766..c4f0864 100644
--- a/watchdog/server/src/IoPerfCollection.cpp
+++ b/watchdog/server/src/IoPerfCollection.cpp
@@ -18,6 +18,7 @@
 
 #include "IoPerfCollection.h"
 
+#include <WatchdogProperties.sysprop.h>
 #include <android-base/file.h>
 #include <android-base/parseint.h>
 #include <android-base/stringprintf.h>
@@ -54,6 +55,20 @@
 
 namespace {
 
+const int32_t kDefaultTopNStatsPerCategory = 5;
+const int32_t kDefaultTopNStatsPerSubcategory = 3;
+const std::chrono::seconds kDefaultBoottimeCollectionInterval = 1s;
+const std::chrono::seconds kDefaultPeriodicCollectionInterval = 10s;
+// Number of periodic collection perf data snapshots to cache in memory.
+const int32_t kDefaultPeriodicCollectionBufferSize = 180;
+
+// Minimum collection interval between subsequent collections.
+const std::chrono::nanoseconds kMinCollectionInterval = 1s;
+
+// Default values for the custom collection interval and max_duration.
+const std::chrono::nanoseconds kCustomCollectionInterval = 10s;
+const std::chrono::nanoseconds kCustomCollectionDuration = 30min;
+
 const std::string kDumpMajorDelimiter = std::string(100, '-') + "\n";
 
 double percentage(uint64_t numer, uint64_t denom) {
@@ -219,20 +234,30 @@
             return Error(INVALID_OPERATION)
                     << "Cannot start I/O performance collection more than once";
         }
-
-        // TODO(b/148489461): Once |kTopNStatsPerCategory|, |kBoottimeCollectionInterval| and
-        // |kPeriodicCollectionInterval| constants are moved to read-only persistent properties,
-        // read and store them in the collection infos.
-
+        mTopNStatsPerCategory = static_cast<int>(
+                sysprop::topNStatsPerCategory().value_or(kDefaultTopNStatsPerCategory));
+        mTopNStatsPerSubcategory = static_cast<int>(
+                sysprop::topNStatsPerSubcategory().value_or(kDefaultTopNStatsPerSubcategory));
+        std::chrono::nanoseconds boottimeCollectionInterval =
+                std::chrono::duration_cast<std::chrono::nanoseconds>(
+                        std::chrono::seconds(sysprop::boottimeCollectionInterval().value_or(
+                                kDefaultBoottimeCollectionInterval.count())));
+        std::chrono::nanoseconds periodicCollectionInterval =
+                std::chrono::duration_cast<std::chrono::nanoseconds>(
+                        std::chrono::seconds(sysprop::periodicCollectionInterval().value_or(
+                                kDefaultPeriodicCollectionInterval.count())));
+        size_t periodicCollectionBufferSize =
+                static_cast<size_t>(sysprop::periodicCollectionBufferSize().value_or(
+                        kDefaultPeriodicCollectionBufferSize));
         mBoottimeCollection = {
-                .interval = kBoottimeCollectionInterval,
+                .interval = boottimeCollectionInterval,
                 .maxCacheSize = std::numeric_limits<std::size_t>::max(),
                 .lastCollectionUptime = 0,
                 .records = {},
         };
         mPeriodicCollection = {
-                .interval = kPeriodicCollectionInterval,
-                .maxCacheSize = kPeriodicCollectionBufferSize,
+                .interval = periodicCollectionInterval,
+                .maxCacheSize = periodicCollectionBufferSize,
                 .lastCollectionUptime = 0,
                 .records = {},
         };
@@ -646,7 +671,7 @@
     for (const auto& usage : topNReads) {
         if (usage->ios.isZero()) {
             // End of non-zero usage records. This case occurs when the number of UIDs with active
-            // I/O operations is < |kTopNStatsPerCategory|.
+            // I/O operations is < |ro.carwatchdog.top_n_stats_per_category|.
             break;
         }
         UidIoPerfData::Stats stats = {
@@ -666,7 +691,7 @@
     for (const auto& usage : topNWrites) {
         if (usage->ios.isZero()) {
             // End of non-zero usage records. This case occurs when the number of UIDs with active
-            // I/O operations is < |kTopNStatsPerCategory|.
+            // I/O operations is < |ro.carwatchdog.top_n_stats_per_category|.
             break;
         }
         UidIoPerfData::Stats stats = {
@@ -760,7 +785,7 @@
     for (const auto& it : topNIoBlockedUids) {
         if (it->ioBlockedTasksCnt == 0) {
             // End of non-zero elements. This case occurs when the number of UIDs with I/O blocked
-            // processes is < |kTopNStatsPerCategory|.
+            // processes is < |ro.carwatchdog.top_n_stats_per_category|.
             break;
         }
         ProcessIoPerfData::Stats stats = {
@@ -777,7 +802,7 @@
     for (const auto& it : topNMajorFaults) {
         if (it->majorFaults == 0) {
             // End of non-zero elements. This case occurs when the number of UIDs with major faults
-            // is < |kTopNStatsPerCategory|.
+            // is < |ro.carwatchdog.top_n_stats_per_category|.
             break;
         }
         ProcessIoPerfData::Stats stats = {
diff --git a/watchdog/server/src/IoPerfCollection.h b/watchdog/server/src/IoPerfCollection.h
index 7a242d9..4753600 100644
--- a/watchdog/server/src/IoPerfCollection.h
+++ b/watchdog/server/src/IoPerfCollection.h
@@ -45,21 +45,6 @@
 namespace automotive {
 namespace watchdog {
 
-// TODO(b/148489461): Replace the below constants (except kCustomCollection* and
-// kMinCollectionInterval constants) with read-only persistent properties.
-const int kTopNStatsPerCategory = 5;
-const std::chrono::nanoseconds kBoottimeCollectionInterval = 1s;
-const std::chrono::nanoseconds kPeriodicCollectionInterval = 10s;
-// Number of periodic collection perf data snapshots to cache in memory.
-const size_t kPeriodicCollectionBufferSize = 180;
-
-// Minimum collection interval between subsequent collections.
-const std::chrono::nanoseconds kMinCollectionInterval = 1s;
-
-// Default values for the custom collection interval and max_duration.
-const std::chrono::nanoseconds kCustomCollectionInterval = 10s;
-const std::chrono::nanoseconds kCustomCollectionDuration = 30min;
-
 constexpr const char* kStartCustomCollectionFlag = "--start_io";
 constexpr const char* kEndCustomCollectionFlag = "--stop_io";
 constexpr const char* kIntervalFlag = "--interval";
@@ -163,7 +148,6 @@
 class IoPerfCollection : public MessageHandler {
 public:
     IoPerfCollection() :
-          mTopNStatsPerCategory(kTopNStatsPerCategory),
           mHandlerLooper(new LooperWrapper()),
           mBoottimeCollection({}),
           mPeriodicCollection({}),
@@ -208,9 +192,8 @@
     // |maxDuration| is reached, the looper receives a message to end the collection, discards the
     // collected data, and starts the periodic collection. This is needed to ensure the custom
     // collection doesn't run forever when a subsequent |endCustomCollection| call is not received.
-    android::base::Result<void> startCustomCollection(
-            std::chrono::nanoseconds interval = kCustomCollectionInterval,
-            std::chrono::nanoseconds maxDuration = kCustomCollectionDuration);
+    android::base::Result<void> startCustomCollection(std::chrono::nanoseconds interval,
+                                                      std::chrono::nanoseconds maxDuration);
 
     // Ends the current custom collection, generates a dump, sends message to looper to start the
     // periodic collection, and returns immediately. Returns an error when there is no custom
@@ -244,8 +227,12 @@
     // Retrieves package manager from the default service manager.
     android::base::Result<void> retrievePackageManager();
 
+    // Top N per-UID stats per category.
     int mTopNStatsPerCategory;
 
+    // Top N per-process stats per subcategory.
+    int mTopNStatsPerSubcategory;
+
     // Thread on which the actual collection happens.
     std::thread mCollectionThread;
 
@@ -260,7 +247,7 @@
     CollectionInfo mBoottimeCollection GUARDED_BY(mMutex);
 
     // Info for the |CollectionEvent::PERIODIC| collection event. The cache size is limited by
-    // |kPeriodicCollectionBufferSize|.
+    // |ro.carwatchdog.periodic_collection_buffer_size|.
     CollectionInfo mPeriodicCollection GUARDED_BY(mMutex);
 
     // Info for the |CollectionEvent::CUSTOM| collection event. The info is cleared at the end of
diff --git a/watchdog/server/sysprop/Android.bp b/watchdog/server/sysprop/Android.bp
new file mode 100644
index 0000000..a3c507a
--- /dev/null
+++ b/watchdog/server/sysprop/Android.bp
@@ -0,0 +1,21 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+sysprop_library {
+  name: "libwatchdog_properties",
+  srcs: ["*.sysprop"],
+  property_owner: "Platform",
+  api_packages: ["android.sysprop"],
+  recovery_available: true,
+}
diff --git a/watchdog/server/sysprop/WatchdogProperties.sysprop b/watchdog/server/sysprop/WatchdogProperties.sysprop
new file mode 100644
index 0000000..3a4b21a
--- /dev/null
+++ b/watchdog/server/sysprop/WatchdogProperties.sysprop
@@ -0,0 +1,61 @@
+# Copyright (C) 2020 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the License);
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an AS IS BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+module: "android.automotive.watchdog.sysprop"
+owner: Platform
+
+# Top N per-UID statistics/category collected by the performance data collector.
+prop {
+    api_name: "topNStatsPerCategory"
+    type: Integer
+    scope: Internal
+    access: Readonly
+    prop_name: "ro.carwatchdog.top_n_stats_per_category"
+}
+
+# Top N per-process statistics/category collected by the performance data collector.
+prop {
+    api_name: "topNStatsPerSubcategory"
+    type: Integer
+    scope: Internal
+    access: Readonly
+    prop_name: "ro.carwatchdog.top_n_stats_per_subcategory"
+}
+
+# Interval in seconds between consecutive boot-time performance data collections.
+prop {
+    api_name: "boottimeCollectionInterval"
+    type: Integer
+    scope: Internal
+    access: Readonly
+    prop_name: "ro.carwatchdog.boottime_collection_interval"
+}
+
+# Interval in seconds between consecutive periodic performance data collections.
+prop {
+    api_name: "periodicCollectionInterval"
+    type: Integer
+    scope: Internal
+    access: Readonly
+    prop_name: "ro.carwatchdog.periodic_collection_interval"
+}
+
+# Maximum number of periodically collected records to be cached in memory.
+prop {
+    api_name: "periodicCollectionBufferSize"
+    type: Integer
+    scope: Internal
+    access: Readonly
+    prop_name: "ro.carwatchdog.periodic_collection_buffer_size"
+}
diff --git a/watchdog/server/sysprop/api/libwatchdog_properties-current.txt b/watchdog/server/sysprop/api/libwatchdog_properties-current.txt
new file mode 100644
index 0000000..ab59538
--- /dev/null
+++ b/watchdog/server/sysprop/api/libwatchdog_properties-current.txt
@@ -0,0 +1,33 @@
+props {
+  module: "android.automotive.watchdog.sysprop"
+  prop {
+    api_name: "boottimeCollectionInterval"
+    type: Integer
+    scope: Internal
+    prop_name: "ro.carwatchdog.boottime_collection_interval"
+  }
+  prop {
+    api_name: "periodicCollectionBufferSize"
+    type: Integer
+    scope: Internal
+    prop_name: "ro.carwatchdog.periodic_collection_buffer_size"
+  }
+  prop {
+    api_name: "periodicCollectionInterval"
+    type: Integer
+    scope: Internal
+    prop_name: "ro.carwatchdog.periodic_collection_interval"
+  }
+  prop {
+    api_name: "topNStatsPerCategory"
+    type: Integer
+    scope: Internal
+    prop_name: "ro.carwatchdog.top_n_stats_per_category"
+  }
+  prop {
+    api_name: "topNStatsPerSubcategory"
+    type: Integer
+    scope: Internal
+    prop_name: "ro.carwatchdog.top_n_stats_per_subcategory"
+  }
+}
diff --git a/watchdog/server/sysprop/api/libwatchdog_properties-latest.txt b/watchdog/server/sysprop/api/libwatchdog_properties-latest.txt
new file mode 100644
index 0000000..ab59538
--- /dev/null
+++ b/watchdog/server/sysprop/api/libwatchdog_properties-latest.txt
@@ -0,0 +1,33 @@
+props {
+  module: "android.automotive.watchdog.sysprop"
+  prop {
+    api_name: "boottimeCollectionInterval"
+    type: Integer
+    scope: Internal
+    prop_name: "ro.carwatchdog.boottime_collection_interval"
+  }
+  prop {
+    api_name: "periodicCollectionBufferSize"
+    type: Integer
+    scope: Internal
+    prop_name: "ro.carwatchdog.periodic_collection_buffer_size"
+  }
+  prop {
+    api_name: "periodicCollectionInterval"
+    type: Integer
+    scope: Internal
+    prop_name: "ro.carwatchdog.periodic_collection_interval"
+  }
+  prop {
+    api_name: "topNStatsPerCategory"
+    type: Integer
+    scope: Internal
+    prop_name: "ro.carwatchdog.top_n_stats_per_category"
+  }
+  prop {
+    api_name: "topNStatsPerSubcategory"
+    type: Integer
+    scope: Internal
+    prop_name: "ro.carwatchdog.top_n_stats_per_subcategory"
+  }
+}
diff --git a/watchdog/server/tests/IoPerfCollectionTest.cpp b/watchdog/server/tests/IoPerfCollectionTest.cpp
index 580de6e..47433cd 100644
--- a/watchdog/server/tests/IoPerfCollectionTest.cpp
+++ b/watchdog/server/tests/IoPerfCollectionTest.cpp
@@ -16,6 +16,7 @@
 
 #include "IoPerfCollection.h"
 
+#include <WatchdogProperties.sysprop.h>
 #include <android-base/file.h>
 #include <cutils/android_filesystem_config.h>
 
@@ -172,6 +173,25 @@
     ASSERT_TRUE(collector->mCollectionThread.joinable()) << "Collection thread not created";
     ASSERT_FALSE(collector->start())
             << "No error returned when collector was started more than once";
+    ASSERT_TRUE(sysprop::topNStatsPerCategory().has_value());
+    ASSERT_EQ(collector->mTopNStatsPerCategory, sysprop::topNStatsPerCategory().value());
+
+    ASSERT_TRUE(sysprop::boottimeCollectionInterval().has_value());
+    ASSERT_EQ(std::chrono::duration_cast<std::chrono::seconds>(
+                      collector->mBoottimeCollection.interval)
+                      .count(),
+              sysprop::boottimeCollectionInterval().value());
+
+    ASSERT_TRUE(sysprop::topNStatsPerCategory().has_value());
+    ASSERT_EQ(std::chrono::duration_cast<std::chrono::seconds>(
+                      collector->mPeriodicCollection.interval)
+                      .count(),
+              sysprop::periodicCollectionInterval().value());
+
+    ASSERT_TRUE(sysprop::periodicCollectionBufferSize().has_value());
+    ASSERT_EQ(collector->mPeriodicCollection.maxCacheSize,
+              sysprop::periodicCollectionBufferSize().value());
+
     collector->terminate();
     ASSERT_FALSE(collector->mCollectionThread.joinable()) << "Collection thread did not terminate";
 }
@@ -1198,6 +1218,7 @@
     ASSERT_TRUE(ret) << "Failed to populate proc pid dir: " << ret.error();
 
     IoPerfCollection collector;
+    collector.mTopNStatsPerCategory = 5;
     collector.mProcPidStat = new ProcPidStat(prodDir.path);
     struct ProcessIoPerfData actualProcessIoPerfData = {};
     ret = collector.collectProcessIoPerfDataLocked(&actualProcessIoPerfData);