Add driving safety region support with one time bypassing
- Driving safety regions of app tells which regional
regulation the app supports.
- App still should have distractionOptimized metadata
besides driving safety region metadata.
- Apps not supporting the curret system's region will be considered
as not safe.
- driving safety region can be set from system properties:
ro.android.car.drivingsafetyregion
- It can be also set through adb shell command for testing purpose but
it is not allowed for user build.
$ adb shell cmd car_service set-drivingsafety-region {REGION_NAME}
- Driving safety region is currently only for OEM apps only.
Other than all region, there is no other standared regions
defined.
- App should specify its regions using android.car.drivingsafetyregions
metadata in AndroidManifest.
- Check added DrivingSafetyRegionTest for additional details on the
expected behavior.
Bug: 193247516
Test: run added api test under userdebug or eng build
$ atest android.car.apitest.DrivingSafetyRegionTest
Change-Id: I31728ac6cebf25ce93fac0e47a8dadee954e67e2
Merged-In: I31728ac6cebf25ce93fac0e47a8dadee954e67e2
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/service/src/com/android/car/CarShellCommand.java b/service/src/com/android/car/CarShellCommand.java
index 2e16d10..f1fec38 100644
--- a/service/src/com/android/car/CarShellCommand.java
+++ b/service/src/com/android/car/CarShellCommand.java
@@ -41,6 +41,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;
@@ -205,6 +206,9 @@
private static final String COMMAND_WATCHDOG_CONTROL_PROCESS_HEALTH_CHECK =
"watchdog-control-health-check";
+ 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
@@ -637,6 +641,10 @@
pw.printf("\t%s enable|disable\n", COMMAND_WATCHDOG_CONTROL_PROCESS_HEALTH_CHECK);
pw.println("\t Enables/disables car watchdog process health check.");
pw.println("\t Set to true to disable the process health check.");
+
+ 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) {
@@ -972,6 +980,9 @@
case COMMAND_WATCHDOG_CONTROL_PROCESS_HEALTH_CHECK:
controlWatchdogProcessHealthCheck(args, writer);
break;
+ case COMMAND_DRIVING_SAFETY_SET_REGION:
+ setDrivingSafetyRegion(args, writer);
+ break;
default:
writer.println("Unknown command: \"" + cmd + "\"");
showHelp(writer);
@@ -2155,6 +2166,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 10288b1..e8e4315 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;
@@ -57,6 +62,8 @@
import android.os.Process;
import android.os.RemoteException;
import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
+import android.os.SystemProperties;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -69,6 +76,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;
@@ -76,6 +84,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;
import com.android.server.utils.Slogf;
@@ -99,6 +109,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.
@@ -107,6 +119,9 @@
private static final int LOG_SIZE = 20;
private static final String[] WINDOW_DUMP_ARGUMENTS = new String[]{"windows"};
+ 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;
@@ -153,8 +168,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 boolean mPreventTemplatedAppsFromShowingDialog;
private final String mTemplateActivityClassName;
@@ -211,6 +233,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) {
@@ -256,13 +304,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");
@@ -302,6 +429,11 @@
Slog.d(TAG, "isActivityDistractionOptimized" + dumpPoliciesLocked(false));
}
+ if (mTempAllowedActivities.contains(getComponentNameString(packageName,
+ className))) {
+ return true;
+ }
+
for (int i = mTopActivityWithDialogPerDisplay.size() - 1; i >= 0; i--) {
ComponentName activityWithDialog = mTopActivityWithDialogPerDisplay.get(
mTopActivityWithDialogPerDisplay.keyAt(i));
@@ -493,13 +625,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.
@@ -519,6 +662,7 @@
mProxies.clear();
}
mWaitingPolicies.clear();
+ resetTempAllowedActivitiesLocked();
mLock.notifyAll();
}
mContext.unregisterReceiver(mPackageParsingEventReceiver);
@@ -529,6 +673,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();
@@ -771,9 +937,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)) {
@@ -842,10 +1007,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);
}
}
@@ -1026,9 +1195,9 @@
synchronized (mLock) {
writer.println("*CarPackageManagerService*");
writer.println("mEnableActivityBlocking:" + mEnableActivityBlocking);
- writer.println("mPreventTemplatedAppsFromShowingDialog"
+ writer.println("mPreventTemplatedAppsFromShowingDialog:"
+ mPreventTemplatedAppsFromShowingDialog);
- writer.println("mTemplateActivityClassName" + mTemplateActivityClassName);
+ writer.println("mTemplateActivityClassName:" + mTemplateActivityClassName);
List<String> restrictions = new ArrayList<>(mUxRestrictionsListeners.size());
for (int i = 0; i < mUxRestrictionsListeners.size(); i++) {
int displayId = mUxRestrictionsListeners.keyAt(i);
@@ -1040,6 +1209,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);
}
}
@@ -1158,13 +1331,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
@@ -1343,13 +1514,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/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 {
+}