[automerged blank] Import translations. DO NOT MERGE ANYWHERE 2p: 9870b62ce2
Blank merge reason: commit message contains skip directive
Original change: https://googleplex-android-review.googlesource.com/c/platform/packages/services/Car/+/15958632
Change-Id: I2d94530457be693382de5a99f584b5cba8decce1
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 1b105cc..926a882 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -12,6 +12,9 @@
"include-filter": "android.car.apitest.PreInstalledPackagesTest"
}
]
+ },
+ {
+ "name": "SampleCustomInputServiceTest"
}
]
}
\ No newline at end of file
diff --git a/car-lib/src/android/car/content/pm/CarPackageManager.java b/car-lib/src/android/car/content/pm/CarPackageManager.java
index e66d851..27eff37 100644
--- a/car-lib/src/android/car/content/pm/CarPackageManager.java
+++ b/car-lib/src/android/car/content/pm/CarPackageManager.java
@@ -16,20 +16,28 @@
package android.car.content.pm;
+import static android.car.Car.PERMISSION_CONTROL_APP_BLOCKING;
+
import android.annotation.IntDef;
import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.annotation.TestApi;
+import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.car.Car;
import android.car.CarManagerBase;
import android.content.ComponentName;
+import android.content.pm.PackageManager.NameNotFoundException;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
+import android.os.ServiceSpecificException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.List;
/**
* Provides car specific API related with package management.
@@ -77,6 +85,36 @@
@Deprecated
public static final int FLAG_SET_POLICY_REMOVE = 0x4;
+ /**
+ * Represents support of all regions for driving safety.
+ *
+ * @hide
+ */
+ public static final String DRIVING_SAFETY_REGION_ALL = "android.car.drivingsafetyregion.all";
+
+ /**
+ * Metadata which Activity can use to specify the driving safety regions it is supporting.
+ *
+ * <p>Definition of driving safety region is car OEM specific for now and only OEM apps
+ * should use this. If there are multiple regions, it should be comma separated. Not specifying
+ * this means supporting all regions.
+ *
+ * <p>Some examples are:
+ * <meta-data android:name="android.car.drivingsafetyregions"
+ * android:value="com.android.drivingsafetyregion.1,com.android.drivingsafetyregion.2"/>
+ *
+ * @hide
+ */
+ public static final String DRIVING_SAFETY_ACTIVITY_METADATA_REGIONS =
+ "android.car.drivingsafetyregions";
+
+ /**
+ * Internal error code for throwing {@code NameNotFoundException} from service.
+ *
+ * @hide
+ */
+ public static final int ERROR_CODE_NO_PACKAGE = -100;
+
/** @hide */
@IntDef(flag = true,
value = {FLAG_SET_POLICY_WAIT_FOR_CHANGE, FLAG_SET_POLICY_ADD, FLAG_SET_POLICY_REMOVE})
@@ -242,4 +280,108 @@
return handleRemoteExceptionFromCarService(e, false);
}
}
+
+ /**
+ * Returns the current driving safety region of the system. It will return OEM specific regions
+ * or {@link #DRIVING_SAFETY_REGION_ALL} when all regions are supported.
+ *
+ * <p> System's driving safety region is static and does not change until system restarts.
+ *
+ * @hide
+ */
+ @RequiresPermission(anyOf = {PERMISSION_CONTROL_APP_BLOCKING,
+ Car.PERMISSION_CAR_DRIVING_STATE})
+ @NonNull
+ public String getCurrentDrivingSafetyRegion() {
+ try {
+ return mService.getCurrentDrivingSafetyRegion();
+ } catch (RemoteException e) {
+ return handleRemoteExceptionFromCarService(e, DRIVING_SAFETY_REGION_ALL);
+ }
+ }
+
+ /**
+ * Enables or disables bypassing of unsafe {@code Activity} blocking for a specific
+ * {@code Activity} temporarily.
+ *
+ * <p> Enabling bypassing only lasts until the user stops using the car or until a user
+ * switching happens. Apps like launcher may ask user's consent to bypass. Note that bypassing
+ * is done for the package for all android users including the current user and user 0.
+ * <p> If bypassing is disabled and if the unsafe app is in foreground with driving state, the
+ * app will be immediately blocked.
+ *
+ * @param packageName Target package name.
+ * @param activityClassName Target Activity name (in full class name).
+ * @param bypass Bypass {@code Activity} blocking when true. Do not bypass anymore when false.
+ * @param userId User Id where the package is installed. Even if the bypassing is enabled for
+ * all android users, the package should be available for the specified user id.
+ *
+ * @throws NameNotFoundException If the given package / Activity class does not exist for the
+ * user.
+ *
+ * @hide
+ */
+ @RequiresPermission(allOf = {PERMISSION_CONTROL_APP_BLOCKING,
+ android.Manifest.permission.QUERY_ALL_PACKAGES})
+ public void controlTemporaryActivityBlockingBypassingAsUser(String packageName,
+ String activityClassName, boolean bypass, @UserIdInt int userId)
+ throws NameNotFoundException {
+ try {
+ mService.controlOneTimeActivityBlockingBypassingAsUser(packageName, activityClassName,
+ bypass, userId);
+ } catch (ServiceSpecificException e) {
+ handleServiceSpecificFromCarService(e, packageName, activityClassName, userId);
+ } catch (RemoteException e) {
+ handleRemoteExceptionFromCarService(e);
+ }
+ }
+
+ /**
+ * Returns all supported driving safety regions for the given Activity. If the Activity supports
+ * all regions, it will only include {@link #DRIVING_SAFETY_REGION_ALL}.
+ *
+ * <p> The permission specification requires {@code PERMISSION_CONTROL_APP_BLOCKING} and
+ * {@code QUERY_ALL_PACKAGES} but this API will also work if the client has
+ * {@link Car#PERMISSION_CAR_DRIVING_STATE} and {@code QUERY_ALL_PACKAGES} permissions.
+ *
+ * @param packageName Target package name.
+ * @param activityClassName Target Activity name (in full class name).
+ * @param userId Android user Id to check the package.
+ *
+ * @return Empty list if the Activity does not support driving safety (=no
+ * {@code distractionOptimized} metadata). Otherwise returns full list of all supported
+ * regions.
+ *
+ * @throws NameNotFoundException If the given package / Activity class does not exist for the
+ * user.
+ *
+ * @hide
+ */
+ @RequiresPermission(allOf = {PERMISSION_CONTROL_APP_BLOCKING,
+ android.Manifest.permission.QUERY_ALL_PACKAGES})
+ @NonNull
+ public List<String> getSupportedDrivingSafetyRegionsForActivityAsUser(String packageName,
+ String activityClassName, @UserIdInt int userId) throws NameNotFoundException {
+ try {
+ return mService.getSupportedDrivingSafetyRegionsForActivityAsUser(packageName,
+ activityClassName, userId);
+ } catch (ServiceSpecificException e) {
+ handleServiceSpecificFromCarService(e, packageName, activityClassName, userId);
+ } catch (RemoteException e) {
+ return handleRemoteExceptionFromCarService(e, Collections.EMPTY_LIST);
+ }
+ return Collections.EMPTY_LIST; // cannot reach here but the compiler complains.
+ }
+
+ private void handleServiceSpecificFromCarService(ServiceSpecificException e,
+ String packageName, String activityClassName, @UserIdInt int userId)
+ throws NameNotFoundException {
+ if (e.errorCode == ERROR_CODE_NO_PACKAGE) {
+ throw new NameNotFoundException(
+ "cannot find " + packageName + "/" + activityClassName + " for user id:"
+ + userId);
+ }
+ // don't know what this is
+ throw new IllegalStateException(e);
+ }
}
diff --git a/car-lib/src/android/car/content/pm/ICarPackageManager.aidl b/car-lib/src/android/car/content/pm/ICarPackageManager.aidl
index b8261f2..b0bdc7c 100644
--- a/car-lib/src/android/car/content/pm/ICarPackageManager.aidl
+++ b/car-lib/src/android/car/content/pm/ICarPackageManager.aidl
@@ -29,4 +29,9 @@
void setEnableActivityBlocking(boolean enable) = 4;
void restartTask(int taskId) = 5;
boolean isPendingIntentDistractionOptimized(in PendingIntent pendingIntent) = 6;
+ String getCurrentDrivingSafetyRegion() = 7;
+ void controlOneTimeActivityBlockingBypassingAsUser(String packageName, String activityClassName,
+ boolean bypass, int userId) = 8;
+ List<String> getSupportedDrivingSafetyRegionsForActivityAsUser(String packageName,
+ String activityClassName, int userId) = 9;
}
diff --git a/car-maps-placeholder/res/values-gu/strings.xml b/car-maps-placeholder/res/values-gu/strings.xml
index 845f341..45e1af7 100644
--- a/car-maps-placeholder/res/values-gu/strings.xml
+++ b/car-maps-placeholder/res/values-gu/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="6575346965016311017">"Maps"</string>
- <string name="error_text" msgid="5575174711944349180">"કોઈ Maps ઍપ્લિકેશન ઇન્સ્ટૉલ કરેલી નથી. કૃપા કરીને તમારી કારના નિર્માતાનો સંપર્ક કરો."</string>
+ <string name="error_text" msgid="5575174711944349180">"કોઈ નકશા એપ્લિકેશન ઇન્સ્ટૉલ કરેલ નથી. કૃપા કરીને તમારી કારના નિર્માતાનો સંપર્ક કરો."</string>
</resources>
diff --git a/cpp/libsysfsmonitor/Android.bp b/cpp/libsysfsmonitor/Android.bp
new file mode 100644
index 0000000..545255b
--- /dev/null
+++ b/cpp/libsysfsmonitor/Android.bp
@@ -0,0 +1,60 @@
+// Copyright (C) 2021 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 {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_defaults {
+ name: "libsysfsmonitor_defaults",
+ cflags: [
+ "-Wall",
+ "-Wno-missing-field-initializers",
+ "-Werror",
+ "-Wno-unused-variable",
+ "-Wunused-parameter",
+ ],
+ shared_libs: [
+ "libbase",
+ "liblog",
+ "libutils",
+ ],
+}
+
+cc_library {
+ name: "libsysfsmonitor",
+ srcs: [
+ "src/SysfsMonitor.cpp",
+ ],
+ defaults: [
+ "libsysfsmonitor_defaults",
+ ],
+ export_include_dirs: [
+ "src",
+ ],
+}
+
+cc_test {
+ name: "libsysfsmonitor_test",
+ srcs: [
+ "tests/SysfsMonitorTest.cpp",
+ ],
+ defaults: [
+ "libsysfsmonitor_defaults",
+ ],
+ static_libs: [
+ "libgtest",
+ "libsysfsmonitor",
+ ],
+}
diff --git a/cpp/libsysfsmonitor/src/SysfsMonitor.cpp b/cpp/libsysfsmonitor/src/SysfsMonitor.cpp
new file mode 100644
index 0000000..ee89ec0
--- /dev/null
+++ b/cpp/libsysfsmonitor/src/SysfsMonitor.cpp
@@ -0,0 +1,135 @@
+/**
+ * Copyright (c) 2021, 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.
+ */
+
+#define LOG_TAG "libsysfsmonitor"
+#define DEBUG false
+
+#include "SysfsMonitor.h"
+
+#include <android-base/stringprintf.h>
+#include <log/log.h>
+
+#include <sys/epoll.h>
+
+namespace {
+
+using ::android::base::Error;
+using ::android::base::Result;
+using ::android::base::StringPrintf;
+
+// The maximum number of sysfs files to monitor.
+constexpr int32_t EPOLL_MAX_EVENTS = 10;
+
+} // namespace
+
+namespace android {
+namespace automotive {
+
+Result<void> SysfsMonitor::init(CallbackFunc callback) {
+ if (mEpollFd >= 0) {
+ return Error() << "Epoll instance was already created";
+ }
+ if (mEpollFd.reset(epoll_create1(EPOLL_CLOEXEC)); mEpollFd < 0) {
+ return Error() << "Cannot create epoll instance: errno = " << errno;
+ }
+ mCallback = callback;
+ return {};
+}
+
+Result<void> SysfsMonitor::release() {
+ if (mEpollFd < 0) {
+ return Error() << "Epoll instance wasn't created";
+ }
+ for (const int32_t fd : mMonitoringFds) {
+ if (epoll_ctl(mEpollFd, EPOLL_CTL_DEL, fd, /*event=*/nullptr)) {
+ ALOGW("Failed to deregister fd(%d) from epoll instance: errno = %d", fd, errno);
+ }
+ }
+ mMonitoringFds.clear();
+ mEpollFd.reset();
+ mCallback = nullptr;
+ return {};
+}
+
+Result<void> SysfsMonitor::registerFd(int32_t fd) {
+ if (fd < 0) {
+ return Error() << StringPrintf("fd(%d) is invalid", fd);
+ }
+ if (mMonitoringFds.count(fd) > 0) {
+ return Error() << StringPrintf("fd(%d) is already being monitored", fd);
+ }
+ if (mMonitoringFds.size() == EPOLL_MAX_EVENTS) {
+ return Error() << "Cannot monitor more than " << EPOLL_MAX_EVENTS << " sysfs files";
+ }
+ struct epoll_event eventItem = {};
+ eventItem.events = EPOLLIN | EPOLLPRI | EPOLLET;
+ eventItem.data.fd = fd;
+ if (int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, &eventItem); result != 0) {
+ return Error() << StringPrintf("Failed to add fd(%d) to epoll instance: errno = %d", fd,
+ errno);
+ }
+ mMonitoringFds.insert(fd);
+ return {};
+}
+
+Result<void> SysfsMonitor::unregisterFd(int32_t fd) {
+ if (fd < 0) {
+ return Error() << StringPrintf("fd(%d) is invalid", fd);
+ }
+ if (mMonitoringFds.count(fd) == 0) {
+ return Error() << StringPrintf("fd(%d) is not being monitored", fd);
+ }
+ // Even when epoll_ctl() fails, we proceed to handle the request.
+ if (epoll_ctl(mEpollFd, EPOLL_CTL_DEL, fd, /*event=*/nullptr)) {
+ ALOGW("Failed to deregister fd(%d) from epoll instance: errno = %d", fd, errno);
+ }
+ mMonitoringFds.erase(fd);
+ return {};
+}
+
+Result<void> SysfsMonitor::observe() {
+ if (mEpollFd < 0) {
+ return Error() << "Epoll instance is not initialized";
+ }
+
+ struct epoll_event events[EPOLL_MAX_EVENTS];
+ while (true) {
+ int pollResult = epoll_wait(mEpollFd, events, EPOLL_MAX_EVENTS, /*timeout=*/-1);
+ if (pollResult < 0) {
+ ALOGW("Polling sysfs failed, but continue polling: errno = %d", errno);
+ continue;
+ }
+ std::vector<int32_t> fds;
+ for (int i = 0; i < pollResult; i++) {
+ int fd = events[i].data.fd;
+ if (mMonitoringFds.count(fd) == 0) {
+ continue;
+ }
+ if (events[i].events & EPOLLIN) {
+ fds.push_back(fd);
+ } else if (events[i].events & EPOLLERR) {
+ ALOGW("An error occurred when polling fd(%d)", fd);
+ }
+ }
+ if (mCallback && fds.size() > 0) {
+ mCallback(fds);
+ }
+ }
+ return {};
+}
+
+} // namespace automotive
+} // namespace android
diff --git a/cpp/libsysfsmonitor/src/SysfsMonitor.h b/cpp/libsysfsmonitor/src/SysfsMonitor.h
new file mode 100644
index 0000000..f941041
--- /dev/null
+++ b/cpp/libsysfsmonitor/src/SysfsMonitor.h
@@ -0,0 +1,59 @@
+/**
+ * Copyright (c) 2021, 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.
+ */
+
+#ifndef CPP_LIBSYSFSMONITOR_SRC_SYSFSMONITOR_H_
+#define CPP_LIBSYSFSMONITOR_SRC_SYSFSMONITOR_H_
+
+#include <android-base/result.h>
+#include <android-base/unique_fd.h>
+#include <utils/RefBase.h>
+
+#include <functional>
+#include <string>
+#include <unordered_set>
+
+namespace android {
+namespace automotive {
+
+using CallbackFunc = ::std::function<void(const std::vector<int32_t>&)>;
+
+/**
+ * SysfsMonitor monitors sysfs file changes and invokes the registered callback when there is a
+ * change at the sysfs files to monitor.
+ */
+class SysfsMonitor final : public RefBase {
+public:
+ // Initializes SysfsMonitor instance.
+ android::base::Result<void> init(CallbackFunc callback);
+ // Releases resources used for monitoring.
+ android::base::Result<void> release();
+ // Registers a sysfs file to monitor.
+ android::base::Result<void> registerFd(int32_t fd);
+ // Unregisters a sysfs file to monitor.
+ android::base::Result<void> unregisterFd(int32_t fd);
+ // Starts observing sysfs file changes.
+ android::base::Result<void> observe();
+
+private:
+ android::base::unique_fd mEpollFd;
+ std::unordered_set<int32_t> mMonitoringFds;
+ CallbackFunc mCallback;
+};
+
+} // namespace automotive
+} // namespace android
+
+#endif // CPP_LIBSYSFSMONITOR_SRC_SYSFSMONITOR_H_
diff --git a/cpp/libsysfsmonitor/tests/SysfsMonitorTest.cpp b/cpp/libsysfsmonitor/tests/SysfsMonitorTest.cpp
new file mode 100644
index 0000000..f6dda6c
--- /dev/null
+++ b/cpp/libsysfsmonitor/tests/SysfsMonitorTest.cpp
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2021, 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.
+ */
+
+#include "SysfsMonitor.h"
+
+#include <gtest/gtest.h>
+#include <utils/StrongPointer.h>
+
+#include <sys/socket.h>
+
+#include <vector>
+
+namespace android {
+namespace automotive {
+
+class SysfsMonitorTest : public ::testing::Test {
+public:
+ void SetUp() override { mSysfsMonitor = sp<SysfsMonitor>::make(); }
+ void TearDown() override { mSysfsMonitor = nullptr; }
+
+protected:
+ sp<SysfsMonitor> mSysfsMonitor;
+};
+
+TEST_F(SysfsMonitorTest, TestDuplicateInitialize) {
+ auto ret = mSysfsMonitor->init([](const std::vector<int32_t>&) {});
+ ASSERT_TRUE(ret.ok()) << "First initialization should be successful: " << ret.error().message();
+ ASSERT_FALSE(mSysfsMonitor->init([](const std::vector<int32_t>&) {}).ok())
+ << "Initialization cannot be done twice";
+}
+
+TEST_F(SysfsMonitorTest, TestDuplicateRelease) {
+ auto ret = mSysfsMonitor->init([](const std::vector<int32_t>&) {});
+ ASSERT_TRUE(ret.ok()) << "First initialization should be successful: " << ret.error().message();
+ ret = mSysfsMonitor->release();
+ ASSERT_TRUE(ret.ok()) << "Releasing the initialized instance should be successful: "
+ << ret.error().message();
+ ASSERT_FALSE(mSysfsMonitor->release().ok()) << "Released instance cannot be released";
+}
+
+TEST_F(SysfsMonitorTest, TestNoInitRelease) {
+ ASSERT_FALSE(mSysfsMonitor->release().ok()) << "Uninitailized instance cannot be released";
+}
+
+TEST_F(SysfsMonitorTest, TestUnregisterNotRegisteredFd) {
+ // A regular file cannot be registered to epoll instance. So, the test is using a socket file.
+ int32_t fd = socket(AF_UNIX, SOCK_DGRAM, /*protocol=*/0);
+ mSysfsMonitor->init([](const std::vector<int32_t>&) {});
+
+ ASSERT_FALSE(mSysfsMonitor->unregisterFd(fd).ok())
+ << "Unregistered file description cannot be unregistered";
+ close(fd);
+}
+
+TEST_F(SysfsMonitorTest, TestDuplicateRegister) {
+ int32_t fd = socket(AF_UNIX, SOCK_DGRAM, /*protocol=*/0);
+ mSysfsMonitor->init([](const std::vector<int32_t>&) {});
+
+ auto ret = mSysfsMonitor->registerFd(fd);
+ ASSERT_TRUE(ret.ok()) << "Registering a file descriptor first time should be successful: "
+ << ret.error().message();
+ ASSERT_FALSE(mSysfsMonitor->registerFd(fd).ok())
+ << "Registering a file descriptor twice cannot be done";
+ close(fd);
+}
+
+TEST_F(SysfsMonitorTest, TestDuplicateUnregister) {
+ int32_t fd = socket(AF_UNIX, SOCK_DGRAM, /*protocol=*/0);
+ mSysfsMonitor->init([](const std::vector<int32_t>&) {});
+
+ auto ret = mSysfsMonitor->registerFd(fd);
+ ASSERT_TRUE(ret.ok()) << "Registering a file descriptor first time should be successful: "
+ << ret.error().message();
+ ret = mSysfsMonitor->unregisterFd(fd);
+ ASSERT_TRUE(ret.ok()) << "The registered file descriptor should be unregistered: "
+ << ret.error().message();
+ ASSERT_FALSE(mSysfsMonitor->unregisterFd(fd).ok())
+ << "Unregistering the unregistered file descriptor cannot be done";
+ close(fd);
+}
+
+TEST_F(SysfsMonitorTest, TestRegisterInvalidFd) {
+ const int32_t invalidFd = -1;
+ ASSERT_FALSE(mSysfsMonitor->registerFd(invalidFd).ok())
+ << "fd(-1) cannot be registered to SysfsMonitor";
+}
+
+TEST_F(SysfsMonitorTest, TestUnregisterInvalidFd) {
+ const int32_t invalidFd = -1;
+ ASSERT_FALSE(mSysfsMonitor->unregisterFd(invalidFd).ok())
+ << "fd(-1) cannot be given to unregisterFd";
+}
+
+TEST_F(SysfsMonitorTest, TestRegisterMultipleFds) {
+ const int32_t maxFdCount = 10;
+ int32_t fdsToMonitor[maxFdCount + 1];
+
+ mSysfsMonitor->init([](const std::vector<int32_t>&) {});
+ for (int i = 0; i < maxFdCount; i++) {
+ fdsToMonitor[i] = socket(AF_UNIX, SOCK_DGRAM, /*protocol=*/0);
+ auto ret = mSysfsMonitor->registerFd(fdsToMonitor[i]);
+ ASSERT_TRUE(ret.ok()) << "Registering a file descriptor first time should be successful: "
+ << ret.error().message();
+ }
+ fdsToMonitor[maxFdCount] = socket(AF_UNIX, SOCK_DGRAM, /*protocol=*/0);
+ ASSERT_FALSE(mSysfsMonitor->registerFd(fdsToMonitor[maxFdCount]).ok())
+ << "Registering more than " << maxFdCount << " files cannot be done";
+
+ mSysfsMonitor->release();
+ for (int i = 0; i <= maxFdCount; i++) {
+ close(fdsToMonitor[i]);
+ }
+}
+
+TEST_F(SysfsMonitorTest, TestObserveWithoutInitialization) {
+ ASSERT_FALSE(mSysfsMonitor->observe().ok()) << "Uninitialized instance cannot observe";
+}
+
+} // namespace automotive
+} // namespace android
diff --git a/cpp/powerpolicy/server/Android.bp b/cpp/powerpolicy/server/Android.bp
index 094a3f5..2dd40bd 100644
--- a/cpp/powerpolicy/server/Android.bp
+++ b/cpp/powerpolicy/server/Android.bp
@@ -36,6 +36,9 @@
"libtinyxml2",
"libutils",
],
+ static_libs: [
+ "libsysfsmonitor",
+ ],
}
cc_library {
diff --git a/cpp/powerpolicy/server/src/SilentModeHandler.cpp b/cpp/powerpolicy/server/src/SilentModeHandler.cpp
index c57b6fa..79d78be 100644
--- a/cpp/powerpolicy/server/src/SilentModeHandler.cpp
+++ b/cpp/powerpolicy/server/src/SilentModeHandler.cpp
@@ -25,7 +25,7 @@
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
-#include <sys/inotify.h>
+#include <sys/epoll.h>
#include <sys/stat.h>
#include <unistd.h>
@@ -35,6 +35,7 @@
namespace powerpolicy {
using ::android::Mutex;
+using ::android::automotive::SysfsMonitor;
using ::android::base::Error;
using ::android::base::GetProperty;
using ::android::base::ReadFileToString;
@@ -49,12 +50,11 @@
constexpr const char kPropertySystemBootReason[] = "sys.boot.reason";
constexpr const char kSilentModeHwStateFilename[] = "/sys/power/pm_silentmode_hw_state";
-constexpr const char kKernelSilentModeFilename[] = "/sys/power/pm_silentmode_kernel";
+constexpr const char kKernelSilentModeFilename[] = "/sys/power/pm_silentmode_kernel_state";
// To prevent boot animation from being started.
constexpr const char kPropertyNoBootAnimation[] = "debug.sf.nobootanimation";
// To stop boot animation while it is being played.
constexpr const char kPropertyBootAnimationExit[] = "service.bootanim.exit";
-constexpr int kEventBufferSize = 512;
bool fileExists(const char* filename) {
struct stat buffer;
@@ -68,7 +68,7 @@
mSilentModeHwStateFilename(kSilentModeHwStateFilename),
mKernelSilentModeFilename(kKernelSilentModeFilename),
mSilentModeChangeHandler(handler),
- mFdInotify(-1) {
+ mSysfsMonitor(sp<SysfsMonitor>::make()) {
mBootReason = GetProperty(kPropertySystemBootReason, "");
}
@@ -83,7 +83,7 @@
if (mForcedMode) {
handleSilentModeChange(mSilentModeByHwState);
mSilentModeChangeHandler->notifySilentModeChange(mSilentModeByHwState);
- ALOGI("Now in forced mode: monitoring %s is disabled", kSilentModeHwStateFilename);
+ ALOGI("Now in forced mode: monitoring %s is disabled", mSilentModeHwStateFilename.c_str());
} else {
startMonitoringSilentModeHwState();
}
@@ -101,13 +101,15 @@
void SilentModeHandler::stopMonitoringSilentModeHwState(bool shouldWaitThread) {
if (mIsMonitoring) {
mIsMonitoring = false;
- inotify_rm_watch(mFdInotify, mWdSilentModeHwState);
- mWdSilentModeHwState = -1;
+ if (auto ret = mSysfsMonitor->unregisterFd(mFdSilentModeHwState.get()); !ret.ok()) {
+ ALOGW("Unregistering %s from SysfsMonitor failed", mSilentModeHwStateFilename.c_str());
+ }
if (shouldWaitThread && mSilentModeMonitoringThread.joinable()) {
mSilentModeMonitoringThread.join();
}
}
- mFdInotify.reset(-1);
+ mFdSilentModeHwState.reset();
+ mSysfsMonitor->release();
}
Result<void> SilentModeHandler::dump(int fd, const Vector<String16>& /*args*/) {
@@ -132,56 +134,38 @@
ALOGW("Silent Mode monitoring is already started");
return;
}
- if (mFdInotify < 0) {
- mFdInotify.reset(inotify_init1(IN_CLOEXEC));
- if (mFdInotify < 0) {
- ALOGE("Failed to start monitoring Silent Mode HW state: creating inotify instance "
- "failed (errno = %d)",
- errno);
- return;
- }
- }
const char* filename = mSilentModeHwStateFilename.c_str();
if (!fileExists(filename)) {
ALOGW("Failed to start monitoring Silent Mode HW state: %s doesn't exist", filename);
- mFdInotify.reset(-1);
return;
}
- // TODO(b/178843534): Additional masks might be needed to detect sysfs change.
- const uint32_t masks = IN_MODIFY;
- mWdSilentModeHwState = inotify_add_watch(mFdInotify, filename, masks);
+ if (mFdSilentModeHwState == -1) {
+ if (mFdSilentModeHwState.reset(open(filename, O_RDONLY | O_NONBLOCK | O_CLOEXEC));
+ mFdSilentModeHwState == -1) {
+ ALOGW("Failed to open %s for monitoring: errno = %d", filename, errno);
+ return;
+ }
+ }
+ auto ret = mSysfsMonitor->init([this](const std::vector<int32_t>& fileDescriptors) {
+ // Only one sysfs file is registered.
+ if (mFdSilentModeHwState != fileDescriptors[0]) {
+ return;
+ }
+ handleSilentModeHwStateChange();
+ });
+ if (!ret.ok()) {
+ ALOGW("Failed to initialize SysfsMonitor: %s", ret.error().message().c_str());
+ return;
+ }
+ if (auto ret = mSysfsMonitor->registerFd(mFdSilentModeHwState.get()); !ret.ok()) {
+ ALOGW("Failed to register %s to SysfsMonitor: %s", filename, ret.error().message().c_str());
+ return;
+ }
mIsMonitoring = true;
- mSilentModeMonitoringThread = std::thread([this]() {
- char eventBuf[kEventBufferSize];
- struct inotify_event* event;
- constexpr size_t inotifyEventSize = sizeof(*event);
- ALOGI("Monitoring %s started", mSilentModeHwStateFilename.c_str());
- while (mIsMonitoring) {
- int eventPos = 0;
- int numBytes = read(mFdInotify, eventBuf, sizeof(eventBuf));
- if (numBytes < static_cast<int>(inotifyEventSize)) {
- if (errno == EINTR) {
- ALOGW("System call interrupted. Wait for inotify event again.");
- continue;
- }
- mIsMonitoring = false;
- inotify_rm_watch(mFdInotify, mWdSilentModeHwState);
- mWdSilentModeHwState = -1;
- mFdInotify.reset(-1);
- ALOGW("Failed to wait for change at %s (errno = %d)",
- mSilentModeHwStateFilename.c_str(), errno);
- return;
- }
- while (numBytes >= static_cast<int>(inotifyEventSize)) {
- int eventSize;
- event = (struct inotify_event*)(eventBuf + eventPos);
- if (event->wd == mWdSilentModeHwState && (event->mask & masks)) {
- handleSilentModeHwStateChange();
- }
- eventSize = inotifyEventSize + event->len;
- numBytes -= eventSize;
- eventPos += eventSize;
- }
+ mSilentModeMonitoringThread = std::thread([this, filename]() {
+ if (auto ret = mSysfsMonitor->observe(); !ret.ok()) {
+ ALOGI("Failed to observe %s", filename);
+ return;
}
ALOGI("Monitoring %s ended", mSilentModeHwStateFilename.c_str());
});
diff --git a/cpp/powerpolicy/server/src/SilentModeHandler.h b/cpp/powerpolicy/server/src/SilentModeHandler.h
index 9fae109..96074f8 100644
--- a/cpp/powerpolicy/server/src/SilentModeHandler.h
+++ b/cpp/powerpolicy/server/src/SilentModeHandler.h
@@ -21,8 +21,11 @@
#include <android-base/unique_fd.h>
#include <utils/Mutex.h>
#include <utils/String16.h>
+#include <utils/StrongPointer.h>
#include <utils/Vector.h>
+#include <SysfsMonitor.h>
+
#include <atomic>
#include <thread> // NOLINT(build/c++11)
@@ -47,8 +50,8 @@
/**
* SilentModeHandler monitors {@code /sys/power/pm_silentmode_hw_state} in sysfs to detect Silent
- * Mode change by a vehicle processor. Also, it updates {@code /sys/power/pm_silentmode_kernel} in
- * sysfs to tell kernel the current Silent Mode.
+ * Mode change by a vehicle processor. Also, it updates
+ * {@code /sys/power/pm_silentmode_kernel_state} in sysfs to tell kernel the current Silent Mode.
*/
class SilentModeHandler final {
public:
@@ -80,9 +83,9 @@
std::string mKernelSilentModeFilename;
ISilentModeChangeHandler* mSilentModeChangeHandler;
std::thread mSilentModeMonitoringThread;
- android::base::unique_fd mFdInotify;
- int mWdSilentModeHwState = -1;
std::atomic_bool mIsMonitoring = false;
+ android::sp<android::automotive::SysfsMonitor> mSysfsMonitor;
+ android::base::unique_fd mFdSilentModeHwState;
// For unit tests.
friend class internal::SilentModeHandlerPeer;
diff --git a/cpp/powerpolicy/server/tests/SilentModeHandlerTest.cpp b/cpp/powerpolicy/server/tests/SilentModeHandlerTest.cpp
index 1eaec81..6548cf1 100644
--- a/cpp/powerpolicy/server/tests/SilentModeHandlerTest.cpp
+++ b/cpp/powerpolicy/server/tests/SilentModeHandlerTest.cpp
@@ -46,20 +46,6 @@
constexpr int kMaxPollingAttempts = 5;
constexpr std::chrono::microseconds kPollingDelayUs = 50ms;
-bool waitForSilentMode(SilentModeHandler* handler, bool expectedSilentMode) {
- int count = 0;
- while (true) {
- if (handler->isSilentMode() == expectedSilentMode) {
- return true;
- }
- if (count++; count == kMaxPollingAttempts) {
- break;
- }
- usleep(kPollingDelayUs.count());
- }
- return false;
-}
-
} // namespace
namespace internal {
@@ -124,23 +110,6 @@
sp<MockCarPowerPolicyServer> carPowerPolicyServer;
};
-TEST_F(SilentModeHandlerTest, TestSilentModeHwStateMonitoring) {
- SilentModeHandler handler(carPowerPolicyServer.get());
- internal::SilentModeHandlerPeer handlerPeer(&handler);
- handlerPeer.injectBootReason(kBootReasonNormal);
- handlerPeer.init();
-
- handlerPeer.updateSilentModeHwState(/*isSilent=*/true);
-
- ASSERT_TRUE(waitForSilentMode(&handler, /*expectedSilentMode=*/true))
- << "It should be silent mode when HW state is on";
-
- handlerPeer.updateSilentModeHwState(/*isSilent=*/false);
-
- ASSERT_TRUE(waitForSilentMode(&handler, /*expectedSilentMode=*/false))
- << "It should be non-silent mode when HW state is off";
-}
-
TEST_F(SilentModeHandlerTest, TestRebootForForcedSilentMode) {
SilentModeHandler handler(carPowerPolicyServer.get());
internal::SilentModeHandlerPeer handlerPeer(&handler);
diff --git a/service/src/com/android/car/CarShellCommand.java b/service/src/com/android/car/CarShellCommand.java
index bad6b71..083eeb8 100644
--- a/service/src/com/android/car/CarShellCommand.java
+++ b/service/src/com/android/car/CarShellCommand.java
@@ -40,6 +40,7 @@
import android.car.Car;
import android.car.CarOccupantZoneManager;
import android.car.VehiclePropertyIds;
+import android.car.content.pm.CarPackageManager;
import android.car.input.CarInputManager;
import android.car.input.CustomInputEvent;
import android.car.input.RotaryEvent;
@@ -198,6 +199,9 @@
private static final String COMMAND_WATCHDOG_IO_GET_3P_FOREGROUND_BYTES =
"watchdog-io-get-3p-foreground-bytes";
+ private static final String COMMAND_DRIVING_SAFETY_SET_REGION =
+ "set-drivingsafety-region";
+
private static final String[] CREATE_OR_MANAGE_USERS_PERMISSIONS = new String[] {
android.Manifest.permission.CREATE_USERS,
android.Manifest.permission.MANAGE_USERS
@@ -612,6 +616,10 @@
pw.printf("\t%s\n", COMMAND_WATCHDOG_IO_GET_3P_FOREGROUND_BYTES);
pw.println("\t Gets third-party apps foreground I/O overuse threshold");
+
+ pw.printf("\t%s [REGION_STRING]", COMMAND_DRIVING_SAFETY_SET_REGION);
+ pw.println("\t Set driving safety region.");
+ pw.println("\t Skipping REGION_STRING leads into resetting to all regions");
}
private static int showInvalidArguments(IndentingPrintWriter pw) {
@@ -940,7 +948,9 @@
case COMMAND_WATCHDOG_IO_GET_3P_FOREGROUND_BYTES:
getWatchdogIoThirdPartyForegroundBytes(writer);
break;
-
+ case COMMAND_DRIVING_SAFETY_SET_REGION:
+ setDrivingSafetyRegion(args, writer);
+ break;
default:
writer.println("Unknown command: \"" + cmd + "\"");
showHelp(writer);
@@ -2101,6 +2111,17 @@
}
}
+ private void setDrivingSafetyRegion(String[] args, IndentingPrintWriter writer) {
+ if (args.length != 1 && args.length != 2) {
+ showInvalidArguments(writer);
+ return;
+ }
+ String region = args.length == 2 ? args[1] : CarPackageManager.DRIVING_SAFETY_REGION_ALL;
+ writer.println("Set driving safety region to:" + region);
+ CarLocalServices.getService(CarPackageManagerService.class).resetDrivingSafetyRegion(
+ region);
+ }
+
private void getRearviewCameraId(IndentingPrintWriter writer) {
writer.printf("CarEvsService is using %s for the rearview.\n",
mCarEvsService.getRearviewCameraIdFromCommand());
diff --git a/service/src/com/android/car/pm/CarAppMetadataReader.java b/service/src/com/android/car/pm/CarAppMetadataReader.java
index 2b67f25..0e7c136 100644
--- a/service/src/com/android/car/pm/CarAppMetadataReader.java
+++ b/service/src/com/android/car/pm/CarAppMetadataReader.java
@@ -15,19 +15,25 @@
*/
package com.android.car.pm;
+import static android.car.content.pm.CarPackageManager.DRIVING_SAFETY_ACTIVITY_METADATA_REGIONS;
+
+import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.car.content.pm.CarPackageManager;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
-import android.util.Log;
import android.util.Slog;
import com.android.car.CarLog;
import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
/**
@@ -49,19 +55,13 @@
/** Name of the meta-data attribute of the Activity that denotes distraction optimized */
private static final String DO_METADATA_ATTRIBUTE = "distractionOptimized";
- /**
- * Parses the given package and returns Distraction Optimized information, if present.
- *
- * @return Array of DO activity names in the given package
- */
- @Nullable
- public static String[] findDistractionOptimizedActivitiesAsUser(Context context,
- String packageName, int userId) throws NameNotFoundException {
- final PackageManager pm = context.getPackageManager();
+ private static final List<String> ALL_REGION_ONLY = Collections.singletonList(
+ CarPackageManager.DRIVING_SAFETY_REGION_ALL);
- // Check if any of the activities in the package are DO by checking all the
- // <activity> elements. MATCH_DISABLED_COMPONENTS is included so that we are immediately
- // prepared to respond to any components that toggle from disabled to enabled.
+ @Nullable
+ private static ActivityInfo[] getAllActivitiesForPackageAsUser(Context context,
+ String packageName, @UserIdInt int userId) throws NameNotFoundException {
+ final PackageManager pm = context.getPackageManager();
PackageInfo pkgInfo =
pm.getPackageInfoAsUser(
packageName, PackageManager.GET_ACTIVITIES
@@ -74,18 +74,43 @@
return null;
}
- ActivityInfo[] activities = pkgInfo.activities;
+ return pkgInfo.activities;
+ }
+
+ /**
+ * Parses the given package and returns Distraction Optimized information, if present.
+ *
+ * @return Array of DO activity names in the given package
+ */
+ @Nullable
+ public static String[] findDistractionOptimizedActivitiesAsUser(Context context,
+ String packageName, @UserIdInt int userId, @NonNull String drivingSafetyRegion)
+ throws NameNotFoundException {
+
+
+ // Check if any of the activities in the package are DO by checking all the
+ // <activity> elements. MATCH_DISABLED_COMPONENTS is included so that we are immediately
+ // prepared to respond to any components that toggle from disabled to enabled.
+ ActivityInfo[] activities = getAllActivitiesForPackageAsUser(context, packageName, userId);
if (activities == null) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (CarPackageManagerService.DBG) {
Slog.d(TAG, "Null Activities for " + packageName);
}
return null;
}
List<String> optimizedActivityList = new ArrayList();
for (ActivityInfo activity : activities) {
- Bundle mData = activity.metaData;
- if (mData != null && mData.getBoolean(DO_METADATA_ATTRIBUTE, false)) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Bundle metaData = activity.metaData;
+ if (metaData == null) {
+ continue;
+ }
+ String regionString = metaData.getString(DRIVING_SAFETY_ACTIVITY_METADATA_REGIONS,
+ CarPackageManager.DRIVING_SAFETY_REGION_ALL);
+ if (!isRegionSupported(regionString, drivingSafetyRegion)) {
+ continue;
+ }
+ if (metaData.getBoolean(DO_METADATA_ATTRIBUTE, false)) {
+ if (CarPackageManagerService.DBG) {
Slog.d(TAG,
"DO Activity:" + activity.packageName + "/" + activity.name);
}
@@ -97,4 +122,66 @@
}
return optimizedActivityList.toArray(new String[optimizedActivityList.size()]);
}
+
+ /**
+ * Check {@link CarPackageManager#getSupportedDrivingSafetyRegionsForActivityAsUser(
+ * String, String, int)}.
+ */
+ public static List<String> getSupportedDrivingSafetyRegionsForActivityAsUser(Context context,
+ String packageName, String activityClassName, @UserIdInt int userId)
+ throws NameNotFoundException {
+ ActivityInfo[] activities = getAllActivitiesForPackageAsUser(context, packageName, userId);
+ if (activities == null) {
+ throw new NameNotFoundException();
+ }
+ for (ActivityInfo info: activities) {
+ if (!info.name.equals(activityClassName)) {
+ continue;
+ }
+ // Found activity
+ Bundle metaData = info.metaData;
+ if (metaData == null) {
+ return Collections.EMPTY_LIST;
+ }
+ if (!metaData.getBoolean(DO_METADATA_ATTRIBUTE, false)) {
+ // no distractionOptimized, so region metadata does not matter.
+ return Collections.EMPTY_LIST;
+ }
+ String regionString = metaData.getString(DRIVING_SAFETY_ACTIVITY_METADATA_REGIONS,
+ CarPackageManager.DRIVING_SAFETY_REGION_ALL);
+ String[] regions = regionString.split(",");
+ for (String region: regions) {
+ if (CarPackageManager.DRIVING_SAFETY_REGION_ALL.equals(region)) {
+ return ALL_REGION_ONLY;
+ }
+ }
+ return Arrays.asList(regions);
+ }
+ throw new NameNotFoundException();
+ }
+
+ private static boolean isRegionSupported(String regionString, String currentRegion) {
+ if (regionString.isEmpty()) { // not specified means all regions.
+ return true;
+ }
+ if (currentRegion.equals(CarPackageManager.DRIVING_SAFETY_REGION_ALL)) {
+ return true;
+ }
+ String[] regions = regionString.split(",");
+ for (String region: regions) {
+ if (CarPackageManager.DRIVING_SAFETY_REGION_ALL.equals(region)) {
+ return true;
+ }
+ if (currentRegion.equals(region)) {
+ return true;
+ }
+ }
+ // valid regions but does not match currentRegion.
+ if (CarPackageManagerService.DBG) {
+ Slog.d(TAG,
+ "isRegionSupported not supported, regionString:" + regionString
+ + " region:" + currentRegion);
+ }
+ return false;
+ }
}
diff --git a/service/src/com/android/car/pm/CarPackageManagerService.java b/service/src/com/android/car/pm/CarPackageManagerService.java
index 8c8a105..4c262b5 100644
--- a/service/src/com/android/car/pm/CarPackageManagerService.java
+++ b/service/src/com/android/car/pm/CarPackageManagerService.java
@@ -32,6 +32,11 @@
import android.car.content.pm.ICarPackageManager;
import android.car.drivingstate.CarUxRestrictions;
import android.car.drivingstate.ICarUxRestrictionsChangeListener;
+import android.car.hardware.power.CarPowerPolicy;
+import android.car.hardware.power.CarPowerPolicyFilter;
+import android.car.hardware.power.ICarPowerPolicyListener;
+import android.car.hardware.power.PowerComponent;
+import android.car.user.CarUserManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -53,6 +58,8 @@
import android.os.Looper;
import android.os.Message;
import android.os.Process;
+import android.os.ServiceSpecificException;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
@@ -64,6 +71,7 @@
import android.view.Display;
import android.view.DisplayAddress;
+import com.android.car.CarLocalServices;
import com.android.car.CarLog;
import com.android.car.CarServiceBase;
import com.android.car.CarServiceUtils;
@@ -71,6 +79,8 @@
import com.android.car.R;
import com.android.car.SystemActivityMonitoringService;
import com.android.car.SystemActivityMonitoringService.TopTaskInfoContainer;
+import com.android.car.power.CarPowerManagementService;
+import com.android.car.user.CarUserService;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -90,6 +100,8 @@
public class CarPackageManagerService extends ICarPackageManager.Stub implements CarServiceBase {
+ static final boolean DBG = false;
+
private static final String TAG = CarLog.tagFor(CarPackageManagerService.class);
// Delimiters to parse packages and activities in the configuration XML resource.
@@ -97,6 +109,9 @@
private static final String PACKAGE_ACTIVITY_DELIMITER = "/";
private static final int LOG_SIZE = 20;
+ private static final String PROPERTY_RO_DRIVING_SAFETY_REGION =
+ "ro.android.car.drivingsafetyregion";
+
private final Context mContext;
private final SystemActivityMonitoringService mSystemActivityMonitoringService;
private final PackageManager mPackageManager;
@@ -139,8 +154,15 @@
@GuardedBy("mLock")
private final LinkedList<CarAppBlockingPolicy> mWaitingPolicies = new LinkedList<>();
+ @GuardedBy("mLock")
+ private String mCurrentDrivingSafetyRegion = CarPackageManager.DRIVING_SAFETY_REGION_ALL;
+ // Package name + '/' + className format
+ @GuardedBy("mLock")
+ private final ArraySet<String> mTempAllowedActivities = new ArraySet<>();
+
private final CarUxRestrictionsManagerService mCarUxRestrictionsService;
- private boolean mEnableActivityBlocking;
+ private final boolean mEnableActivityBlocking;
+
private final ComponentName mActivityBlockingActivity;
private final ActivityLaunchListener mActivityLaunchListener = new ActivityLaunchListener();
@@ -195,6 +217,32 @@
*/
public static final String BLOCKING_INTENT_EXTRA_DISPLAY_ID = "display_id";
+ private final CarUserManager.UserLifecycleListener mUserLifecycleListener =
+ new CarUserManager.UserLifecycleListener() {
+ @Override
+ public void onEvent(CarUserManager.UserLifecycleEvent event) {
+ if (event.getEventType()
+ == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING) {
+ synchronized (mLock) {
+ resetTempAllowedActivitiesLocked();
+ }
+ }
+ }
+ };
+
+ private final ICarPowerPolicyListener mDisplayPowerPolicyListener =
+ new ICarPowerPolicyListener.Stub() {
+ @Override
+ public void onPolicyChanged(CarPowerPolicy policy,
+ CarPowerPolicy accumulatedPolicy) {
+ if (!policy.isComponentEnabled(PowerComponent.DISPLAY)) {
+ synchronized (mLock) {
+ resetTempAllowedActivitiesLocked();
+ }
+ }
+ }
+ };
+
public CarPackageManagerService(Context context,
CarUxRestrictionsManagerService uxRestrictionsService,
SystemActivityMonitoringService systemActivityMonitoringService) {
@@ -236,13 +284,92 @@
mSystemActivityMonitoringService.restartTask(taskId);
}
- private void doSetAppBlockingPolicy(String packageName, CarAppBlockingPolicy policy,
- int flags) {
+ @Override
+ public String getCurrentDrivingSafetyRegion() {
+ assertAppBlockingOrDrivingStatePermission();
+ synchronized (mLock) {
+ return mCurrentDrivingSafetyRegion;
+ }
+ }
+
+ private String getComponentNameString(String packageName, String className) {
+ return packageName + '/' + className;
+ }
+
+ @Override
+ public void controlOneTimeActivityBlockingBypassingAsUser(String packageName,
+ String activityClassName, boolean bypass, @UserIdInt int userId) {
+ assertAppBlockingPermission();
+ if (!callerCanQueryPackage(packageName)) {
+ throw new SecurityException("cannot query other package");
+ }
+ try {
+ // Read this to check the validity of pkg / activity name. Not checking this can allow
+ // bad apps to be allowed later.
+ CarAppMetadataReader.getSupportedDrivingSafetyRegionsForActivityAsUser(mContext,
+ packageName, activityClassName, userId);
+ } catch (NameNotFoundException e) {
+ throw new ServiceSpecificException(CarPackageManager.ERROR_CODE_NO_PACKAGE,
+ e.getMessage());
+ }
+ String componentName = getComponentNameString(packageName, activityClassName);
+ synchronized (mLock) {
+ if (bypass) {
+ mTempAllowedActivities.add(componentName);
+ } else {
+ mTempAllowedActivities.remove(componentName);
+ }
+ }
+ if (!bypass) { // block top activities if bypassing is disabled.
+ blockTopActivitiesIfNecessary();
+ }
+ }
+
+ @GuardedBy("mLock")
+ private void resetTempAllowedActivitiesLocked() {
+ mTempAllowedActivities.clear();
+ }
+
+ @Override
+ public List<String> getSupportedDrivingSafetyRegionsForActivityAsUser(String packageName,
+ String activityClassName, @UserIdInt int userId) {
+ assertAppBlockingOrDrivingStatePermission();
+ if (!callerCanQueryPackage(packageName)) {
+ throw new SecurityException("cannot query other package");
+ }
+ long token = Binder.clearCallingIdentity();
+ try {
+ return CarAppMetadataReader.getSupportedDrivingSafetyRegionsForActivityAsUser(mContext,
+ packageName, activityClassName, userId);
+ } catch (NameNotFoundException e) {
+ throw new ServiceSpecificException(CarPackageManager.ERROR_CODE_NO_PACKAGE,
+ e.getMessage());
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
+ }
+
+ private void assertAppBlockingOrDrivingStatePermission() {
+ if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_CONTROL_APP_BLOCKING)
+ != PackageManager.PERMISSION_GRANTED && mContext.checkCallingOrSelfPermission(
+ Car.PERMISSION_CAR_DRIVING_STATE) != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException(
+ "requires permission " + Car.PERMISSION_CONTROL_APP_BLOCKING + " or "
+ + Car.PERMISSION_CAR_DRIVING_STATE);
+ }
+ }
+
+ private void assertAppBlockingPermission() {
if (mContext.checkCallingOrSelfPermission(Car.PERMISSION_CONTROL_APP_BLOCKING)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException(
"requires permission " + Car.PERMISSION_CONTROL_APP_BLOCKING);
}
+ }
+
+ private void doSetAppBlockingPolicy(String packageName, CarAppBlockingPolicy policy,
+ int flags) {
+ assertAppBlockingPermission();
CarServiceUtils.assertPackageName(mContext, packageName);
if (policy == null) {
throw new IllegalArgumentException("policy cannot be null");
@@ -282,6 +409,11 @@
Slog.d(TAG, "isActivityDistractionOptimized" + dumpPoliciesLocked(false));
}
+ if (mTempAllowedActivities.contains(getComponentNameString(packageName,
+ className))) {
+ return true;
+ }
+
if (searchFromClientPolicyBlocklistsLocked(packageName)) {
return false;
}
@@ -464,13 +596,24 @@
@Override
public void init() {
+ String safetyRegion = SystemProperties.get(PROPERTY_RO_DRIVING_SAFETY_REGION, "");
synchronized (mLock) {
+ setDrivingSafetyRegionWithCheckLocked(safetyRegion);
mHandler.requestInit();
}
+ CarLocalServices.getService(CarUserService.class).addUserLifecycleListener(
+ mUserLifecycleListener);
+ CarLocalServices.getService(CarPowerManagementService.class).addPowerPolicyListener(
+ new CarPowerPolicyFilter.Builder().setComponents(PowerComponent.DISPLAY).build(),
+ mDisplayPowerPolicyListener);
}
@Override
public void release() {
+ CarLocalServices.getService(CarPowerManagementService.class).removePowerPolicyListener(
+ mDisplayPowerPolicyListener);
+ CarLocalServices.getService(CarUserService.class).removeUserLifecycleListener(
+ mUserLifecycleListener);
synchronized (mLock) {
mHandler.requestRelease();
// wait for release do be done. This guarantees that init is done.
@@ -490,6 +633,7 @@
mProxies.clear();
}
mWaitingPolicies.clear();
+ resetTempAllowedActivitiesLocked();
mLock.notifyAll();
}
mContext.unregisterReceiver(mPackageParsingEventReceiver);
@@ -500,6 +644,28 @@
}
}
+ @GuardedBy("mLock")
+ private void setDrivingSafetyRegionWithCheckLocked(String region) {
+ if (region.isEmpty()) {
+ mCurrentDrivingSafetyRegion = CarPackageManager.DRIVING_SAFETY_REGION_ALL;
+ } else {
+ mCurrentDrivingSafetyRegion = region;
+ }
+ }
+
+ /**
+ * Reset driving stat and all dynamically added allow list so that region information for
+ * all packages are reset. This also resets one time allow list.
+ */
+ public void resetDrivingSafetyRegion(@NonNull String region) {
+ synchronized (mLock) {
+ setDrivingSafetyRegionWithCheckLocked(region);
+ resetTempAllowedActivitiesLocked();
+ mActivityAllowlistMap.clear();
+ mActivityDenylistPackages.clear();
+ }
+ }
+
// run from HandlerThread
private void doHandleInit() {
startAppBlockingPolicies();
@@ -742,9 +908,8 @@
}
try {
- String[] doActivities =
- CarAppMetadataReader.findDistractionOptimizedActivitiesAsUser(
- mContext, info.packageName, userId);
+ String[] doActivities = findDistractionOptimizedActivitiesAsUser(info.packageName,
+ userId);
if (doActivities != null) {
// Some of the activities in this app are Distraction Optimized.
if (Log.isLoggable(TAG, Log.DEBUG)) {
@@ -813,10 +978,14 @@
synchronized (mLock) {
if (wrapper != null) {
- Slog.d(TAG, "Package: " + packageName + " added in allowlist.");
+ if (DBG) {
+ Slog.d(TAG, "Package: " + packageName + " added in allowlist.");
+ }
mActivityAllowlistMap.put(packageName, wrapper);
} else {
- Slog.d(TAG, "Package: " + packageName + " added in denylist.");
+ if (DBG) {
+ Slog.d(TAG, "Package: " + packageName + " added in denylist.");
+ }
mActivityDenylistPackages.add(packageName);
}
}
@@ -1008,6 +1177,10 @@
writer.println(" Blocked activity log:");
mBlockedActivityLogs.dump(writer);
writer.print(dumpPoliciesLocked(true));
+ writer.print("mCurrentDrivingSafetyRegion:");
+ writer.println(mCurrentDrivingSafetyRegion);
+ writer.print("mTempAllowedActivities:");
+ writer.println(mTempAllowedActivities);
}
}
@@ -1121,13 +1294,11 @@
if (allowed) {
return;
}
- synchronized (mLock) {
- if (!mEnableActivityBlocking) {
- Slog.d(TAG, "Current activity " + topTask.topActivity
- + " not allowed, blocking disabled. Number of tasks in stack:"
- + topTask.taskInfo.childTaskIds.length);
- return;
- }
+ if (!mEnableActivityBlocking) {
+ Slog.d(TAG, "Current activity " + topTask.topActivity
+ + " not allowed, blocking disabled. Number of tasks in stack:"
+ + topTask.taskInfo.childTaskIds.length);
+ return;
}
if (Log.isLoggable(TAG, Log.DEBUG)) {
Slog.d(TAG, "Current activity " + topTask.topActivity
@@ -1228,13 +1399,23 @@
@Nullable
public String[] getDistractionOptimizedActivities(String pkgName) {
try {
- return CarAppMetadataReader.findDistractionOptimizedActivitiesAsUser(mContext, pkgName,
+ return findDistractionOptimizedActivitiesAsUser(pkgName,
mActivityManager.getCurrentUser());
} catch (NameNotFoundException e) {
return null;
}
}
+ private String[] findDistractionOptimizedActivitiesAsUser(String pkgName, int userId)
+ throws NameNotFoundException {
+ String regionString;
+ synchronized (mLock) {
+ regionString = mCurrentDrivingSafetyRegion;
+ }
+ return CarAppMetadataReader.findDistractionOptimizedActivitiesAsUser(mContext, pkgName,
+ userId, regionString);
+ }
+
/**
* Reading policy and setting policy can take time. Run it in a separate handler thread.
*/
diff --git a/service/src/com/android/car/power/SilentModeHandler.java b/service/src/com/android/car/power/SilentModeHandler.java
index 2631c01..bb78810 100644
--- a/service/src/com/android/car/power/SilentModeHandler.java
+++ b/service/src/com/android/car/power/SilentModeHandler.java
@@ -39,9 +39,9 @@
* Class to handle Silent Mode and Non-Silent Mode.
*
* <p>This monitors {@code /sys/power/pm_silentmode_hw_state} to figure out when to switch to Silent
- * Mode and updates {@code /sys/power/pm_silentmode_kernel} to tell early-init services about Silent
- * Mode change. Also, it handles forced Silent Mode for testing purpose, which is given through
- * reboot reason.
+ * Mode and updates {@code /sys/power/pm_silentmode_kernel_state} to tell early-init services about
+ * Silent Mode change. Also, it handles forced Silent Mode for testing purpose, which is given
+ * through reboot reason.
*/
final class SilentModeHandler {
static final String SILENT_MODE_FORCED_SILENT = "forced-silent";
@@ -53,7 +53,7 @@
private static final String SYSFS_FILENAME_HW_STATE_MONITORING =
"/sys/power/pm_silentmode_hw_state";
private static final String SYSFS_FILENAME_KERNEL_SILENTMODE =
- "/sys/power/pm_silentmode_kernel";
+ "/sys/power/pm_silentmode_kernel_state";
private static final String VALUE_SILENT_MODE = "1";
private static final String VALUE_NON_SILENT_MODE = "0";
private static final String SYSTEM_BOOT_REASON = "sys.boot.reason";
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index af573f7..8f9f2d2 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -50,6 +50,7 @@
<uses-permission android:name="android.car.permission.READ_CAR_STEERING"/>
<uses-permission android:name="android.car.permission.STORAGE_MONITORING"/>
<uses-permission android:name="android.car.permission.CAR_DYNAMICS_STATE"/>
+ <uses-permission android:name="android.car.permission.CONTROL_APP_BLOCKING"/>
<!-- Allow querying and writing to any property -->
<uses-permission android:name="android.car.permission.CAR_ENERGY_PORTS" />
<uses-permission android:name="android.car.permission.PERMISSION_CONTROL_ENERGY_PORTS" />
diff --git a/tests/android_car_api_test/AndroidManifest.xml b/tests/android_car_api_test/AndroidManifest.xml
index 7ae6fde..3e91a40 100644
--- a/tests/android_car_api_test/AndroidManifest.xml
+++ b/tests/android_car_api_test/AndroidManifest.xml
@@ -34,6 +34,44 @@
<action android:name="android.intent.action.MAIN"/>
</intent-filter>
</activity>
+ <activity android:name=".TestDrivingSafetyAllRegionActivity"
+ android:exported="true">
+ <meta-data android:name="distractionOptimized" android:value="true"/>
+ </activity>
+ <activity android:name=".TestDrivingSafetyExplicitAllRegionsActivity"
+ android:exported="true">
+ <meta-data android:name="distractionOptimized" android:value="true"/>
+ <!-- not necessary as all region is the default state but this is still valid -->
+ <meta-data android:name="android.car.drivingsafetyregions"
+ android:value="android.car.drivingsafetyregion.all"/>
+ </activity>
+ <activity android:name=".TestDrivingSafetyOneRegionActivity"
+ android:exported="true">
+ <meta-data android:name="distractionOptimized" android:value="true"/>
+ <meta-data android:name="android.car.drivingsafetyregions"
+ android:value="com.android.car.test.drivingsafetyregion.1"/>
+ </activity>
+ <activity android:name=".TestDrivingSafetyTwoRegionsActivity"
+ android:exported="true">
+ <meta-data android:name="distractionOptimized" android:value="true"/>
+ <meta-data android:name="android.car.drivingsafetyregions"
+ android:value="com.android.car.test.drivingsafetyregion.1,com.android.car.test.drivingsafetyregion.2"/>
+ </activity>
+ <activity android:name=".TestDrivingSafetyRegion1OnlyActivity"
+ android:exported="true">
+ <!--No distractionOptimized, so this app will be unsafe. -->
+ <meta-data android:name="android.car.drivingsafetyregions"
+ android:value="com.android.test.drivingsafetyregion.1"/>
+ </activity>
+ <activity android:name=".TestDrivingSafetyRegionAllOnlyActivity"
+ android:exported="true">
+ <!--No distractionOptimized, so this app will be unsafe. -->
+ <meta-data android:name="android.car.drivingsafetyregions"
+ android:value="android.car.drivingsafetyregion.all"/>
+ </activity>
+ <activity android:name=".TestDrivingSafetyRegionNoMetadataActivity"
+ android:exported="true">
+ </activity>
<service android:name=".CarProjectionManagerTest$TestService"
android:exported="true"/>
</application>
diff --git a/tests/android_car_api_test/src/android/car/apitest/DrivingSafetyRegionTest.java b/tests/android_car_api_test/src/android/car/apitest/DrivingSafetyRegionTest.java
new file mode 100644
index 0000000..473a8b1
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/DrivingSafetyRegionTest.java
@@ -0,0 +1,274 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.apitest;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+import static org.testng.Assert.assertThrows;
+
+import android.app.ActivityManager;
+import android.car.Car;
+import android.car.content.pm.CarPackageManager;
+import android.content.pm.PackageManager;
+import android.os.Build;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+@SmallTest
+public class DrivingSafetyRegionTest extends CarApiTestBase {
+ private static final String REGION1 = "com.android.car.test.drivingsafetyregion.1";
+ private static final String REGION2 = "com.android.car.test.drivingsafetyregion.2";
+ private static final String REGION3 = "com.android.car.test.drivingsafetyregion.3";
+
+ private static final String TEST_PACKAGE_NAME = "android.car.apitest";
+
+ private CarPackageManager mCarPackageManager;
+ private String mOriginalDrivingSafetyRegion = null;
+
+ private final int mCurrentUser = ActivityManager.getCurrentUser();
+
+ @Before
+ public void setUp() {
+ mCarPackageManager = (CarPackageManager) getCar().getCarManager(Car.PACKAGE_SERVICE);
+
+ assertThat(mCarPackageManager).isNotNull();
+
+ mOriginalDrivingSafetyRegion = mCarPackageManager.getCurrentDrivingSafetyRegion();
+
+ assertThat(mOriginalDrivingSafetyRegion).isNotNull();
+
+ // cannot run this in user build as region change is not allowed in user build for shell.
+ assumeTrue(Build.IS_ENG || Build.IS_USERDEBUG);
+ }
+
+ @After
+ public void tearDown() {
+ if (mOriginalDrivingSafetyRegion != null) {
+ setDrivingSafetyRegion(mOriginalDrivingSafetyRegion);
+ }
+ }
+
+ @Test
+ public void testImplicitAllRegions() throws Exception {
+ doTestAllRegions(TestDrivingSafetyAllRegionActivity.class.getName());
+ }
+
+ @Test
+ public void testExplicitAllRegions() throws Exception {
+ doTestAllRegions(TestDrivingSafetyExplicitAllRegionsActivity.class.getName());
+ }
+
+ private void doTestAllRegions(String activityClassName) throws Exception {
+ assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+ activityClassName)).isTrue();
+
+ List<String> regions = mCarPackageManager.getSupportedDrivingSafetyRegionsForActivityAsUser(
+ TEST_PACKAGE_NAME, activityClassName, ActivityManager.getCurrentUser());
+
+ assertThat(regions).containsExactly(CarPackageManager.DRIVING_SAFETY_REGION_ALL);
+
+ // all region app should be safe always regardless of bypassing / region change
+ setDrivingSafetyRegion(REGION1);
+
+ assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+ activityClassName)).isTrue();
+
+ mCarPackageManager.controlTemporaryActivityBlockingBypassingAsUser(TEST_PACKAGE_NAME,
+ activityClassName, true, mCurrentUser);
+
+ assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+ activityClassName)).isTrue();
+
+ mCarPackageManager.controlTemporaryActivityBlockingBypassingAsUser(TEST_PACKAGE_NAME,
+ activityClassName, false, mCurrentUser);
+
+ assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+ activityClassName)).isTrue();
+ }
+
+ @Test
+ public void testOneRegionOnly() throws Exception {
+ String activityClassName = TestDrivingSafetyOneRegionActivity.class.getName();
+
+ List<String> regions = mCarPackageManager.getSupportedDrivingSafetyRegionsForActivityAsUser(
+ TEST_PACKAGE_NAME, activityClassName, ActivityManager.getCurrentUser());
+
+ assertThat(regions).containsExactly(REGION1);
+
+ setDrivingSafetyRegion(REGION1);
+
+ assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+ activityClassName)).isTrue();
+
+ setDrivingSafetyRegion(REGION2);
+
+ assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+ activityClassName)).isFalse();
+
+ mCarPackageManager.controlTemporaryActivityBlockingBypassingAsUser(TEST_PACKAGE_NAME,
+ activityClassName, true, mCurrentUser);
+
+ assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+ activityClassName)).isTrue();
+
+ mCarPackageManager.controlTemporaryActivityBlockingBypassingAsUser(TEST_PACKAGE_NAME,
+ activityClassName, false, mCurrentUser);
+
+ assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+ activityClassName)).isFalse();
+
+ setDrivingSafetyRegion(CarPackageManager.DRIVING_SAFETY_REGION_ALL);
+
+ assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+ activityClassName)).isTrue();
+ }
+
+ @Test
+ public void testTwoRegionsOnly() throws Exception {
+ String activityClassName = TestDrivingSafetyTwoRegionsActivity.class.getName();
+
+ List<String> regions = mCarPackageManager.getSupportedDrivingSafetyRegionsForActivityAsUser(
+ TEST_PACKAGE_NAME, activityClassName, ActivityManager.getCurrentUser());
+
+ assertThat(regions).containsExactly(REGION1, REGION2);
+
+ setDrivingSafetyRegion(REGION1);
+
+ assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+ activityClassName)).isTrue();
+
+ setDrivingSafetyRegion(REGION2);
+
+ assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+ activityClassName)).isTrue();
+
+ setDrivingSafetyRegion(REGION3);
+
+ assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+ activityClassName)).isFalse();
+
+ setDrivingSafetyRegion(CarPackageManager.DRIVING_SAFETY_REGION_ALL);
+
+ assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+ activityClassName)).isTrue();
+ }
+
+ @Test
+ public void testRegion1OnlyActivity() throws Exception {
+ doTestRegionOnlyOrNoRegionCase(TestDrivingSafetyRegion1OnlyActivity.class.getName());
+ }
+
+ @Test
+ public void testRegionAllOnlyActivity() throws Exception {
+ doTestRegionOnlyOrNoRegionCase(TestDrivingSafetyRegionAllOnlyActivity.class.getName());
+ }
+
+ @Test
+ public void testRegionNoMetadataActivity() throws Exception {
+ doTestRegionOnlyOrNoRegionCase(TestDrivingSafetyRegionNoMetadataActivity.class.getName());
+ }
+
+ private void doTestRegionOnlyOrNoRegionCase(String activityClassName) throws Exception {
+ List<String> regions = mCarPackageManager.getSupportedDrivingSafetyRegionsForActivityAsUser(
+ TEST_PACKAGE_NAME, activityClassName, ActivityManager.getCurrentUser());
+
+ // not distraction optimized, so list should be empty.
+ assertThat(regions).isEmpty();
+
+ assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+ activityClassName)).isFalse();
+
+ // should not be safe for any region.
+ setDrivingSafetyRegion(CarPackageManager.DRIVING_SAFETY_REGION_ALL);
+
+ assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+ activityClassName)).isFalse();
+
+ setDrivingSafetyRegion(REGION1);
+
+ assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+ activityClassName)).isFalse();
+
+ setDrivingSafetyRegion(REGION2);
+
+ assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+ activityClassName)).isFalse();
+
+ mCarPackageManager.controlTemporaryActivityBlockingBypassingAsUser(TEST_PACKAGE_NAME,
+ activityClassName, true, mCurrentUser);
+
+ assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+ activityClassName)).isTrue();
+
+ mCarPackageManager.controlTemporaryActivityBlockingBypassingAsUser(TEST_PACKAGE_NAME,
+ activityClassName, false, mCurrentUser);
+
+ assertThat(mCarPackageManager.isActivityDistractionOptimized(TEST_PACKAGE_NAME,
+ activityClassName)).isFalse();
+ }
+
+ @Test
+ public void testNoPackage() {
+ String noPkg = "NoSuchPackage";
+
+ assertThrows(PackageManager.NameNotFoundException.class,
+ () -> mCarPackageManager.getSupportedDrivingSafetyRegionsForActivityAsUser(
+ noPkg, "", mCurrentUser));
+
+ assertThrows(PackageManager.NameNotFoundException.class,
+ () -> mCarPackageManager.controlTemporaryActivityBlockingBypassingAsUser(
+ noPkg, "", true, mCurrentUser));
+ }
+
+ @Test
+ public void testNoActivity() {
+ String noSuchActivity = "NoSuchActivity";
+
+ assertThrows(PackageManager.NameNotFoundException.class,
+ () -> mCarPackageManager.getSupportedDrivingSafetyRegionsForActivityAsUser(
+ TEST_PACKAGE_NAME, noSuchActivity, mCurrentUser));
+
+ assertThrows(PackageManager.NameNotFoundException.class,
+ () -> mCarPackageManager.controlTemporaryActivityBlockingBypassingAsUser(
+ TEST_PACKAGE_NAME, noSuchActivity, true, mCurrentUser));
+ }
+
+ @Test
+ public void testResetEmptyRegion() {
+ setDrivingSafetyRegion(REGION1);
+
+ assertThat(mCarPackageManager.getCurrentDrivingSafetyRegion()).isEqualTo(REGION1);
+
+ // no arg means all
+ runShellCommand("cmd car_service set-drivingsafety-region");
+
+ assertThat(mCarPackageManager.getCurrentDrivingSafetyRegion()).isEqualTo(
+ CarPackageManager.DRIVING_SAFETY_REGION_ALL);
+ }
+
+ private void setDrivingSafetyRegion(String region) {
+ runShellCommand("cmd car_service set-drivingsafety-region " + region);
+ }
+}
diff --git a/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyAllRegionActivity.java b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyAllRegionActivity.java
new file mode 100644
index 0000000..48af4dd
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyAllRegionActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.apitest;
+
+import android.app.Activity;
+
+public class TestDrivingSafetyAllRegionActivity extends Activity {
+}
diff --git a/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyExplicitAllRegionsActivity.java b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyExplicitAllRegionsActivity.java
new file mode 100644
index 0000000..f369acc
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyExplicitAllRegionsActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.apitest;
+
+import android.app.Activity;
+
+public class TestDrivingSafetyExplicitAllRegionsActivity extends Activity {
+}
diff --git a/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyOneRegionActivity.java b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyOneRegionActivity.java
new file mode 100644
index 0000000..623e5e0
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyOneRegionActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.apitest;
+
+import android.app.Activity;
+
+public class TestDrivingSafetyOneRegionActivity extends Activity {
+}
diff --git a/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyRegion1OnlyActivity.java b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyRegion1OnlyActivity.java
new file mode 100644
index 0000000..2256330
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyRegion1OnlyActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.apitest;
+
+import android.app.Activity;
+
+public class TestDrivingSafetyRegion1OnlyActivity extends Activity {
+}
diff --git a/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyRegionAllOnlyActivity.java b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyRegionAllOnlyActivity.java
new file mode 100644
index 0000000..863d308
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyRegionAllOnlyActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.apitest;
+
+import android.app.Activity;
+
+public class TestDrivingSafetyRegionAllOnlyActivity extends Activity {
+}
diff --git a/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyRegionNoMetadataActivity.java b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyRegionNoMetadataActivity.java
new file mode 100644
index 0000000..e633b6c
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyRegionNoMetadataActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.apitest;
+
+import android.app.Activity;
+
+public class TestDrivingSafetyRegionNoMetadataActivity extends Activity {
+}
diff --git a/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyTwoRegionsActivity.java b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyTwoRegionsActivity.java
new file mode 100644
index 0000000..3124b81
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/TestDrivingSafetyTwoRegionsActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.apitest;
+
+import android.app.Activity;
+
+public class TestDrivingSafetyTwoRegionsActivity extends Activity {
+}