Merge changes I94e08d61,I431fe6b7 into sc-dev
* changes:
Read the on-device XML configurations on initialization.
Convert per state thresholds read from XML configs.
diff --git a/car-lib/src/android/car/VehiclePropertyIds.java b/car-lib/src/android/car/VehiclePropertyIds.java
index cb52c42..3e6c3c5 100644
--- a/car-lib/src/android/car/VehiclePropertyIds.java
+++ b/car-lib/src/android/car/VehiclePropertyIds.java
@@ -138,7 +138,8 @@
/**
* Speed of the vehicle in meters per second.
*
- * <p>PERF_VEHICLE_SPEED property is {@link VehiclePropertyAccess#READ} access, {@link
+ * <p>PERF_VEHICLE_SPEED property is {@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}, {@link
+ * CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}, {@link
* CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS}, and returns a Float type value.
*
* <p>When the vehicle is moving forward, PERF_VEHICLE_SPEED is positive and negative when the
@@ -207,9 +208,9 @@
/**
* Reports wheel ticks.
*
- * <p>WHEEL_TICK property is {@link VehiclePropertyAccess#Read} access,
- * {@link CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS} and returns a Long[] type
- * value.
+ * <p>WHEEL_TICK property is {@link VehicleAreaType#VEHICLE_AREA_TYPE_GLOBAL}, {@link
+ * CarPropertyConfig#VEHICLE_PROPERTY_ACCESS_READ}, {@link
+ * CarPropertyConfig#VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS} and returns a Long[] type value.
*
* <p>The first element in the array is a reset count. A reset indicates
* previous tick counts are not comparable with this and future ones. Some
diff --git a/car-lib/src/android/car/evs/CarEvsManager.java b/car-lib/src/android/car/evs/CarEvsManager.java
index c1eb21b..65ac9e5 100644
--- a/car-lib/src/android/car/evs/CarEvsManager.java
+++ b/car-lib/src/android/car/evs/CarEvsManager.java
@@ -23,23 +23,22 @@
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
-import android.car.annotation.RequiredFeature;
import android.car.Car;
import android.car.CarManagerBase;
+import android.car.annotation.RequiredFeature;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.util.Slog;
-import android.view.SurfaceHolder;
import com.android.internal.annotations.GuardedBy;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
-import java.util.concurrent.Executor;
import java.util.Objects;
+import java.util.concurrent.Executor;
/**
* Provides an application interface for interativing with the Extended View System service.
diff --git a/car-lib/src/android/car/hardware/CarSensorManager.java b/car-lib/src/android/car/hardware/CarSensorManager.java
index b34e171..f933d9e 100644
--- a/car-lib/src/android/car/hardware/CarSensorManager.java
+++ b/car-lib/src/android/car/hardware/CarSensorManager.java
@@ -51,7 +51,12 @@
public static final int SENSOR_TYPE_RESERVED1 = 1;
/**
* This sensor represents vehicle speed in m/s.
- * Sensor data in {@link CarSensorEvent} is a float which will be >= 0.
+ *
+ * <p>Sensor data in {@link CarSensorEvent} is a float. When the vehicle is moving forward,
+ * SENSOR_TYPE_CAR_SPEED is positive and negative when the vehicle is moving backward. Also,
+ * this value is independent of SENSOR_TYPE_GEAR. For example, if SENSOR_TYPE_GEAR is {@link
+ * CarSensorEvent#GEAR_NEUTRAL}, SENSOR_TYPE_CAR_SPEED is positive when the vehicle is moving
+ * forward, negative when moving backward, and zero when not moving.
*/
public static final int SENSOR_TYPE_CAR_SPEED = 0x11600207;
/**
diff --git a/car-lib/src/android/car/input/CarInputHandlingService.java b/car-lib/src/android/car/input/CarInputHandlingService.java
index 02a4929..c7e3f96 100644
--- a/car-lib/src/android/car/input/CarInputHandlingService.java
+++ b/car-lib/src/android/car/input/CarInputHandlingService.java
@@ -15,6 +15,8 @@
*/
package android.car.input;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DEPRECATED_CODE;
+
import android.annotation.CallSuper;
import android.annotation.MainThread;
import android.annotation.SystemApi;
@@ -32,6 +34,8 @@
import android.util.Log;
import android.view.KeyEvent;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
@@ -57,6 +61,7 @@
*/
@SystemApi
@Deprecated
+@ExcludeFromCodeCoverageGeneratedReport(reason = DEPRECATED_CODE)
public abstract class CarInputHandlingService extends Service {
private static final String TAG = CarLibLog.TAG_INPUT;
private static final boolean DBG = false;
diff --git a/car-lib/src/com/android/car/internal/ExcludeFromCodeCoverageGeneratedReport.java b/car-lib/src/com/android/car/internal/ExcludeFromCodeCoverageGeneratedReport.java
new file mode 100644
index 0000000..dfbe530
--- /dev/null
+++ b/car-lib/src/com/android/car/internal/ExcludeFromCodeCoverageGeneratedReport.java
@@ -0,0 +1,66 @@
+/*
+ * 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 com.android.car.internal;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation used to mark code to be excluded from coverage report.
+ */
+@Target({ElementType.TYPE, ElementType.CONSTRUCTOR, ElementType.METHOD})
+public @interface ExcludeFromCodeCoverageGeneratedReport {
+
+ // Reason annotation and its associated constant values
+ int DEPRECATED_CODE = 0;
+ int BOILERPLATE_CODE = 1;
+ int DUMP_INFO = 2;
+ int DEBUGGING_CODE = 3;
+
+ @IntDef(prefix = "REASON_", value = {
+ DEPRECATED_CODE,
+ BOILERPLATE_CODE,
+ DUMP_INFO,
+ DEBUGGING_CODE
+ })
+ @interface Reason { }
+
+ /**
+ * The reason explaining why the code is being excluded from the code coverage report.
+ * <p>
+ * Possible reasons to exclude code from coverage report are:
+ * <p><ul>
+ * <li>{@link ExcludeFromCodeCoverageGeneratedReport#DEPRECATED_CODE} to exclude deprecated
+ * code from coverage report
+ * <li>{@link ExcludeFromCodeCoverageGeneratedReport#BOILERPLATE_CODE} to exclude boilerplate
+ * code like {@link java.lang.Object} methods, {@link android.os.Parcel} methods, etc
+ * <li>{@link ExcludeFromCodeCoverageGeneratedReport#DUMP_INFO} to exclude dump info methods
+ * <li>{@link ExcludeFromCodeCoverageGeneratedReport#DEBUGGING_CODE} to exclude debugging
+ * purpose
+ * code
+ * </ul><p>
+ */
+ @Reason int reason();
+
+ /**
+ * Optional field used to provide extra details about the excluded code (e.g. it can be used to
+ * tag a follow up bug).
+ */
+ String details() default "";
+}
diff --git a/car_product/build/car.mk b/car_product/build/car.mk
index 3107597..df09aae 100644
--- a/car_product/build/car.mk
+++ b/car_product/build/car.mk
@@ -40,8 +40,6 @@
PRODUCT_PACKAGES += \
DefaultStorageMonitoringCompanionApp \
EmbeddedKitchenSinkApp \
- DirectRenderingCluster \
- ClusterHomeSample \
GarageModeTestApp \
ExperimentalCarService \
BugReportApp \
@@ -52,6 +50,15 @@
BOARD_SEPOLICY_DIRS += packages/services/Car/car_product/sepolicy/test
endif
+# ClusterOsDouble is the testing app to test Cluster2 framework and it can handle Cluster VHAL
+# and do some Cluster OS role.
+ifeq ($(ENABLE_CLUSTER_OS_DOUBLE), true)
+PRODUCT_PACKAGES += ClusterHomeSample ClusterOsDouble
+else
+# DirectRenderingCluster is the sample app for the old Cluster framework.
+PRODUCT_PACKAGES += DirectRenderingCluster
+endif # ENABLE_CLUSTER_OS_DOUBLE
+
PRODUCT_COPY_FILES += \
frameworks/av/media/libeffects/data/audio_effects.conf:system/etc/audio_effects.conf
diff --git a/cpp/computepipe/aidl/Android.bp b/cpp/computepipe/aidl/Android.bp
index 2cf9ca1..bb0e1e3 100644
--- a/cpp/computepipe/aidl/Android.bp
+++ b/cpp/computepipe/aidl/Android.bp
@@ -13,6 +13,9 @@
"android.hardware.graphics.common-V2",
],
stability: "vintf",
+ // Automotive has different platform schedule. We shouldn't need to
+ // freeze Auto interfaces when the mainline Android launches.
+ owner: "automotive",
backend: {
java: {
enabled: false,
@@ -33,6 +36,9 @@
"android/automotive/computepipe/*.aidl",
],
stability: "vintf",
+ // Automotive has different platform schedule. We shouldn't need to
+ // freeze Auto interfaces when the mainline Android launches.
+ owner: "automotive",
backend: {
java: {
enabled: false,
diff --git a/cpp/powerpolicy/server/src/PolicyManager.cpp b/cpp/powerpolicy/server/src/PolicyManager.cpp
index 55100e9..2681f51 100644
--- a/cpp/powerpolicy/server/src/PolicyManager.cpp
+++ b/cpp/powerpolicy/server/src/PolicyManager.cpp
@@ -48,7 +48,7 @@
namespace {
// Vendor power policy filename.
-constexpr const char* kVendorPolicyFile = "/vendor/etc/power_policy.xml";
+constexpr const char* kVendorPolicyFile = "/vendor/etc/automotive/power_policy.xml";
// Tags and attributes in vendor power policy XML file.
constexpr const char* kTagRoot = "powerPolicy";
diff --git a/service/src/com/android/car/CarInputService.java b/service/src/com/android/car/CarInputService.java
index aeb384a..5870b32 100644
--- a/service/src/com/android/car/CarInputService.java
+++ b/service/src/com/android/car/CarInputService.java
@@ -19,6 +19,8 @@
import static android.hardware.input.InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
import static android.service.voice.VoiceInteractionSession.SHOW_SOURCE_PUSH_TO_TALK;
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
+
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.ActivityManager;
@@ -56,6 +58,7 @@
import android.view.ViewConfiguration;
import com.android.car.hal.InputHalService;
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
import com.android.car.internal.common.UserHelperLite;
import com.android.car.user.CarUserService;
import com.android.internal.annotations.GuardedBy;
@@ -689,6 +692,7 @@
}
@Override
+ @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
public void dump(IndentingPrintWriter writer) {
writer.println("*Input Service*");
writer.println("Long-press delay: " + mLongPressDelaySupplier.getAsInt() + "ms");
diff --git a/service/src/com/android/car/CarShellCommand.java b/service/src/com/android/car/CarShellCommand.java
index 7a8c9cb..dcc9371 100644
--- a/service/src/com/android/car/CarShellCommand.java
+++ b/service/src/com/android/car/CarShellCommand.java
@@ -154,6 +154,8 @@
"get-user-auth-association";
private static final String COMMAND_SET_USER_AUTH_ASSOCIATION =
"set-user-auth-association";
+ private static final String COMMAND_SET_START_BG_USERS_ON_GARAGE_MODE =
+ "set-start-bg-users-on-garage-mode";
private static final String COMMAND_DEFINE_POWER_POLICY = "define-power-policy";
private static final String COMMAND_APPLY_POWER_POLICY = "apply-power-policy";
private static final String COMMAND_DEFINE_POWER_POLICY_GROUP = "define-power-policy-group";
@@ -190,7 +192,7 @@
// This map is looked up first, then USER_BUILD_COMMAND_TO_PERMISSION_MAP
private static final ArrayMap<String, String[]> USER_BUILD_COMMAND_TO_PERMISSIONS_MAP;
static {
- USER_BUILD_COMMAND_TO_PERMISSIONS_MAP = new ArrayMap<>(6);
+ USER_BUILD_COMMAND_TO_PERMISSIONS_MAP = new ArrayMap<>(7);
USER_BUILD_COMMAND_TO_PERMISSIONS_MAP.put(COMMAND_GET_INITIAL_USER_INFO,
CREATE_OR_MANAGE_USERS_PERMISSIONS);
USER_BUILD_COMMAND_TO_PERMISSIONS_MAP.put(COMMAND_SWITCH_USER,
@@ -203,6 +205,8 @@
CREATE_OR_MANAGE_USERS_PERMISSIONS);
USER_BUILD_COMMAND_TO_PERMISSIONS_MAP.put(COMMAND_SET_USER_AUTH_ASSOCIATION,
CREATE_OR_MANAGE_USERS_PERMISSIONS);
+ USER_BUILD_COMMAND_TO_PERMISSIONS_MAP.put(COMMAND_SET_START_BG_USERS_ON_GARAGE_MODE,
+ CREATE_OR_MANAGE_USERS_PERMISSIONS);
}
// List of commands allowed in user build. All these command should be protected with
@@ -526,6 +530,11 @@
pw.printf("\t %s\n", VALID_USER_AUTH_TYPES_HELP);
pw.printf("\t %s\n", VALID_USER_AUTH_SET_VALUES_HELP);
+ pw.printf("\t%s [true|false]\n", COMMAND_SET_START_BG_USERS_ON_GARAGE_MODE);
+ pw.println("\t Controls backgroud user start and stop during garage mode.");
+ pw.println("\t If false, garage mode operations (background users start at garage mode"
+ + " entry and background users stop at garage mode exit) will be skipped.");
+
pw.printf("\t %s [%s|%s|%s|%s]\n", COMMAND_SILENT_MODE, SILENT_MODE_FORCED_SILENT,
SILENT_MODE_FORCED_NON_SILENT, SILENT_MODE_NON_FORCED, PARAM_QUERY_MODE);
pw.println("\t Forces silent mode silent or non-silent. With query (or no command) "
@@ -856,6 +865,9 @@
case COMMAND_SET_USER_AUTH_ASSOCIATION:
setUserAuthAssociation(args, writer);
break;
+ case COMMAND_SET_START_BG_USERS_ON_GARAGE_MODE:
+ setStartBackgroundUsersOnGarageMode(args, writer);
+ break;
case COMMAND_EMULATE_DRIVING_STATE:
emulateDrivingState(args, writer);
break;
@@ -879,6 +891,18 @@
return RESULT_OK;
}
+ private void setStartBackgroundUsersOnGarageMode(String[] args, IndentingPrintWriter writer) {
+ if (args.length < 2) {
+ writer.println("Insufficient number of args");
+ return;
+ }
+
+ boolean enabled = Boolean.parseBoolean(args[1]);
+ Slog.d(TAG, "setStartBackgroundUsersOnGarageMode(): " + (enabled ? "enabled" : "disabled"));
+ mCarUserService.setStartBackgroundUsersOnGarageMode(enabled);
+ writer.printf("StartBackgroundUsersOnGarageMode set to %b\n", enabled);
+ }
+
private void startFixedActivity(String[] args, IndentingPrintWriter writer) {
if (args.length != 4) {
writer.println("Incorrect number of arguments");
diff --git a/service/src/com/android/car/audio/CarAudioDeviceInfo.java b/service/src/com/android/car/audio/CarAudioDeviceInfo.java
index 091b505..62af82d 100644
--- a/service/src/com/android/car/audio/CarAudioDeviceInfo.java
+++ b/service/src/com/android/car/audio/CarAudioDeviceInfo.java
@@ -39,6 +39,7 @@
*/
/* package */ class CarAudioDeviceInfo {
+ public static final int DEFAULT_SAMPLE_RATE = 48000;
private final AudioDeviceInfo mAudioDeviceInfo;
private final int mSampleRate;
private final int mEncodingFormat;
@@ -58,10 +59,10 @@
CarAudioDeviceInfo(AudioDeviceInfo audioDeviceInfo) {
mAudioDeviceInfo = audioDeviceInfo;
mSampleRate = getMaxSampleRate(audioDeviceInfo);
- mEncodingFormat = getEncodingFormat(audioDeviceInfo);
+ mEncodingFormat = AudioFormat.ENCODING_PCM_16BIT;
mChannelCount = getMaxChannels(audioDeviceInfo);
- final AudioGain audioGain = Objects.requireNonNull(
- getAudioGain(), "No audio gain on device port " + audioDeviceInfo);
+ AudioGain audioGain = Objects.requireNonNull(getAudioGain(audioDeviceInfo.getPort()),
+ "No audio gain on device port " + audioDeviceInfo);
mDefaultGain = audioGain.defaultValue();
mMaxGain = audioGain.maxValue();
mMinGain = audioGain.minValue();
@@ -110,6 +111,15 @@
return mStepValue;
}
+ /**
+ * @return {@link AudioGain} with {@link AudioGain#MODE_JOINT} on a given {@link AudioPort}.
+ * This is useful for inspecting the configuration data associated with this gain controller
+ * (min/max/step/default).
+ */
+ AudioGain getAudioGain() {
+ return getAudioGain(getAudioDevicePort());
+ }
+
// Input is in millibels
void setCurrentGain(int gainInMillibels) {
// Clamp the incoming value to our valid range. Out of range values ARE legal input
@@ -148,10 +158,10 @@
}
}
- private int getMaxSampleRate(AudioDeviceInfo info) {
+ private static int getMaxSampleRate(AudioDeviceInfo info) {
int[] sampleRates = info.getSampleRates();
if (sampleRates == null || sampleRates.length == 0) {
- return 48000;
+ return DEFAULT_SAMPLE_RATE;
}
int sampleRate = sampleRates[0];
for (int i = 1; i < sampleRates.length; i++) {
@@ -162,19 +172,7 @@
return sampleRate;
}
- /** Always returns {@link AudioFormat#ENCODING_PCM_16BIT} as for now */
- private int getEncodingFormat(AudioDeviceInfo info) {
- return AudioFormat.ENCODING_PCM_16BIT;
- }
-
- /**
- * Gets the maximum channel count for a given {@link AudioDeviceInfo}
- *
- * @param info {@link AudioDeviceInfo} instance to get maximum channel count for
- * @return Maximum channel count for a given {@link AudioDeviceInfo},
- * 1 (mono) if there is no channel masks configured
- */
- private int getMaxChannels(AudioDeviceInfo info) {
+ private static int getMaxChannels(AudioDeviceInfo info) {
int numChannels = 1;
int[] channelMasks = info.getChannelMasks();
if (channelMasks == null) {
@@ -189,13 +187,7 @@
return numChannels;
}
- /**
- * @return {@link AudioGain} with {@link AudioGain#MODE_JOINT} on a given {@link AudioPort}.
- * This is useful for inspecting the configuration data associated with this gain controller
- * (min/max/step/default).
- */
- AudioGain getAudioGain() {
- final AudioDevicePort audioPort = getAudioDevicePort();
+ private static AudioGain getAudioGain(AudioDevicePort audioPort) {
if (audioPort != null && audioPort.gains().length > 0) {
for (AudioGain audioGain : audioPort.gains()) {
if ((audioGain.mode() & AudioGain.MODE_JOINT) != 0) {
@@ -209,7 +201,7 @@
/**
* Constraints applied to gain configuration, see also audio_policy_configuration.xml
*/
- private AudioGain checkAudioGainConfiguration(AudioGain audioGain) {
+ private static AudioGain checkAudioGainConfiguration(AudioGain audioGain) {
Preconditions.checkArgument(audioGain.maxValue() >= audioGain.minValue());
Preconditions.checkArgument((audioGain.defaultValue() >= audioGain.minValue())
&& (audioGain.defaultValue() <= audioGain.maxValue()));
diff --git a/service/src/com/android/car/audio/CarDucking.java b/service/src/com/android/car/audio/CarDucking.java
index 3e05b95..0ac53c4 100644
--- a/service/src/com/android/car/audio/CarDucking.java
+++ b/service/src/com/android/car/audio/CarDucking.java
@@ -58,25 +58,43 @@
}
}
- public void onFocusChange(int audioZoneId, @NonNull List<AudioFocusInfo> focusHolders) {
+ @Override
+ public void onFocusChange(int[] audioZoneIds,
+ @NonNull SparseArray<List<AudioFocusInfo>> focusHoldersByZoneId) {
synchronized (mLock) {
- CarDuckingInfo oldDuckingInfo = mCurrentDuckingInfo.get(audioZoneId);
- CarDuckingInfo newDuckingInfo = generateNewDuckingInfoLocked(oldDuckingInfo,
- focusHolders);
- mCurrentDuckingInfo.put(audioZoneId, newDuckingInfo);
- mAudioControlWrapper.onDevicesToDuckChange(newDuckingInfo);
+ List<CarDuckingInfo> newDuckingInfos = new ArrayList<>(audioZoneIds.length);
+ for (int i = 0; i < audioZoneIds.length; i++) {
+ int zoneId = audioZoneIds[i];
+ List<AudioFocusInfo> focusHolders = focusHoldersByZoneId.get(zoneId);
+ CarDuckingInfo newDuckingInfo = updateDuckingForZoneIdLocked(zoneId, focusHolders);
+ newDuckingInfos.add(newDuckingInfo);
+ }
+ mAudioControlWrapper.onDevicesToDuckChange(newDuckingInfos);
}
}
+ @GuardedBy("mLock")
+ private CarDuckingInfo updateDuckingForZoneIdLocked(int zoneId,
+ List<AudioFocusInfo> focusHolders) {
+ CarDuckingInfo oldDuckingInfo = mCurrentDuckingInfo.get(zoneId);
+ CarDuckingInfo newDuckingInfo = generateNewDuckingInfoLocked(oldDuckingInfo,
+ focusHolders);
+ mCurrentDuckingInfo.put(zoneId, newDuckingInfo);
+ return newDuckingInfo;
+ }
+
public void dump(IndentingPrintWriter writer) {
writer.printf("*%s*\n", TAG);
writer.increaseIndent();
- for (int i = 0; i < mCurrentDuckingInfo.size(); i++) {
- mCurrentDuckingInfo.valueAt(i).dump(writer);
+ synchronized (mLock) {
+ for (int i = 0; i < mCurrentDuckingInfo.size(); i++) {
+ mCurrentDuckingInfo.valueAt(i).dump(writer);
+ }
}
writer.decreaseIndent();
}
+ @GuardedBy("mLock")
private CarDuckingInfo generateNewDuckingInfoLocked(CarDuckingInfo oldDuckingInfo,
List<AudioFocusInfo> focusHolders) {
int zoneId = oldDuckingInfo.mZoneId;
diff --git a/service/src/com/android/car/audio/CarZonesAudioFocus.java b/service/src/com/android/car/audio/CarZonesAudioFocus.java
index 4f19e1a..ca5d532 100644
--- a/service/src/com/android/car/audio/CarZonesAudioFocus.java
+++ b/service/src/com/android/car/audio/CarZonesAudioFocus.java
@@ -148,16 +148,19 @@
}
void setRestrictFocus(boolean isFocusRestricted) {
+ int[] zoneIds = new int[mFocusZones.size()];
for (int i = 0; i < mFocusZones.size(); i++) {
+ zoneIds[i] = mFocusZones.keyAt(i);
mFocusZones.valueAt(i).setRestrictFocus(isFocusRestricted);
}
+ notifyFocusCallback(zoneIds);
}
@Override
public void onAudioFocusRequest(AudioFocusInfo afi, int requestResult) {
int zoneId = getAudioZoneIdForAudioFocusInfo(afi);
getCarAudioFocusForZoneId(zoneId).onAudioFocusRequest(afi, requestResult);
- notifyFocusCallback(zoneId);
+ notifyFocusCallback(new int[]{zoneId});
}
/**
@@ -169,7 +172,7 @@
public void onAudioFocusAbandon(AudioFocusInfo afi) {
int zoneId = getAudioZoneIdForAudioFocusInfo(afi);
getCarAudioFocusForZoneId(zoneId).onAudioFocusAbandon(afi);
- notifyFocusCallback(zoneId);
+ notifyFocusCallback(new int[]{zoneId});
}
@NonNull
@@ -203,13 +206,18 @@
return zoneId;
}
- private void notifyFocusCallback(int audioZoneId) {
+ private void notifyFocusCallback(int[] zoneIds) {
if (mCarFocusCallback == null) {
return;
}
+ SparseArray<List<AudioFocusInfo>> focusHoldersByZoneId = new SparseArray<>();
+ for (int i = 0; i < zoneIds.length; i++) {
+ int zoneId = zoneIds[i];
+ List<AudioFocusInfo> focusHolders = mFocusZones.get(zoneId).getAudioFocusHolders();
+ focusHoldersByZoneId.put(zoneId, focusHolders);
+ }
- List<AudioFocusInfo> focusHolders = mFocusZones.get(audioZoneId).getAudioFocusHolders();
- mCarFocusCallback.onFocusChange(audioZoneId, focusHolders);
+ mCarFocusCallback.onFocusChange(zoneIds, focusHoldersByZoneId);
}
void dump(IndentingPrintWriter writer) {
@@ -242,9 +250,11 @@
/**
* Called after a focus request or abandon call is handled.
*
- * @param audioZoneId ID of the zone where the change took place
- * @param focusHolders list of {@link AudioFocusInfo}s holding focus in specified audio zone
+ * @param audioZoneIds IDs of the zones where the changes took place
+ * @param focusHoldersByZoneId sparse array by zone ID, where each value is a list of
+ * {@link AudioFocusInfo}s holding focus in specified audio zone
*/
- void onFocusChange(int audioZoneId, @NonNull List<AudioFocusInfo> focusHolders);
+ void onFocusChange(int[] audioZoneIds,
+ @NonNull SparseArray<List<AudioFocusInfo>> focusHoldersByZoneId);
}
}
diff --git a/service/src/com/android/car/audio/hal/AudioControlWrapper.java b/service/src/com/android/car/audio/hal/AudioControlWrapper.java
index d42aa04..1866f33 100644
--- a/service/src/com/android/car/audio/hal/AudioControlWrapper.java
+++ b/service/src/com/android/car/audio/hal/AudioControlWrapper.java
@@ -103,9 +103,10 @@
* Notifies HAL of changes in usages holding focus and the corresponding ducking changes for a
* given zone.
*
- * @param carDuckingInfo information about focus and addresses to duck to relay to the HAL.
+ * @param carDuckingInfos list of information about focus and addresses to duck for each
+ * impacted zone to relay to the HAL.
*/
- void onDevicesToDuckChange(@NonNull CarDuckingInfo carDuckingInfo);
+ void onDevicesToDuckChange(@NonNull List<CarDuckingInfo> carDuckingInfos);
/**
* Notifies HAL of changes in muting changes for all audio zones.
diff --git a/service/src/com/android/car/audio/hal/AudioControlWrapperAidl.java b/service/src/com/android/car/audio/hal/AudioControlWrapperAidl.java
index b985407..4953f97 100644
--- a/service/src/com/android/car/audio/hal/AudioControlWrapperAidl.java
+++ b/service/src/com/android/car/audio/hal/AudioControlWrapperAidl.java
@@ -142,15 +142,18 @@
}
@Override
- public void onDevicesToDuckChange(@NonNull CarDuckingInfo carDuckingInfo) {
- Objects.requireNonNull(carDuckingInfo);
- DuckingInfo duckingInfo = carDuckingInfo.generateDuckingInfo();
+ public void onDevicesToDuckChange(@NonNull List<CarDuckingInfo> carDuckingInfos) {
+ Objects.requireNonNull(carDuckingInfos);
+ DuckingInfo[] duckingInfos = new DuckingInfo[carDuckingInfos.size()];
+ for (int i = 0; i < carDuckingInfos.size(); i++) {
+ CarDuckingInfo info = Objects.requireNonNull(carDuckingInfos.get(i));
+ duckingInfos[i] = info.generateDuckingInfo();
+ }
try {
- mAudioControl.onDevicesToDuckChange(new DuckingInfo[] {duckingInfo});
+ mAudioControl.onDevicesToDuckChange(duckingInfos);
} catch (RemoteException e) {
- Slog.e(TAG, "onDevicesToDuckChange for zone " + carDuckingInfo.mZoneId
- + " failed", e);
+ Slog.e(TAG, "onDevicesToDuckChange failed", e);
}
}
diff --git a/service/src/com/android/car/audio/hal/AudioControlWrapperV1.java b/service/src/com/android/car/audio/hal/AudioControlWrapperV1.java
index 9a0fa28..fd959cf 100644
--- a/service/src/com/android/car/audio/hal/AudioControlWrapperV1.java
+++ b/service/src/com/android/car/audio/hal/AudioControlWrapperV1.java
@@ -108,7 +108,7 @@
}
@Override
- public void onDevicesToDuckChange(CarDuckingInfo carDuckingInfo) {
+ public void onDevicesToDuckChange(List<CarDuckingInfo> carDuckingInfos) {
throw new UnsupportedOperationException("HAL ducking is unsupported for IAudioControl@1.0");
}
diff --git a/service/src/com/android/car/audio/hal/AudioControlWrapperV2.java b/service/src/com/android/car/audio/hal/AudioControlWrapperV2.java
index d99a22f..5354f23 100644
--- a/service/src/com/android/car/audio/hal/AudioControlWrapperV2.java
+++ b/service/src/com/android/car/audio/hal/AudioControlWrapperV2.java
@@ -140,7 +140,7 @@
}
@Override
- public void onDevicesToDuckChange(CarDuckingInfo carDuckingInfo) {
+ public void onDevicesToDuckChange(List<CarDuckingInfo> carDuckingInfos) {
throw new UnsupportedOperationException("HAL ducking is unsupported for IAudioControl@2.0");
}
diff --git a/service/src/com/android/car/garagemode/GarageMode.java b/service/src/com/android/car/garagemode/GarageMode.java
index 3809ab0..f315dca 100644
--- a/service/src/com/android/car/garagemode/GarageMode.java
+++ b/service/src/com/android/car/garagemode/GarageMode.java
@@ -134,8 +134,8 @@
private final Runnable mStartBackgroundUsers = new Runnable() {
@Override
public void run() {
- ArrayList<Integer> startedUsers =
- CarLocalServices.getService(CarUserService.class).startAllBackgroundUsers();
+ ArrayList<Integer> startedUsers = CarLocalServices.getService(CarUserService.class)
+ .startAllBackgroundUsersInGarageMode();
Slogf.i(TAG, "Started background user during garage mode: %s", startedUsers);
synchronized (mLock) {
// Stop stopping background users if there is any users left from last Garage mode,
@@ -157,8 +157,8 @@
if (numberOfIdleJobsRunning() == 0) { // all jobs done or stopped.
// Keep user until job scheduling is stopped. Otherwise, it can crash jobs.
if (userToStop != UserHandle.USER_SYSTEM) {
- CarLocalServices.getService(CarUserService.class).stopBackgroundUser(
- userToStop);
+ CarLocalServices.getService(CarUserService.class)
+ .stopBackgroundUserInGagageMode(userToStop);
Slogf.i(TAG, "Stopping background user:%d remaining users:%d", userToStop,
mStartedBackgroundUsers.size() - 1);
}
diff --git a/service/src/com/android/car/power/PolicyReader.java b/service/src/com/android/car/power/PolicyReader.java
index 9daabc0..afd8648 100644
--- a/service/src/com/android/car/power/PolicyReader.java
+++ b/service/src/com/android/car/power/PolicyReader.java
@@ -81,7 +81,7 @@
static final int INVALID_POWER_STATE = -1;
private static final String TAG = CarLog.tagFor(PolicyReader.class);
- private static final String VENDOR_POLICY_PATH = "/vendor/etc/car/power_policy.xml";
+ private static final String VENDOR_POLICY_PATH = "/vendor/etc/automotive/power_policy.xml";
private static final String NAMESPACE = null;
private static final Set<String> VALID_VERSIONS = new ArraySet<>(Arrays.asList("1.0"));
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index 0e8e9f2..de5739a 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -236,6 +236,15 @@
private boolean mUxRestricted;
/**
+ * If {@code false}, garage mode operations (background users start at garage mode entry and
+ * background users stop at garage mode exit) will be skipped. Controlled using car shell
+ * command {@code adb shell set-start-bg-users-on-garage-mode [true|false]}
+ * Purpose: Garage mode testing and simulation
+ */
+ @GuardedBy("mLockUser")
+ private boolean mStartBackgroundUsersOnGarageMode = true;
+
+ /**
* Callback to notify {@code CarServiceHelper} about driving safety changes (through
* {@link ICarServiceHelper#setSafetyMode(boolean).
*
@@ -357,6 +366,8 @@
writer.println("FailedToCreateUserIds: " + mFailedToCreateUserIds);
}
writer.printf("Is UX restricted: %b\n", mUxRestricted);
+ writer.printf("Start Background Users On Garage Mode=%s\n",
+ mStartBackgroundUsersOnGarageMode);
}
writer.println("SwitchGuestUserBeforeSleep: " + mSwitchGuestUserBeforeSleep);
@@ -1904,7 +1915,15 @@
* @return list of background users started successfully.
*/
@NonNull
- public ArrayList<Integer> startAllBackgroundUsers() {
+ public ArrayList<Integer> startAllBackgroundUsersInGarageMode() {
+ synchronized (mLockUser) {
+ if (!mStartBackgroundUsersOnGarageMode) {
+ Slogf.i(TAG, "Background users are not started as mStartBackgroundUsersOnGarageMode"
+ + " is false.");
+ return new ArrayList<>();
+ }
+ }
+
ArrayList<Integer> users;
synchronized (mLockUser) {
users = new ArrayList<>(mBackgroundUsersToRestart);
@@ -2000,11 +2019,28 @@
}
/**
+ * Sets boolean to control background user operations during garage mode.
+ */
+ public void setStartBackgroundUsersOnGarageMode(boolean enable) {
+ synchronized (mLockUser) {
+ mStartBackgroundUsersOnGarageMode = enable;
+ }
+ }
+
+ /**
* Stops a background user.
*
* @return whether stopping succeeds.
*/
- public boolean stopBackgroundUser(@UserIdInt int userId) {
+ public boolean stopBackgroundUserInGagageMode(@UserIdInt int userId) {
+ synchronized (mLockUser) {
+ if (!mStartBackgroundUsersOnGarageMode) {
+ Slogf.i(TAG, "Background users are not stopped as mStartBackgroundUsersOnGarageMode"
+ + " is false.");
+ return false;
+ }
+ }
+
@UserStopResult.Status int userStopStatus = stopBackgroundUserInternal(userId);
if (UserStopResult.isSuccess(userStopStatus)) {
// Remove the stopped user from the mBackgroundUserRestartedHere list.
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothConnectionPermissionChecker.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothConnectionPermissionChecker.java
new file mode 100644
index 0000000..3cbfe86
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothConnectionPermissionChecker.java
@@ -0,0 +1,51 @@
+/*
+ * 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 com.google.android.car.kitchensink.bluetooth;
+
+import android.Manifest;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.pm.PackageManager;
+
+import androidx.activity.result.contract.ActivityResultContracts;
+import androidx.fragment.app.Fragment;
+
+public final class BluetoothConnectionPermissionChecker {
+ private BluetoothConnectionPermissionChecker() {
+ }
+
+ static boolean isPermissionGranted(Activity mActivity) {
+ return mActivity.checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+
+ static void requestPermission(Fragment fragment, @Nullable Runnable isGrantedRunnable,
+ @Nullable Runnable isNotGrantedRunnable) {
+ fragment.registerForActivityResult(new ActivityResultContracts.RequestPermission(),
+ isGranted -> {
+ if (isGranted) {
+ if (isGrantedRunnable != null) {
+ isGrantedRunnable.run();
+ }
+ } else {
+ if (isNotGrantedRunnable != null) {
+ isNotGrantedRunnable.run();
+ }
+ }
+ }).launch(Manifest.permission.BLUETOOTH_CONNECT);
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothHeadsetFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothHeadsetFragment.java
index 619a2b3..45da3a2 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothHeadsetFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothHeadsetFragment.java
@@ -39,6 +39,7 @@
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
+import com.google.android.car.kitchensink.KitchenSinkActivity;
import com.google.android.car.kitchensink.R;
public class BluetoothHeadsetFragment extends Fragment {
@@ -86,13 +87,19 @@
mEndOutgoingCall = (Button) v.findViewById(R.id.bluetooth_end_outgoing_call);
mOutgoingPhoneNumber = (EditText) v.findViewById(R.id.bluetooth_outgoing_phone_number);
- // Pick a bluetooth device
- mDevicePicker.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- launchDevicePicker();
- }
- });
+ if (!BluetoothConnectionPermissionChecker.isPermissionGranted(
+ (KitchenSinkActivity) getHost())) {
+ BluetoothConnectionPermissionChecker.requestPermission(this,
+ this::setDevicePickerButtonClickable,
+ () -> {
+ setDevicePickerButtonUnclickable();
+ Toast.makeText(getContext(),
+ "Device picker can't run without BLUETOOTH_CONNECT permission. "
+ + "(You can change permissions in Settings.)",
+ Toast.LENGTH_SHORT).show();
+ }
+ );
+ }
// Connect profile
mConnect.setOnClickListener(new View.OnClickListener() {
@@ -177,6 +184,22 @@
return v;
}
+ private void setDevicePickerButtonClickable() {
+ mDevicePicker.setClickable(true);
+
+ // Pick a bluetooth device
+ mDevicePicker.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ launchDevicePicker();
+ }
+ });
+ }
+
+ private void setDevicePickerButtonUnclickable() {
+ mDevicePicker.setClickable(false);
+ }
+
void launchDevicePicker() {
IntentFilter filter = new IntentFilter();
filter.addAction(BluetoothDevicePicker.ACTION_DEVICE_SELECTED);
@@ -370,6 +393,13 @@
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mBluetoothAdapter.getProfileProxy(
getContext(), new ProfileServiceListener(), BluetoothProfile.HEADSET_CLIENT);
+
+ if (BluetoothConnectionPermissionChecker.isPermissionGranted(
+ (KitchenSinkActivity) getHost())) {
+ setDevicePickerButtonClickable();
+ } else {
+ setDevicePickerButtonUnclickable();
+ }
}
@Override
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java
index e998fd8..7bf8e7f 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java
@@ -128,6 +128,18 @@
@Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.sms_received, container, false);
mActivity = (KitchenSinkActivity) getHost();
+
+ if (!BluetoothConnectionPermissionChecker.isPermissionGranted(mActivity)) {
+ BluetoothConnectionPermissionChecker.requestPermission(this,
+ this::registerMapServiceListenerAndNotificationReceiver,
+ () -> {
+ Toast.makeText(getContext(),
+ "Connected devices can't be detected without BLUETOOTH_CONNECT "
+ + "permission. (You can change permissions in Settings.)",
+ Toast.LENGTH_SHORT).show();
+ });
+ }
+
Button reply = (Button) v.findViewById(R.id.reply);
Button checkMessages = (Button) v.findViewById(R.id.check_messages);
mBluetoothDevice = (TextView) v.findViewById(R.id.bluetoothDevice);
@@ -208,7 +220,6 @@
}
});
- mTransmissionStatusReceiver = new NotificationReceiver();
return v;
}
@@ -234,22 +245,19 @@
public void onResume() {
super.onResume();
- mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
- mBluetoothAdapter.getProfileProxy(getContext(), new MapServiceListener(),
- BluetoothProfile.MAP_CLIENT);
-
- IntentFilter intentFilter = new IntentFilter();
- intentFilter.addAction(ACTION_MESSAGE_SENT_SUCCESSFULLY);
- intentFilter.addAction(ACTION_MESSAGE_DELIVERED_SUCCESSFULLY);
- intentFilter.addAction(BluetoothMapClient.ACTION_MESSAGE_RECEIVED);
- intentFilter.addAction(BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED);
- getContext().registerReceiver(mTransmissionStatusReceiver, intentFilter);
+ if (BluetoothConnectionPermissionChecker.isPermissionGranted(mActivity)) {
+ registerMapServiceListenerAndNotificationReceiver();
+ }
}
@Override
public void onPause() {
super.onPause();
- getContext().unregisterReceiver(mTransmissionStatusReceiver);
+
+ if (mTransmissionStatusReceiver != null) {
+ getContext().unregisterReceiver(mTransmissionStatusReceiver);
+ mTransmissionStatusReceiver = null;
+ }
}
private void getMessages() {
@@ -270,6 +278,20 @@
}
}
+ private void registerMapServiceListenerAndNotificationReceiver() {
+ mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ mBluetoothAdapter.getProfileProxy(getContext(), new MapServiceListener(),
+ BluetoothProfile.MAP_CLIENT);
+
+ mTransmissionStatusReceiver = new NotificationReceiver();
+ IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(ACTION_MESSAGE_SENT_SUCCESSFULLY);
+ intentFilter.addAction(ACTION_MESSAGE_DELIVERED_SUCCESSFULLY);
+ intentFilter.addAction(BluetoothMapClient.ACTION_MESSAGE_RECEIVED);
+ intentFilter.addAction(BluetoothMapClient.ACTION_CONNECTION_STATE_CHANGED);
+ getContext().registerReceiver(mTransmissionStatusReceiver, intentFilter);
+ }
+
private void sendNewMsgOnClick(int msgType) {
String messageToSend = "";
switch (msgType) {
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java b/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
index 799e569..4685b62 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
@@ -143,21 +143,30 @@
}
}
- protected static void suspendToRamAndResume() throws Exception {
+ protected static void suspendToRamAndResume(boolean disableBackgroundUsersStart)
+ throws Exception {
Log.d(TAG, "Emulate suspend to RAM and resume");
- PowerManager powerManager = sContext.getSystemService(PowerManager.class);
- // clear log
- runShellCommand("logcat -b all -c");
- runShellCommand("cmd car_service suspend");
- // Check for suspend success
- waitUntil("screen is still on after suspend",
- SUSPEND_TIMEOUT_MS, () -> !powerManager.isScreenOn());
+ try {
+ if (disableBackgroundUsersStart) {
+ runShellCommand("cmd car_service set-start-bg-users-on-garage-mode false");
+ }
- // Force turn off garage mode
- runShellCommand("cmd car_service garage-mode off");
- runShellCommand("cmd car_service resume");
- waitForLogcatMessage("logcat -b events", "car_user_svc_initial_user_info_req_complete: "
- + InitialUserInfoRequestType.RESUME, 60_000);
+ PowerManager powerManager = sContext.getSystemService(PowerManager.class);
+ // clear log
+ runShellCommand("logcat -b all -c");
+ runShellCommand("cmd car_service suspend");
+ // Check for suspend success
+ waitUntil("screen is still on after suspend",
+ SUSPEND_TIMEOUT_MS, () -> !powerManager.isScreenOn());
+
+ // Force turn off garage mode
+ runShellCommand("cmd car_service garage-mode off");
+ runShellCommand("cmd car_service resume");
+ waitForLogcatMessage("logcat -b events", "car_user_svc_initial_user_info_req_complete: "
+ + InitialUserInfoRequestType.RESUME, 60_000);
+ } catch (Exception e) {
+ runShellCommand("cmd car_service set-start-bg-users-on-garage-mode true");
+ }
}
/**
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarDevicePolicyManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarDevicePolicyManagerTest.java
index 160e3fb..9b4f293 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarDevicePolicyManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarDevicePolicyManagerTest.java
@@ -176,12 +176,13 @@
}
@Test
+ @FlakyTest(bugId = 190417819)
public void testLockNow_safe() throws Exception {
lockNowTest(/* safe= */ true);
}
@Test
- @FlakyTest(bugId = 178475817)
+ @FlakyTest(bugId = 190417819)
public void testLockNow_unsafe() throws Exception {
lockNowTest(/* safe= */ false);
}
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarMultiUserTestBase.java b/tests/android_car_api_test/src/android/car/apitest/CarMultiUserTestBase.java
index cfa0d8a..eb331f5 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarMultiUserTestBase.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarMultiUserTestBase.java
@@ -16,6 +16,8 @@
package android.car.apitest;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
+
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -26,6 +28,7 @@
import android.app.ActivityManager;
import android.car.Car;
import android.car.test.util.AndroidHelper;
+import android.car.testapi.BlockingUserLifecycleListener;
import android.car.user.CarUserManager;
import android.car.user.UserCreationResult;
import android.car.user.UserRemovalResult;
@@ -58,8 +61,8 @@
private static final String TAG = CarMultiUserTestBase.class.getSimpleName();
- private static final long REMOVE_USER_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10_000);
- private static final long SWITCH_USER_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(10_000);
+ private static final long REMOVE_USER_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(30_000);
+ private static final long SWITCH_USER_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(30_000);
private static final String PROP_STOP_BG_USERS_ON_SWITCH = "fw.stop_bg_users_on_switch";
@@ -83,6 +86,9 @@
public final void setMultiUserFixtures() throws Exception {
Log.d(TAG, "setMultiUserFixtures() for " + mTestName.getMethodName());
+ // Make sure user doesn't stop on switch (otherwise test process would crash)
+ setSystemProperty(PROP_STOP_BG_USERS_ON_SWITCH, "0");
+
mCarUserManager = getCarService(Car.CAR_USER_SERVICE);
mUserManager = getContext().getSystemService(UserManager.class);
@@ -94,9 +100,6 @@
}
}, filter);
- // Make sure user doesn't stop on switch (otherwise test process would crash)
- setSystemProperty(PROP_STOP_BG_USERS_ON_SWITCH, "0");
-
List<UserInfo> users = mUserManager.getAliveUsers();
// Set current user
@@ -142,18 +145,15 @@
mSetupFinished = true;
}
- @After
public final void resetStopUserOnSwitch() throws Exception {
- if (!mSetupFinished) return;
-
setSystemProperty(PROP_STOP_BG_USERS_ON_SWITCH, "-1");
}
@After
public final void cleanupUserState() throws Exception {
- if (!mSetupFinished) return;
-
try {
+ if (!mSetupFinished) return;
+
int currentUserId = getCurrentUserId();
int initialUserId = mInitialUser.id;
if (currentUserId != initialUserId) {
@@ -177,6 +177,9 @@
Log.e(TAG, "Caught exception on " + getTestName()
+ " disconnectCarAndCleanupUserState()", e);
}
+ finally {
+ resetStopUserOnSwitch();
+ }
}
@UserIdInt
@@ -252,14 +255,36 @@
}
protected void switchUser(@UserIdInt int userId) throws Exception {
- Log.i(TAG, "Switching to user " + userId + " using CarUserManager");
+ boolean waitForUserSwitchToComplete = true;
+ // If current user is the target user, no life cycle event is expected.
+ if (getCurrentUserId() == userId) waitForUserSwitchToComplete = false;
- AsyncFuture<UserSwitchResult> future = mCarUserManager.switchUser(userId);
- UserSwitchResult result = future.get(SWITCH_USER_TIMEOUT_MS, TimeUnit.MILLISECONDS);
- Log.d(TAG, "Result: " + result);
+ Log.d(TAG, "registering listener for user switching");
+ BlockingUserLifecycleListener listener = BlockingUserLifecycleListener
+ .forSpecificEvents()
+ .forUser(userId)
+ .setTimeout(SWITCH_USER_TIMEOUT_MS)
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING)
+ .build();
+ mCarUserManager.addListener(Runnable::run, listener);
- assertWithMessage("User %s switched in %sms. Result: %s", userId, SWITCH_USER_TIMEOUT_MS,
- result).that(result.isSuccess()).isTrue();
+ try {
+ Log.i(TAG, "Switching to user " + userId + " using CarUserManager");
+ AsyncFuture<UserSwitchResult> future = mCarUserManager.switchUser(userId);
+ UserSwitchResult result = future.get(SWITCH_USER_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ Log.d(TAG, "Result: " + result);
+
+ assertWithMessage("User %s switched in %sms. Result: %s", userId,
+ SWITCH_USER_TIMEOUT_MS, result).that(result.isSuccess()).isTrue();
+
+ if (waitForUserSwitchToComplete) {
+ listener.waitForEvents();
+ }
+ } finally {
+ mCarUserManager.removeListener(listener);
+ }
+
+ Log.d(TAG, "User switch complete. User id: " + userId);
}
protected void removeUser(@UserIdInt int userId) {
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarUserManagerLifeCycleTest.java b/tests/android_car_api_test/src/android/car/apitest/CarUserManagerLifeCycleTest.java
new file mode 100644
index 0000000..b643065
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/CarUserManagerLifeCycleTest.java
@@ -0,0 +1,187 @@
+/*
+ * 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 android.car.test.util.UserTestingHelper.setMaxSupportedUsers;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPED;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPING;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED;
+import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.annotation.UserIdInt;
+import android.app.ActivityManager;
+import android.app.IActivityManager;
+import android.car.testapi.BlockingUserLifecycleListener;
+import android.car.user.CarUserManager.UserLifecycleEvent;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.util.Log;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+// DO NOT ADD ANY TEST TO THIS CLASS
+// This class will have only one test testLifecycleListener.
+public final class CarUserManagerLifeCycleTest extends CarMultiUserTestBase {
+
+ private static final String TAG = CarUserManagerLifeCycleTest.class.getSimpleName();
+
+ private static final int PIN = 2345;
+
+ private static final int SWITCH_TIMEOUT_MS = 70_000;
+ // A large stop timeout is required as sometimes stop user broadcast takes a significantly
+ // long time to complete. This happen when there are multiple users starting/stopping in
+ // background which is the case in this test class.
+ private static final int STOP_TIMEOUT_MS = 600_000;
+
+ /**
+ * Stopping the user takes a while, even when calling force stop - change it to {@code false}
+ * if {@code testLifecycleListener} becomes flaky.
+ */
+ private static final boolean TEST_STOP_EVENTS = true;
+
+ private static final int sMaxNumberUsersBefore = UserManager.getMaxSupportedUsers();
+ private static boolean sChangedMaxNumberUsers;
+
+ @BeforeClass
+ public static void setupMaxNumberOfUsers() {
+ int requiredUsers = 3; // system user, current user, 1 extra user
+ if (sMaxNumberUsersBefore < requiredUsers) {
+ sChangedMaxNumberUsers = true;
+ Log.i(TAG, "Increasing maximing number of users from " + sMaxNumberUsersBefore + " to "
+ + requiredUsers);
+ setMaxSupportedUsers(requiredUsers);
+ }
+ }
+
+ @AfterClass
+ public static void restoreMaxNumberOfUsers() {
+ if (sChangedMaxNumberUsers) {
+ Log.i(TAG, "Restoring maximum number of users to " + sMaxNumberUsersBefore);
+ setMaxSupportedUsers(sMaxNumberUsersBefore);
+ }
+ }
+
+ @Test(timeout = 600_000)
+ public void testLifecycleListener() throws Exception {
+ int initialUserId = getCurrentUserId();
+ int newUserId = createUser().id;
+
+ BlockingUserLifecycleListener startListener = BlockingUserLifecycleListener
+ .forSpecificEvents()
+ .forUser(newUserId)
+ .setTimeout(SWITCH_TIMEOUT_MS)
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING)
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING)
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING)
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)
+ .build();
+
+ Log.d(TAG, "registering start listener: " + startListener);
+ AtomicBoolean executedRef = new AtomicBoolean();
+
+ Executor mExecutor = (r) -> {
+ executedRef.set(true);
+ r.run();
+ };
+ mCarUserManager.addListener(mExecutor, startListener);
+
+ // Switch while listener is registered
+ switchUser(newUserId);
+
+ List<UserLifecycleEvent> startEvents = startListener.waitForEvents();
+ Log.d(TAG, "Received start events: " + startEvents);
+
+ // Make sure listener callback was executed in the proper threaqd
+ assertWithMessage("executed on executor").that(executedRef.get()).isTrue();
+
+ // Assert user ids
+ List<UserLifecycleEvent> expectedEvents = startListener.waitForEvents();
+ Log.d(TAG, "Received expected events: " + expectedEvents);
+ for (UserLifecycleEvent event : expectedEvents) {
+ assertWithMessage("userId on event %s", event)
+ .that(event.getUserId()).isEqualTo(newUserId);
+ assertWithMessage("userHandle on event %s", event)
+ .that(event.getUserHandle().getIdentifier()).isEqualTo(newUserId);
+ if (event.getEventType() == USER_LIFECYCLE_EVENT_TYPE_SWITCHING) {
+ assertWithMessage("previousUserId on event %s", event)
+ .that(event.getPreviousUserId()).isEqualTo(initialUserId);
+ assertWithMessage("previousUserHandle on event %s", event)
+ .that(event.getPreviousUserHandle().getIdentifier()).isEqualTo(initialUserId);
+ }
+ }
+
+ Log.d(TAG, "unregistering start listener: " + startListener);
+ mCarUserManager.removeListener(startListener);
+
+ BlockingUserLifecycleListener stopListener = BlockingUserLifecycleListener
+ .forSpecificEvents()
+ .forUser(newUserId)
+ .setTimeout(STOP_TIMEOUT_MS)
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPING)
+ .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPED)
+ .build();
+
+ Log.d(TAG, "registering stop listener: " + stopListener);
+ mCarUserManager.addListener(mExecutor, stopListener);
+
+ // Switch back to the initial user
+ switchUser(initialUserId);
+
+ if (TEST_STOP_EVENTS) {
+ // Must force stop the user, otherwise it can take minutes for its process to finish
+ forceStopUser(newUserId);
+
+ List<UserLifecycleEvent> stopEvents = stopListener.waitForEvents();
+ Log.d(TAG, "stopEvents: " + stopEvents + "; all events on stop listener: "
+ + stopListener.getAllReceivedEvents());
+
+ // Assert user ids
+ for (UserLifecycleEvent event : stopEvents) {
+ assertWithMessage("userId on %s", event)
+ .that(event.getUserId()).isEqualTo(newUserId);
+ assertWithMessage("wrong userHandle on %s", event)
+ .that(event.getUserHandle().getIdentifier()).isEqualTo(newUserId);
+ }
+ } else {
+ Log.w(TAG, "NOT testing user stop events");
+ }
+
+ // Make sure unregistered listener didn't receive any more events
+ List<UserLifecycleEvent> allStartEvents = startListener.getAllReceivedEvents();
+ Log.d(TAG, "All start events: " + startEvents);
+ assertThat(allStartEvents).containsAtLeastElementsIn(startEvents).inOrder();
+
+ Log.d(TAG, "unregistering stop listener: " + stopListener);
+ mCarUserManager.removeListener(stopListener);
+ }
+
+ private static void forceStopUser(@UserIdInt int userId) throws RemoteException {
+ Log.i(TAG, "Force-stopping user " + userId);
+ IActivityManager am = ActivityManager.getService();
+ am.stopUser(userId, /* force=*/ true, /* listener= */ null);
+ }
+}
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java
index 0cdca04..d66e3dd 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarUserManagerTest.java
@@ -18,36 +18,22 @@
import static android.car.test.util.UserTestingHelper.clearUserLockCredentials;
import static android.car.test.util.UserTestingHelper.setMaxSupportedUsers;
import static android.car.test.util.UserTestingHelper.setUserLockCredentials;
-import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING;
-import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPED;
-import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPING;
import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING;
import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED;
-import static android.car.user.CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING;
-import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
-import android.annotation.UserIdInt;
import android.app.ActivityManager;
-import android.app.IActivityManager;
import android.car.testapi.BlockingUserLifecycleListener;
import android.car.user.CarUserManager.UserLifecycleEvent;
import android.content.pm.UserInfo;
-import android.os.RemoteException;
import android.os.UserManager;
import android.util.Log;
-import androidx.test.filters.FlakyTest;
-
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
-import java.util.List;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
-
public final class CarUserManagerTest extends CarMultiUserTestBase {
private static final String TAG = CarUserManagerTest.class.getSimpleName();
@@ -55,13 +41,6 @@
private static final int PIN = 2345;
private static final int SWITCH_TIMEOUT_MS = 70_000;
- private static final int STOP_TIMEOUT_MS = 300_000;
-
- /**
- * Stopping the user takes a while, even when calling force stop - change it to {@code false}
- * if {@code testLifecycleListener} becomes flaky.
- */
- private static final boolean TEST_STOP_EVENTS = true;
private static final int sMaxNumberUsersBefore = UserManager.getMaxSupportedUsers();
private static boolean sChangedMaxNumberUsers;
@@ -85,106 +64,10 @@
}
}
- @FlakyTest //TODO(b/183519890): STOPSHIP switch is getting wrong previous id
- @Test
- public void testLifecycleListener() throws Exception {
- int initialUserId = getCurrentUserId();
- int newUserId = createUser().id;
-
- BlockingUserLifecycleListener startListener = BlockingUserLifecycleListener
- .forSpecificEvents()
- .forUser(newUserId)
- .setTimeout(SWITCH_TIMEOUT_MS)
- .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STARTING)
- .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_SWITCHING)
- .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKING)
- .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_UNLOCKED)
- .build();
-
- Log.d(TAG, "registering start listener: " + startListener);
- AtomicBoolean executedRef = new AtomicBoolean();
-
- Executor mExecutor = (r) -> {
- executedRef.set(true);
- r.run();
- };
- mCarUserManager.addListener(mExecutor, startListener);
-
- // Switch while listener is registered
- switchUser(newUserId);
-
- List<UserLifecycleEvent> startEvents = startListener.waitForEvents();
- Log.d(TAG, "Received start events: " + startEvents);
-
- // Make sure listener callback was executed in the proper threaqd
- assertWithMessage("executed on executor").that(executedRef.get()).isTrue();
-
- // Assert user ids
- List<UserLifecycleEvent> expectedEvents = startListener.waitForEvents();
- Log.d(TAG, "Received expected events: " + expectedEvents);
- for (UserLifecycleEvent event : expectedEvents) {
- assertWithMessage("userId on event %s", event)
- .that(event.getUserId()).isEqualTo(newUserId);
- assertWithMessage("userHandle on event %s", event)
- .that(event.getUserHandle().getIdentifier()).isEqualTo(newUserId);
- if (event.getEventType() == USER_LIFECYCLE_EVENT_TYPE_SWITCHING) {
- assertWithMessage("previousUserId on event %s", event)
- .that(event.getPreviousUserId()).isEqualTo(initialUserId);
- assertWithMessage("previousUserHandle on event %s", event)
- .that(event.getPreviousUserHandle().getIdentifier()).isEqualTo(initialUserId);
- }
- }
-
- Log.d(TAG, "unregistering start listener: " + startListener);
- mCarUserManager.removeListener(startListener);
-
- BlockingUserLifecycleListener stopListener = BlockingUserLifecycleListener
- .forSpecificEvents()
- .forUser(newUserId)
- .setTimeout(STOP_TIMEOUT_MS)
- .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPING)
- .addExpectedEvent(USER_LIFECYCLE_EVENT_TYPE_STOPPED)
- .build();
-
- Log.d(TAG, "registering stop listener: " + stopListener);
- mCarUserManager.addListener(mExecutor, stopListener);
-
- // Switch back to the initial user
- switchUser(initialUserId);
-
- if (TEST_STOP_EVENTS) {
- // Must force stop the user, otherwise it can take minutes for its process to finish
- forceStopUser(newUserId);
-
- List<UserLifecycleEvent> stopEvents = stopListener.waitForEvents();
- Log.d(TAG, "stopEvents: " + stopEvents + "; all events on stop listener: "
- + stopListener.getAllReceivedEvents());
-
- // Assert user ids
- for (UserLifecycleEvent event : stopEvents) {
- assertWithMessage("userId on %s", event)
- .that(event.getUserId()).isEqualTo(newUserId);
- assertWithMessage("wrong userHandle on %s", event)
- .that(event.getUserHandle().getIdentifier()).isEqualTo(newUserId);
- }
- } else {
- Log.w(TAG, "NOT testing user stop events");
- }
-
- // Make sure unregistered listener didn't receive any more events
- List<UserLifecycleEvent> allStartEvents = startListener.getAllReceivedEvents();
- Log.d(TAG, "All start events: " + startEvents);
- assertThat(allStartEvents).containsAtLeastElementsIn(startEvents).inOrder();
-
- Log.d(TAG, "unregistering stop listener: " + stopListener);
- mCarUserManager.removeListener(stopListener);
- }
-
/**
* Tests resume behavior when current user is ephemeral guest, a new guest user should be
* created and switched to.
*/
- @FlakyTest //TODO(b/183519890): STOPSHIP often fails due to missing events or crashes
@Test
public void testGuestUserResumeToNewGuestUser() throws Exception {
// Create new guest user
@@ -213,7 +96,7 @@
mCarUserManager.addListener(Runnable::run, listener2);
// Emulate suspend to RAM
- suspendToRamAndResume();
+ suspendToRamAndResume(/* disableBackgroundUsersStart=*/ true);
UserLifecycleEvent event = listener2.waitForEvents().get(0);
int newGuestId = event.getUserId();
@@ -233,7 +116,6 @@
* Tests resume behavior when current user is guest but with secured lock screen,
* resume to same guest user.
*/
- @FlakyTest //TODO(b/183519890): STOPSHIP often fails due to missing events or crashes
@Test
public void testSecuredGuestUserResumeToSameUser() throws Exception {
// Create new guest user
@@ -257,7 +139,7 @@
setUserLockCredentials(guestUserId, PIN);
try {
// Emulate suspend to RAM
- suspendToRamAndResume();
+ suspendToRamAndResume(/* disableBackgroundUsersStart=*/ true);
assertWithMessage("current user remains guest user (%s)", guestUser)
.that(getCurrentUserId()).isEqualTo(guestUserId);
@@ -269,7 +151,6 @@
/**
* Tests resume behavior when current user is persistent user.
*/
- @FlakyTest // TODO(b/183519890): STOPSHIP often fails due to missing events or crashes
@Test
public void testPersistentUserResumeToUser() throws Exception {
int newUserId = createUser().id;
@@ -284,7 +165,7 @@
listener.waitForEvents();
// Emulate suspend to RAM
- suspendToRamAndResume();
+ suspendToRamAndResume(/* disableBackgroundUsersStart=*/ true);
listener.waitForEvents();
assertWithMessage("current user remains new user (%s)", newUserId)
@@ -292,10 +173,4 @@
mCarUserManager.removeListener(listener);
}
-
- private static void forceStopUser(@UserIdInt int userId) throws RemoteException {
- Log.i(TAG, "Force-stopping user " + userId);
- IActivityManager am = ActivityManager.getService();
- am.stopUser(userId, /* force=*/ true, /* listener= */ null);
- }
}
diff --git a/tests/carservice_test/src/com/android/car/audio/CarZonesAudioFocusTest.java b/tests/carservice_test/src/com/android/car/audio/CarZonesAudioFocusTest.java
index 484197c..8306420 100644
--- a/tests/carservice_test/src/com/android/car/audio/CarZonesAudioFocusTest.java
+++ b/tests/carservice_test/src/com/android/car/audio/CarZonesAudioFocusTest.java
@@ -285,11 +285,12 @@
carZonesAudioFocus.onAudioFocusRequest(audioFocusInfo, AUDIOFOCUS_REQUEST_GRANTED);
- ArgumentCaptor<List<AudioFocusInfo>> focusHoldersCaptor = ArgumentCaptor.forClass(
- List.class);
- verify(mMockCarFocusCallback).onFocusChange(eq(PRIMARY_ZONE_ID),
+ ArgumentCaptor<SparseArray<List<AudioFocusInfo>>> focusHoldersCaptor =
+ ArgumentCaptor.forClass(SparseArray.class);
+ verify(mMockCarFocusCallback).onFocusChange(eq(new int[]{PRIMARY_ZONE_ID}),
focusHoldersCaptor.capture());
- assertThat(focusHoldersCaptor.getValue()).containsExactly(audioFocusInfo);
+ assertThat(focusHoldersCaptor.getValue().get(PRIMARY_ZONE_ID))
+ .containsExactly(audioFocusInfo);
}
@Test
@@ -299,11 +300,11 @@
carZonesAudioFocus.onAudioFocusAbandon(audioFocusInfo);
- ArgumentCaptor<List<AudioFocusInfo>> focusHoldersCaptor = ArgumentCaptor.forClass(
- List.class);
- verify(mMockCarFocusCallback).onFocusChange(eq(PRIMARY_ZONE_ID),
+ ArgumentCaptor<SparseArray<List<AudioFocusInfo>>> focusHoldersCaptor =
+ ArgumentCaptor.forClass(SparseArray.class);
+ verify(mMockCarFocusCallback).onFocusChange(eq(new int[]{PRIMARY_ZONE_ID}),
focusHoldersCaptor.capture());
- assertThat(focusHoldersCaptor.getValue()).isEmpty();
+ assertThat(focusHoldersCaptor.getValue().get(PRIMARY_ZONE_ID)).isEmpty();
}
private AudioFocusInfo generateMediaRequestForPrimaryZone() {
diff --git a/tests/carservice_test/src/com/android/car/garagemode/ControllerTest.java b/tests/carservice_test/src/com/android/car/garagemode/ControllerTest.java
index b1f9e0c..ca3598a 100644
--- a/tests/carservice_test/src/com/android/car/garagemode/ControllerTest.java
+++ b/tests/carservice_test/src/com/android/car/garagemode/ControllerTest.java
@@ -110,7 +110,8 @@
CarLocalServices.addService(CarUserService.class, mCarUserServiceMock);
CarLocalServices.removeServiceForTest(SystemInterface.class);
CarLocalServices.addService(SystemInterface.class, mSystemInterfaceMock);
- doReturn(new ArrayList<Integer>()).when(mCarUserServiceMock).startAllBackgroundUsers();
+ doReturn(new ArrayList<Integer>()).when(mCarUserServiceMock)
+ .startAllBackgroundUsersInGarageMode();
doNothing().when(mSystemInterfaceMock)
.sendBroadcastAsUser(any(Intent.class), any(UserHandle.class));
}
diff --git a/tests/carservice_test/src/com/android/car/garagemode/GarageModeTest.java b/tests/carservice_test/src/com/android/car/garagemode/GarageModeTest.java
index 5ef4086..499a95f 100644
--- a/tests/carservice_test/src/com/android/car/garagemode/GarageModeTest.java
+++ b/tests/carservice_test/src/com/android/car/garagemode/GarageModeTest.java
@@ -96,7 +96,8 @@
@Test
public void test_backgroundUsersStopedOnGarageModeCancel() throws Exception {
ArrayList<Integer> userToStartInBackground = new ArrayList<>(Arrays.asList(101, 102, 103));
- when(mCarUserService.startAllBackgroundUsers()).thenReturn(userToStartInBackground);
+ when(mCarUserService.startAllBackgroundUsersInGarageMode())
+ .thenReturn(userToStartInBackground);
mGarageMode.enterGarageMode(/* future= */ null);
CountDownLatch latch = new CountDownLatch(3); // 3 for three users
mockCarUserServiceStopUserCall(getEventListener(), latch);
@@ -104,10 +105,10 @@
mGarageMode.cancel();
waitForHandlerThreadToFinish(latch);
- verify(mCarUserService).startAllBackgroundUsers();
- verify(mCarUserService).stopBackgroundUser(101);
- verify(mCarUserService).stopBackgroundUser(102);
- verify(mCarUserService).stopBackgroundUser(103);
+ verify(mCarUserService).startAllBackgroundUsersInGarageMode();
+ verify(mCarUserService).stopBackgroundUserInGagageMode(101);
+ verify(mCarUserService).stopBackgroundUserInGagageMode(102);
+ verify(mCarUserService).stopBackgroundUserInGagageMode(103);
}
@Test
@@ -140,7 +141,7 @@
doAnswer(inv -> {
latch.countDown();
return userToStartInBackground;
- }).when(mCarUserService).startAllBackgroundUsers();
+ }).when(mCarUserService).startAllBackgroundUsersInGarageMode();
return latch;
}
@@ -161,7 +162,7 @@
listener.onEvent(new UserLifecycleEvent(
CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STOPPED, userId));
return null;
- }).when(mCarUserService).stopBackgroundUser(anyInt());
+ }).when(mCarUserService).stopBackgroundUserInGagageMode(anyInt());
}
}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioDeviceInfoTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioDeviceInfoTest.java
new file mode 100644
index 0000000..5373ceb
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioDeviceInfoTest.java
@@ -0,0 +1,286 @@
+/*
+ * 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 com.android.car.audio;
+
+import static android.media.AudioFormat.CHANNEL_OUT_MONO;
+import static android.media.AudioFormat.CHANNEL_OUT_QUAD;
+import static android.media.AudioFormat.CHANNEL_OUT_STEREO;
+import static android.media.AudioFormat.ENCODING_PCM_16BIT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.media.AudioDeviceInfo;
+import android.media.AudioDevicePort;
+import android.media.AudioGain;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class CarAudioDeviceInfoTest {
+
+ private static final String TEST_ADDRESS = "test address";
+ private static final int MIN_GAIN = 0;
+ private static final int MAX_GAIN = 100;
+ private static final int DEFAULT_GAIN = 50;
+ private static final int STEP_SIZE = 2;
+
+ @Test
+ public void constructor_requiresNonNullGain() {
+ AudioDeviceInfo audioDeviceInfo = mock(AudioDeviceInfo.class);
+ when(audioDeviceInfo.getPort()).thenReturn(null);
+
+ assertThrows(NullPointerException.class, () -> new CarAudioDeviceInfo(audioDeviceInfo));
+ }
+
+ @Test
+ public void constructor_requiresJointModeGain() {
+ AudioGain gainWithChannelMode = new GainBuilder().setMode(AudioGain.MODE_CHANNELS).build();
+ AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(
+ new AudioGain[]{gainWithChannelMode});
+
+ assertThrows(NullPointerException.class, () -> new CarAudioDeviceInfo(audioDeviceInfo));
+ }
+
+ @Test
+ public void constructor_requiresMaxGainLargerThanMin() {
+ AudioGain gainWithChannelMode = new GainBuilder().setMaxValue(10).setMinValue(20).build();
+ AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(
+ new AudioGain[]{gainWithChannelMode});
+
+ assertThrows(IllegalArgumentException.class, () -> new CarAudioDeviceInfo(audioDeviceInfo));
+ }
+
+ @Test
+ public void constructor_requiresDefaultGainLargerThanMin() {
+ AudioGain gainWithChannelMode = new GainBuilder().setDefaultValue(10).setMinValue(
+ 20).build();
+ AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(
+ new AudioGain[]{gainWithChannelMode});
+
+ assertThrows(IllegalArgumentException.class, () -> new CarAudioDeviceInfo(audioDeviceInfo));
+ }
+
+ @Test
+ public void constructor_requiresDefaultGainSmallerThanMax() {
+ AudioGain gainWithChannelMode = new GainBuilder().setDefaultValue(15).setMaxValue(
+ 10).build();
+ AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(
+ new AudioGain[]{gainWithChannelMode});
+
+ assertThrows(IllegalArgumentException.class, () -> new CarAudioDeviceInfo(audioDeviceInfo));
+ }
+
+ @Test
+ public void constructor_requiresGainStepSizeFactorOfRange() {
+ AudioGain gainWithChannelMode = new GainBuilder().setStepSize(7).build();
+ AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(
+ new AudioGain[]{gainWithChannelMode});
+
+ assertThrows(IllegalArgumentException.class, () -> new CarAudioDeviceInfo(audioDeviceInfo));
+ }
+
+ @Test
+ public void constructor_requiresGainStepSizeFactorOfRangeToDefault() {
+ AudioGain gainWithChannelMode = new GainBuilder().setStepSize(7).setMaxValue(98).build();
+ AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo(
+ new AudioGain[]{gainWithChannelMode});
+
+ assertThrows(IllegalArgumentException.class, () -> new CarAudioDeviceInfo(audioDeviceInfo));
+ }
+
+ @Test
+ public void getSampleRate_withMultipleSampleRates_returnsMax() {
+ AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+ int[] sampleRates = new int[]{48000, 96000, 16000, 8000};
+ when(audioDeviceInfo.getSampleRates()).thenReturn(sampleRates);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+ int sampleRate = info.getSampleRate();
+
+ assertThat(sampleRate).isEqualTo(96000);
+ }
+
+ @Test
+ public void getSampleRate_withNullSampleRate_returnsDefault() {
+ AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+ when(audioDeviceInfo.getSampleRates()).thenReturn(null);
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+ int sampleRate = info.getSampleRate();
+
+ assertThat(sampleRate).isEqualTo(CarAudioDeviceInfo.DEFAULT_SAMPLE_RATE);
+ }
+
+ @Test
+ public void getAudioDevicePort_returnsValueFromDeviceInfo() {
+ AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+ assertThat(info.getAudioDevicePort()).isEqualTo(audioDeviceInfo.getPort());
+ }
+
+ @Test
+ public void getAddress_returnsValueFromDeviceInfo() {
+ AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+ assertThat(info.getAddress()).isEqualTo(TEST_ADDRESS);
+ }
+
+ @Test
+ public void getMaxGain_returnsValueFromDeviceInfo() {
+ AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+ assertThat(info.getMaxGain()).isEqualTo(MAX_GAIN);
+ }
+
+ @Test
+ public void getMinGain_returnsValueFromDeviceInfo() {
+ AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+ assertThat(info.getMinGain()).isEqualTo(MIN_GAIN);
+ }
+
+ @Test
+ public void getDefaultGain_returnsValueFromDeviceInfo() {
+ AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+ assertThat(info.getDefaultGain()).isEqualTo(DEFAULT_GAIN);
+ }
+
+ @Test
+ public void getStepValue_returnsValueFromDeviceInfo() {
+ AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+ assertThat(info.getStepValue()).isEqualTo(STEP_SIZE);
+ }
+
+ @Test
+ public void getChannelCount_withNoChannelMasks_returnsOne() {
+ AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+ int channelCount = info.getChannelCount();
+
+ assertThat(channelCount).isEqualTo(1);
+ }
+
+ @Test
+ public void getChannelCount_withMultipleChannels_returnsHighestCount() {
+ AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+ when(audioDeviceInfo.getChannelMasks()).thenReturn(new int[]{CHANNEL_OUT_STEREO,
+ CHANNEL_OUT_QUAD, CHANNEL_OUT_MONO});
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+ int channelCount = info.getChannelCount();
+
+ assertThat(channelCount).isEqualTo(4);
+ }
+
+ @Test
+ public void getAudioDeviceInfo_returnsConstructorParameter() {
+ AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+ assertThat(info.getAudioDeviceInfo()).isEqualTo(audioDeviceInfo);
+ }
+
+ @Test
+ public void getEncodingFormat_returnsPCM16() {
+ AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+
+ assertThat(info.getEncodingFormat()).isEqualTo(ENCODING_PCM_16BIT);
+ }
+
+ @Test
+ public void getAudioGain_returnsGainFromDeviceInfo() {
+ AudioDeviceInfo audioDeviceInfo = getMockAudioDeviceInfo();
+ CarAudioDeviceInfo info = new CarAudioDeviceInfo(audioDeviceInfo);
+ AudioGain expectedGain = audioDeviceInfo.getPort().gains()[0];
+
+ assertThat(info.getAudioGain()).isEqualTo(expectedGain);
+ }
+
+ private AudioDeviceInfo getMockAudioDeviceInfo() {
+ AudioGain mockGain = new GainBuilder().build();
+ return getMockAudioDeviceInfo(new AudioGain[]{mockGain});
+ }
+
+ private AudioDeviceInfo getMockAudioDeviceInfo(AudioGain[] gains) {
+ AudioDeviceInfo mockInfo = mock(AudioDeviceInfo.class);
+ AudioDevicePort mockPort = mock(AudioDevicePort.class);
+ when(mockInfo.getPort()).thenReturn(mockPort);
+ when(mockPort.gains()).thenReturn(gains);
+ when(mockInfo.getAddress()).thenReturn(TEST_ADDRESS);
+ return mockInfo;
+ }
+
+ private static class GainBuilder {
+ int mMode = AudioGain.MODE_JOINT;
+ int mMaxValue = MAX_GAIN;
+ int mMinValue = MIN_GAIN;
+ int mDefaultValue = DEFAULT_GAIN;
+ int mStepSize = STEP_SIZE;
+
+ GainBuilder setMode(int mode) {
+ mMode = mode;
+ return this;
+ }
+
+ GainBuilder setMaxValue(int maxValue) {
+ mMaxValue = maxValue;
+ return this;
+ }
+
+ GainBuilder setMinValue(int minValue) {
+ mMinValue = minValue;
+ return this;
+ }
+
+ GainBuilder setDefaultValue(int defaultValue) {
+ mDefaultValue = defaultValue;
+ return this;
+ }
+
+ GainBuilder setStepSize(int stepSize) {
+ mStepSize = stepSize;
+ return this;
+ }
+
+ AudioGain build() {
+ AudioGain mockGain = mock(AudioGain.class);
+ when(mockGain.mode()).thenReturn(mMode);
+ when(mockGain.maxValue()).thenReturn(mMaxValue);
+ when(mockGain.minValue()).thenReturn(mMinValue);
+ when(mockGain.defaultValue()).thenReturn(mDefaultValue);
+ when(mockGain.stepValue()).thenReturn(mStepSize);
+ return mockGain;
+ }
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarDuckingTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarDuckingTest.java
index 5fa1bfd..4a0171b 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarDuckingTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarDuckingTest.java
@@ -50,6 +50,7 @@
public final class CarDuckingTest {
private static final int PRIMARY_ZONE_ID = 0;
private static final int PASSENGER_ZONE_ID = 1;
+ private static final int[] ONE_ZONE_CHANGE = new int[]{PRIMARY_ZONE_ID};
private static final int REAR_ZONE_ID = 2;
private static final String PRIMARY_MEDIA_ADDRESS = "primary_media";
private static final String PRIMARY_NAVIGATION_ADDRESS = "primary_navigation_address";
@@ -59,18 +60,22 @@
private final AudioFocusInfo mMediaFocusInfo = generateAudioFocusInfoForUsage(USAGE_MEDIA);
private final AudioFocusInfo mNavigationFocusInfo =
generateAudioFocusInfoForUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE);
+ private final SparseArray<List<AudioFocusInfo>> mMediaFocusHolders = new SparseArray<>();
+ private final SparseArray<List<AudioFocusInfo>> mMediaNavFocusHolders = new SparseArray<>();
@Mock
private AudioControlWrapper mMockAudioControlWrapper;
@Captor
- private ArgumentCaptor<CarDuckingInfo> mCarDuckingInfoCaptor;
+ private ArgumentCaptor<List<CarDuckingInfo>> mCarDuckingInfosCaptor;
private CarDucking mCarDucking;
@Before
public void setUp() {
mCarDucking = new CarDucking(mCarAudioZones, mMockAudioControlWrapper);
+ mMediaFocusHolders.put(PRIMARY_ZONE_ID, List.of(mMediaFocusInfo));
+ mMediaNavFocusHolders.put(PRIMARY_ZONE_ID, List.of(mMediaFocusInfo, mNavigationFocusInfo));
}
@Test
@@ -90,9 +95,7 @@
@Test
public void onFocusChange_forPrimaryZone_updatesUsagesHoldingFocus() {
- List<AudioFocusInfo> focusHolders = List.of(mMediaFocusInfo);
-
- mCarDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
+ mCarDucking.onFocusChange(ONE_ZONE_CHANGE, mMediaFocusHolders);
SparseArray<CarDuckingInfo> newDuckingInfo = mCarDucking.getCurrentDuckingInfo();
assertThat(newDuckingInfo.get(PRIMARY_ZONE_ID).mUsagesHoldingFocus)
@@ -101,9 +104,7 @@
@Test
public void onFocusChange_forPrimaryZone_doesNotUpdateSecondaryZones() {
- List<AudioFocusInfo> focusHolders = List.of(mMediaFocusInfo);
-
- mCarDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
+ mCarDucking.onFocusChange(ONE_ZONE_CHANGE, mMediaFocusHolders);
SparseArray<CarDuckingInfo> newDuckingInfo = mCarDucking.getCurrentDuckingInfo();
assertThat(newDuckingInfo.get(PASSENGER_ZONE_ID).mUsagesHoldingFocus).isEmpty();
@@ -112,9 +113,7 @@
@Test
public void onFocusChange_withMultipleFocusHolders_updatesAddressesToDuck() {
- List<AudioFocusInfo> focusHolders = List.of(mMediaFocusInfo, mNavigationFocusInfo);
-
- mCarDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
+ mCarDucking.onFocusChange(ONE_ZONE_CHANGE, mMediaNavFocusHolders);
SparseArray<CarDuckingInfo> newDuckingInfo = mCarDucking.getCurrentDuckingInfo();
assertThat(newDuckingInfo.get(PRIMARY_ZONE_ID).mAddressesToDuck)
@@ -123,11 +122,9 @@
@Test
public void onFocusChange_withDuckedDevices_updatesAddressesToUnduck() {
- List<AudioFocusInfo> focusHolders = List.of(mMediaFocusInfo, mNavigationFocusInfo);
- mCarDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
+ mCarDucking.onFocusChange(ONE_ZONE_CHANGE, mMediaNavFocusHolders);
- List<AudioFocusInfo> updatedHolders = List.of(mMediaFocusInfo);
- mCarDucking.onFocusChange(PRIMARY_ZONE_ID, updatedHolders);
+ mCarDucking.onFocusChange(ONE_ZONE_CHANGE, mMediaFocusHolders);
SparseArray<CarDuckingInfo> newDuckingInfo = mCarDucking.getCurrentDuckingInfo();
assertThat(newDuckingInfo.get(PRIMARY_ZONE_ID).mAddressesToUnduck)
@@ -136,41 +133,48 @@
@Test
public void onFocusChange_notifiesHalOfUsagesHoldingFocus() {
- List<AudioFocusInfo> focusHolders = List.of(mMediaFocusInfo);
+ mCarDucking.onFocusChange(ONE_ZONE_CHANGE, mMediaFocusHolders);
- mCarDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
-
- ArgumentCaptor<CarDuckingInfo> captor = ArgumentCaptor.forClass(CarDuckingInfo.class);
- verify(mMockAudioControlWrapper).onDevicesToDuckChange(captor.capture());
- int[] usagesHoldingFocus = captor.getValue().mUsagesHoldingFocus;
+ verify(mMockAudioControlWrapper).onDevicesToDuckChange(mCarDuckingInfosCaptor.capture());
+ int[] usagesHoldingFocus = mCarDuckingInfosCaptor.getValue().get(0).mUsagesHoldingFocus;
assertThat(usagesHoldingFocus).asList().containsExactly(USAGE_MEDIA);
}
@Test
public void onFocusChange_notifiesHalOfAddressesToDuck() {
- List<AudioFocusInfo> focusHolders = List.of(mMediaFocusInfo, mNavigationFocusInfo);
+ mCarDucking.onFocusChange(ONE_ZONE_CHANGE, mMediaNavFocusHolders);
- mCarDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
-
- verify(mMockAudioControlWrapper).onDevicesToDuckChange(mCarDuckingInfoCaptor.capture());
- List<String> addressesToDuck = mCarDuckingInfoCaptor.getValue().mAddressesToDuck;
+ verify(mMockAudioControlWrapper).onDevicesToDuckChange(mCarDuckingInfosCaptor.capture());
+ List<String> addressesToDuck = mCarDuckingInfosCaptor.getValue().get(0).mAddressesToDuck;
assertThat(addressesToDuck).containsExactly(PRIMARY_MEDIA_ADDRESS);
}
@Test
public void onFocusChange_notifiesHalOfAddressesToUnduck() {
- List<AudioFocusInfo> focusHolders = List.of(mMediaFocusInfo, mNavigationFocusInfo);
- mCarDucking.onFocusChange(PRIMARY_ZONE_ID, focusHolders);
+ mCarDucking.onFocusChange(ONE_ZONE_CHANGE, mMediaNavFocusHolders);
- List<AudioFocusInfo> updatedHolders = List.of(mMediaFocusInfo);
- mCarDucking.onFocusChange(PRIMARY_ZONE_ID, updatedHolders);
+ mCarDucking.onFocusChange(ONE_ZONE_CHANGE, mMediaFocusHolders);
verify(mMockAudioControlWrapper, times(2))
- .onDevicesToDuckChange(mCarDuckingInfoCaptor.capture());
- List<String> addressesToUnduck = mCarDuckingInfoCaptor.getValue().mAddressesToUnduck;
+ .onDevicesToDuckChange(mCarDuckingInfosCaptor.capture());
+ List<String> addressesToUnduck = mCarDuckingInfosCaptor.getValue().get(0)
+ .mAddressesToUnduck;
assertThat(addressesToUnduck).containsExactly(PRIMARY_MEDIA_ADDRESS);
}
+ @Test
+ public void onFocusChange_withMultipleZones_notifiesForEachZone() {
+ int[] zoneIds = new int[]{PRIMARY_ZONE_ID, PASSENGER_ZONE_ID};
+ SparseArray<List<AudioFocusInfo>> focusChanges = new SparseArray<>();
+ focusChanges.put(PRIMARY_ZONE_ID, List.of(mMediaFocusInfo));
+ focusChanges.put(PASSENGER_ZONE_ID, List.of(mNavigationFocusInfo));
+
+ mCarDucking.onFocusChange(zoneIds, focusChanges);
+
+ verify(mMockAudioControlWrapper).onDevicesToDuckChange(mCarDuckingInfosCaptor.capture());
+ assertThat(mCarDuckingInfosCaptor.getValue().size()).isEqualTo(zoneIds.length);
+ }
+
private AudioFocusInfo generateAudioFocusInfoForUsage(int usage) {
AudioAttributes attributes = new AudioAttributes.Builder().setUsage(usage).build();
return new AudioFocusInfo(attributes, 0, "client_id", "package.name",
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarZonesAudioFocusUnitTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarZonesAudioFocusUnitTest.java
index 099474c..685a348 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarZonesAudioFocusUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarZonesAudioFocusUnitTest.java
@@ -20,6 +20,9 @@
import static android.media.AudioManager.AUDIOFOCUS_GAIN;
import static android.media.AudioManager.AUDIOFOCUS_REQUEST_GRANTED;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.description;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
@@ -39,6 +42,7 @@
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
@@ -179,7 +183,15 @@
mCarZonesAudioFocus.onAudioFocusRequest(audioFocusInfo, AUDIOFOCUS_REQUEST_GRANTED);
- verify(mMockCarFocusCallback).onFocusChange(PRIMARY_ZONE_ID, focusHolders);
+ ArgumentCaptor<SparseArray<List<AudioFocusInfo>>> captor =
+ ArgumentCaptor.forClass(SparseArray.class);
+ verify(mMockCarFocusCallback)
+ .onFocusChange(eq(new int[]{PRIMARY_ZONE_ID}), captor.capture());
+ SparseArray<List<AudioFocusInfo>> results = captor.getValue();
+ assertWithMessage("Number of lists returned in sparse array")
+ .that(results.size()).isEqualTo(1);
+ assertWithMessage("Focus holders for primary zone")
+ .that(results.get(PRIMARY_ZONE_ID)).isEqualTo(focusHolders);
}
@Test
@@ -206,6 +218,18 @@
.setRestrictFocus(false);
}
+ @Test
+ public void setRestrictFocus_notifiesFocusCallbackForAllZones() {
+ mCarZonesAudioFocus.setRestrictFocus(false);
+
+ ArgumentCaptor<SparseArray<List<AudioFocusInfo>>> captor =
+ ArgumentCaptor.forClass(SparseArray.class);
+ int[] expectedZoneIds = new int[]{PRIMARY_ZONE_ID, SECONDARY_ZONE_ID};
+ verify(mMockCarFocusCallback).onFocusChange(eq(expectedZoneIds), captor.capture());
+ assertWithMessage("Number of focus holder lists")
+ .that(captor.getValue().size()).isEqualTo(2);
+ }
+
private static SparseArray<CarAudioZone> generateAudioZones() {
SparseArray<CarAudioZone> zones = new SparseArray<>();
zones.put(PRIMARY_ZONE_ID, new CarAudioZone(PRIMARY_ZONE_ID, "Primary zone"));
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/hal/AudioControlWrapperAidlTest.java b/tests/carservice_unit_test/src/com/android/car/audio/hal/AudioControlWrapperAidlTest.java
index 4a99b91..cc914ca 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/hal/AudioControlWrapperAidlTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/hal/AudioControlWrapperAidlTest.java
@@ -182,7 +182,7 @@
CarDuckingInfo carDuckingInfo = new CarDuckingInfo(ZONE_ID, new ArrayList<>(),
new ArrayList<>(), new int[0]);
- mAudioControlWrapperAidl.onDevicesToDuckChange(carDuckingInfo);
+ mAudioControlWrapperAidl.onDevicesToDuckChange(List.of(carDuckingInfo));
ArgumentCaptor<DuckingInfo[]> captor = ArgumentCaptor.forClass(DuckingInfo[].class);
verify(mAudioControl).onDevicesToDuckChange(captor.capture());
@@ -195,7 +195,7 @@
CarDuckingInfo carDuckingInfo = new CarDuckingInfo(ZONE_ID, new ArrayList<>(),
new ArrayList<>(), new int[]{USAGE_MEDIA, USAGE_NOTIFICATION});
- mAudioControlWrapperAidl.onDevicesToDuckChange(carDuckingInfo);
+ mAudioControlWrapperAidl.onDevicesToDuckChange(List.of(carDuckingInfo));
ArgumentCaptor<DuckingInfo[]> captor = ArgumentCaptor.forClass(DuckingInfo[].class);
verify(mAudioControl).onDevicesToDuckChange(captor.capture());
@@ -212,7 +212,7 @@
CarDuckingInfo carDuckingInfo = new CarDuckingInfo(ZONE_ID,
Arrays.asList(mediaAddress, navigationAddress), new ArrayList<>(), new int[0]);
- mAudioControlWrapperAidl.onDevicesToDuckChange(carDuckingInfo);
+ mAudioControlWrapperAidl.onDevicesToDuckChange(List.of(carDuckingInfo));
ArgumentCaptor<DuckingInfo[]> captor = ArgumentCaptor.forClass(DuckingInfo[].class);
verify(mAudioControl).onDevicesToDuckChange(captor.capture());
@@ -228,7 +228,7 @@
CarDuckingInfo carDuckingInfo = new CarDuckingInfo(ZONE_ID, new ArrayList<>(),
Arrays.asList(notificationAddress, callAddress), new int[0]);
- mAudioControlWrapperAidl.onDevicesToDuckChange(carDuckingInfo);
+ mAudioControlWrapperAidl.onDevicesToDuckChange(List.of(carDuckingInfo));
ArgumentCaptor<DuckingInfo[]> captor = ArgumentCaptor.forClass(DuckingInfo[].class);
verify(mAudioControl).onDevicesToDuckChange(captor.capture());
@@ -242,7 +242,7 @@
CarDuckingInfo carDuckingInfo = new CarDuckingInfo(ZONE_ID, new ArrayList<>(),
new ArrayList<>(), new int[0]);
- mAudioControlWrapperAidl.onDevicesToDuckChange(carDuckingInfo);
+ mAudioControlWrapperAidl.onDevicesToDuckChange(List.of(carDuckingInfo));
ArgumentCaptor<DuckingInfo[]> captor = ArgumentCaptor.forClass(DuckingInfo[].class);
verify(mAudioControl).onDevicesToDuckChange(captor.capture());
@@ -251,6 +251,22 @@
}
@Test
+ public void onDevicesToDuckChange_multipleZones_passesADuckingInfoPerZone() throws Exception {
+ CarDuckingInfo carDuckingInfo = new CarDuckingInfo(ZONE_ID, new ArrayList<>(),
+ new ArrayList<>(), new int[0]);
+ CarDuckingInfo secondaryCarDuckingInfo = new CarDuckingInfo(SECONDARY_ZONE_ID,
+ new ArrayList<>(), new ArrayList<>(), new int[0]);
+
+ mAudioControlWrapperAidl.onDevicesToDuckChange(List.of(carDuckingInfo,
+ secondaryCarDuckingInfo));
+
+ ArgumentCaptor<DuckingInfo[]> captor = ArgumentCaptor.forClass(DuckingInfo[].class);
+ verify(mAudioControl).onDevicesToDuckChange(captor.capture());
+ assertWithMessage("Number of ducking infos passed along")
+ .that(captor.getValue().length).isEqualTo(2);
+ }
+
+ @Test
public void linkToDeath_callsBinder() throws Exception {
mAudioControlWrapperAidl.linkToDeath(null);
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
index 364a4ca..bc6c7ea 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/CarUserServiceTest.java
@@ -548,15 +548,15 @@
when(mMockedIActivityManager.startUserInBackground(user2)).thenReturn(true);
when(mMockedIActivityManager.unlockUser(user2, null, null, null)).thenReturn(true);
- assertThat(mCarUserService.startAllBackgroundUsers()).containsExactly(user2);
+ assertThat(mCarUserService.startAllBackgroundUsersInGarageMode()).containsExactly(user2);
sendUserUnlockedEvent(user2);
assertThat(mCarUserService.getBackgroundUsersToRestart()).containsExactly(user2, user3);
when(mMockedIActivityManager.stopUser(user2, true, null))
.thenReturn(ActivityManager.USER_OP_SUCCESS);
// should not stop the current fg user
- assertThat(mCarUserService.stopBackgroundUser(user3)).isFalse();
- assertThat(mCarUserService.stopBackgroundUser(user2)).isTrue();
+ assertThat(mCarUserService.stopBackgroundUserInGagageMode(user3)).isFalse();
+ assertThat(mCarUserService.stopBackgroundUserInGagageMode(user2)).isTrue();
assertThat(mCarUserService.getBackgroundUsersToRestart()).containsExactly(user2, user3);
assertThat(mCarUserService.getBackgroundUsersToRestart()).containsExactly(user2, user3);
}
@@ -645,7 +645,8 @@
mockStopUserWithDelayedLocking(
UserHandle.USER_SYSTEM, ActivityManager.USER_OP_ERROR_IS_SYSTEM);
- assertThat(mCarUserService.stopBackgroundUser(UserHandle.USER_SYSTEM)).isFalse();
+ assertThat(mCarUserService.stopBackgroundUserInGagageMode(UserHandle.USER_SYSTEM))
+ .isFalse();
}
@Test
@@ -653,7 +654,7 @@
int userId = 101;
mockStopUserWithDelayedLocking(userId, ActivityManager.USER_OP_IS_CURRENT);
- assertThat(mCarUserService.stopBackgroundUser(userId)).isFalse();
+ assertThat(mCarUserService.stopBackgroundUserInGagageMode(userId)).isFalse();
}
@Test