Merge "Payload check in propertyHalService" into rvc-dev
diff --git a/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags b/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags
index e5b8db8..9473474 100644
--- a/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags
+++ b/car-internal-lib/src/com/android/internal/car/EventLogTags.logtags
@@ -74,6 +74,7 @@
150108 car_user_svc_post_switch_user_req (target_user_id|1),(current_user_id|1)
150109 car_user_svc_get_user_auth_req (uid|1),(user_id|1),(number_types|1)
150110 car_user_svc_get_user_auth_resp (number_values|1)
+150111 car_user_svc_switch_user_ui_req (user_id|1)
150140 car_user_hal_initial_user_info_req (request_id|1),(request_type|1),(timeout|1)
150141 car_user_hal_initial_user_info_resp (request_id|1),(status|1),(action|1),(user_id|1),(flags|1),(safe_name|3)
@@ -82,6 +83,7 @@
150144 car_user_hal_post_switch_user_req (request_id|1),(target_user_id|1),(current_user_id|1)
150145 car_user_hal_get_user_auth_req (int32values|4)
150146 car_user_hal_get_user_auth_resp (int32values|4),(error_message|3)
+150147 car_user_hal_legacy_switch_user_req (request_id|1),(target_user_id|1),(current_user_id|1)
150171 car_user_mgr_add_listener (uid|1)
150172 car_user_mgr_remove_listener (uid|1)
diff --git a/car-lib/native/include/CarPowerManager.h b/car-lib/native/include/CarPowerManager.h
index 57bbd50..b02c886 100644
--- a/car-lib/native/include/CarPowerManager.h
+++ b/car-lib/native/include/CarPowerManager.h
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-#ifndef CAR_POWER_MANAGER
-#define CAR_POWER_MANAGER
+#ifndef CAR_LIB_NATIVE_INCLUDE_CARPOWERMANAGER_H_
+#define CAR_LIB_NATIVE_INCLUDE_CARPOWERMANAGER_H_
#include <binder/Status.h>
#include <utils/RefBase.h>
@@ -38,6 +38,7 @@
// NOTE: The entries in this enum must match the ones in CarPowerStateListener located in
// packages/services/Car/car-lib/src/android/car/hardware/power/CarPowerManager.java
enum class State {
+ kInvalid = 0,
kWaitForVhal = 1,
kSuspendEnter = 2,
kSuspendExit = 3,
@@ -46,8 +47,7 @@
kShutdownPrepare = 7,
kShutdownCancelled = 8,
-
- kFirst = kWaitForVhal,
+ kFirst = kInvalid,
kLast = kShutdownCancelled,
};
@@ -74,7 +74,7 @@
private:
class CarPowerStateListener final : public BnCarPowerStateListener {
public:
- explicit CarPowerStateListener(CarPowerManager* parent) : mParent(parent) {};
+ explicit CarPowerStateListener(CarPowerManager* parent) : mParent(parent) {}
Status onStateChanged(int state) override {
sp<CarPowerManager> parent = mParent;
@@ -102,9 +102,9 @@
sp<CarPowerStateListener> mListenerToService;
};
-} // namespace power
-} // namespace hardware
-} // namespace car
-} // namespace android
+} // namespace power
+} // namespace hardware
+} // namespace car
+} // namespace android
-#endif // CAR_POWER_MANAGER
+#endif // CAR_LIB_NATIVE_INCLUDE_CARPOWERMANAGER_H_
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index 3160e9e..099d56a 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -988,6 +988,10 @@
/**
* A factory method that creates Car instance for all Car API access.
+ *
+ * <p>Instance created with this should be disconnected from car service by calling
+ * {@link #disconnect()} before the passed {code Context} is released.
+ *
* @param context App's Context. This should not be null. If you are passing
* {@link ContextWrapper}, make sure that its base Context is non-null as well.
* Otherwise it will throw {@link java.lang.NullPointerException}.
@@ -1020,6 +1024,9 @@
* A factory method that creates Car instance for all Car API access using main thread {@code
* Looper}.
*
+ * <p>Instance created with this should be disconnected from car service by calling
+ * {@link #disconnect()} before the passed {code Context} is released.
+ *
* @see #createCar(Context, ServiceConnection, Handler)
*
* @deprecated use {@link #createCar(Context, Handler)} instead.
@@ -1032,6 +1039,9 @@
/**
* Creates new {@link Car} object which connected synchronously to Car Service and ready to use.
*
+ * <p>Instance created with this should be disconnected from car service by calling
+ * {@link #disconnect()} before the passed {code Context} is released.
+ *
* @param context application's context
*
* @return Car object if operation succeeded, otherwise null.
@@ -1044,6 +1054,9 @@
/**
* Creates new {@link Car} object which connected synchronously to Car Service and ready to use.
*
+ * <p>Instance created with this should be disconnected from car service by calling
+ * {@link #disconnect()} before the passed {code Context} is released.
+ *
* @param context App's Context. This should not be null. If you are passing
* {@link ContextWrapper}, make sure that its base Context is non-null as well.
* Otherwise it will throw {@link java.lang.NullPointerException}.
@@ -1111,6 +1124,9 @@
/**
* Creates new {@link Car} object with {@link CarServiceLifecycleListener}.
*
+ * <p>Instance created with this should be disconnected from car service by calling
+ * {@link #disconnect()} before the passed {code Context} is released.
+ *
* <p> If car service is ready inside this call and if the caller is running in the main thread,
* {@link CarServiceLifecycleListener#onLifecycleChanged(Car, boolean)} will be called
* with ready set to be true. Otherwise,
diff --git a/car-lib/src/android/car/ICarUserService.aidl b/car-lib/src/android/car/ICarUserService.aidl
index 9e49790..2eccdbb 100644
--- a/car-lib/src/android/car/ICarUserService.aidl
+++ b/car-lib/src/android/car/ICarUserService.aidl
@@ -37,4 +37,5 @@
oneway void resetLifecycleListenerForUid();
oneway void getInitialUserInfo(int requestType, int timeoutMs, in IResultReceiver receiver);
GetUserIdentificationAssociationResponse getUserIdentificationAssociation(in int[] types);
+ oneway void setUserSwitchUiCallback(in IResultReceiver callback);
}
diff --git a/car-lib/src/android/car/hardware/power/CarPowerManager.java b/car-lib/src/android/car/hardware/power/CarPowerManager.java
index 7c2a323..d9ff33d 100644
--- a/car-lib/src/android/car/hardware/power/CarPowerManager.java
+++ b/car-lib/src/android/car/hardware/power/CarPowerManager.java
@@ -16,6 +16,7 @@
package android.car.hardware.power;
+import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.car.Car;
import android.car.CarManagerBase;
@@ -56,10 +57,15 @@
public interface CarPowerStateListener {
/**
* onStateChanged() states. These definitions must match the ones located in the native
- * CarPowerManager: packages/services/Car/car-lib/native/CarPowerManager/CarPowerManager.h
+ * CarPowerManager: packages/services/Car/car-lib/native/include/CarPowerManager.h
*/
/**
+ * The current power state is unavailable, unknown, or invalid
+ * @hide
+ */
+ int INVALID = 0;
+ /**
* Android is up, but vendor is controlling the audio / display
* @hide
*/
@@ -162,6 +168,20 @@
}
/**
+ * Returns the current power state
+ * @return One of the values defined in {@link CarPowerStateListener}
+ * @hide
+ */
+ @RequiresPermission(Car.PERMISSION_CAR_POWER)
+ public int getPowerState() {
+ try {
+ return mService.getPowerState();
+ } catch (RemoteException e) {
+ return handleRemoteExceptionFromCarService(e, CarPowerStateListener.INVALID);
+ }
+ }
+
+ /**
* Sets a listener to receive power state changes. Only one listener may be set at a
* time for an instance of CarPowerManager.
* The listener is assumed to completely handle the 'onStateChanged' before returning.
diff --git a/car-lib/src/android/car/hardware/power/ICarPower.aidl b/car-lib/src/android/car/hardware/power/ICarPower.aidl
index 581e736..505b075 100644
--- a/car-lib/src/android/car/hardware/power/ICarPower.aidl
+++ b/car-lib/src/android/car/hardware/power/ICarPower.aidl
@@ -31,4 +31,6 @@
void scheduleNextWakeupTime(int seconds) = 4;
void registerListenerWithCompletion(in ICarPowerStateListener listener) = 5;
+
+ int getPowerState() = 6;
}
diff --git a/car-lib/src/android/car/navigation/CarNavigationInstrumentCluster.java b/car-lib/src/android/car/navigation/CarNavigationInstrumentCluster.java
index b41c7de..444f82a 100644
--- a/car-lib/src/android/car/navigation/CarNavigationInstrumentCluster.java
+++ b/car-lib/src/android/car/navigation/CarNavigationInstrumentCluster.java
@@ -57,25 +57,31 @@
private final Bundle mExtra;
- public static final Parcelable.Creator<CarNavigationInstrumentCluster> CREATOR
- = new Parcelable.Creator<CarNavigationInstrumentCluster>() {
- public CarNavigationInstrumentCluster createFromParcel(Parcel in) {
- return new CarNavigationInstrumentCluster(in);
- }
+ public static final Parcelable.Creator<CarNavigationInstrumentCluster> CREATOR =
+ new Parcelable.Creator<CarNavigationInstrumentCluster>() {
+ public CarNavigationInstrumentCluster createFromParcel(Parcel in) {
+ return new CarNavigationInstrumentCluster(in);
+ }
- public CarNavigationInstrumentCluster[] newArray(int size) {
- return new CarNavigationInstrumentCluster[size];
- }
- };
+ public CarNavigationInstrumentCluster[] newArray(int size) {
+ return new CarNavigationInstrumentCluster[size];
+ }
+ };
+ /**
+ * Creates a new {@link CarNavigationInstrumentCluster}.
+ */
public static CarNavigationInstrumentCluster createCluster(int minIntervalMillis) {
return new CarNavigationInstrumentCluster(minIntervalMillis, CLUSTER_TYPE_IMAGE_CODES_ONLY,
0, 0, 0);
}
- public static CarNavigationInstrumentCluster createCustomImageCluster(int minIntervalMs,
+ /**
+ * Creates a new {@link CarNavigationInstrumentCluster}.
+ */
+ public static CarNavigationInstrumentCluster createCustomImageCluster(int minIntervalMillis,
int imageWidth, int imageHeight, int imageColorDepthBits) {
- return new CarNavigationInstrumentCluster(minIntervalMs,
+ return new CarNavigationInstrumentCluster(minIntervalMillis,
CLUSTER_TYPE_CUSTOM_IMAGES_SUPPORTED,
imageWidth, imageHeight, imageColorDepthBits);
}
@@ -108,7 +114,9 @@
* Contains extra information about instrument cluster.
* @hide
*/
- public Bundle getExtra() { return mExtra; }
+ public Bundle getExtra() {
+ return mExtra;
+ }
/**
* If instrument cluster is image, number of bits of colour depth it supports (8, 16, or 32).
@@ -127,10 +135,9 @@
/**
* Whether cluster support custom image or not.
- * @return
*/
public boolean supportsCustomImages() {
- return mType == CLUSTER_TYPE_CUSTOM_IMAGES_SUPPORTED;
+ return mType == CLUSTER_TYPE_CUSTOM_IMAGES_SUPPORTED;
}
private CarNavigationInstrumentCluster(
@@ -174,12 +181,12 @@
/** Converts to string for debug purpose */
@Override
public String toString() {
- return CarNavigationInstrumentCluster.class.getSimpleName() + "{ " +
- "minIntervalMillis: " + mMinIntervalMillis + ", " +
- "type: " + mType + ", " +
- "imageWidth: " + mImageWidth + ", " +
- "imageHeight: " + mImageHeight + ", " +
- "imageColourDepthBits: " + mImageColorDepthBits +
- "extra: " + mExtra + " }";
+ return CarNavigationInstrumentCluster.class.getSimpleName() + "{ "
+ + "minIntervalMillis: " + mMinIntervalMillis + ", "
+ + "type: " + mType + ", "
+ + "imageWidth: " + mImageWidth + ", "
+ + "imageHeight: " + mImageHeight + ", "
+ + "imageColourDepthBits: " + mImageColorDepthBits
+ + "extra: " + mExtra + " }";
}
}
diff --git a/car-lib/src/android/car/storagemonitoring/IoStats.java b/car-lib/src/android/car/storagemonitoring/IoStats.java
index d620169..5154196 100644
--- a/car-lib/src/android/car/storagemonitoring/IoStats.java
+++ b/car-lib/src/android/car/storagemonitoring/IoStats.java
@@ -70,7 +70,7 @@
mUptimeTimestamp = in.getInt("uptime");
JSONArray statsArray = in.getJSONArray("stats");
mStats = new ArrayList<>();
- for(int i = 0; i < statsArray.length(); ++i) {
+ for (int i = 0; i < statsArray.length(); ++i) {
mStats.add(new IoStatsEntry(statsArray.getJSONObject(i)));
}
}
@@ -113,6 +113,11 @@
return Objects.hash(mStats, mUptimeTimestamp);
}
+ /**
+ * Returns user's stats ({@link IoStatsEntry}).
+ *
+ * @param uid Android's user id
+ */
public IoStatsEntry getUserIdStats(int uid) {
for (IoStatsEntry stats : getStats()) {
if (stats.uid == uid) {
@@ -123,6 +128,10 @@
return null;
}
+ /**
+ * Returns the following foreground total metrics: bytes written and read, bytes read from and
+ * written to storage, and number of sync calls.
+ */
public IoStatsEntry.Metrics getForegroundTotals() {
long bytesRead = 0;
long bytesWritten = 0;
@@ -145,6 +154,10 @@
fsyncCalls);
}
+ /**
+ * Returns the following background total metrics: bytes written and read, bytes read from and
+ * written to storage, and number of sync calls.
+ */
public IoStatsEntry.Metrics getBackgroundTotals() {
long bytesRead = 0;
long bytesWritten = 0;
@@ -167,6 +180,10 @@
fsyncCalls);
}
+ /**
+ * Returns the sum of all foreground and background metrics (bytes written, bytes read from
+ * storage, bytes written to storage and number of sync calls).
+ */
public IoStatsEntry.Metrics getTotals() {
IoStatsEntry.Metrics foreground = getForegroundTotals();
IoStatsEntry.Metrics background = getBackgroundTotals();
@@ -181,9 +198,9 @@
@Override
public boolean equals(Object other) {
if (other instanceof IoStats) {
- IoStats delta = (IoStats)other;
- return delta.getTimestamp() == getTimestamp() &&
- delta.getStats().equals(getStats());
+ IoStats delta = (IoStats) other;
+ return delta.getTimestamp() == getTimestamp()
+ && delta.getStats().equals(getStats());
}
return false;
}
diff --git a/car-lib/src/android/car/user/CarUserManager.java b/car-lib/src/android/car/user/CarUserManager.java
index 8d7bfa1..784ee48 100644
--- a/car-lib/src/android/car/user/CarUserManager.java
+++ b/car-lib/src/android/car/user/CarUserManager.java
@@ -338,6 +338,40 @@
}
/**
+ * Sets a callback to be notified before user switch. It should only be used by Car System UI.
+ *
+ * @hide
+ */
+ public void setUserSwitchUiCallback(@NonNull UserSwitchUiCallback callback) {
+ Preconditions.checkArgument(callback != null, "Null callback");
+ UserSwitchUiCallbackReceiver userSwitchUiCallbackReceiver =
+ new UserSwitchUiCallbackReceiver(callback);
+ try {
+ mService.setUserSwitchUiCallback(userSwitchUiCallbackReceiver);
+ } catch (RemoteException e) {
+ handleRemoteExceptionFromCarService(e);
+ }
+ }
+
+ /**
+ * {@code IResultReceiver} used to receive user switch UI Callback.
+ */
+ // TODO(b/154958003): use mReceiver instead as now there are two binder objects
+ private final class UserSwitchUiCallbackReceiver extends IResultReceiver.Stub {
+
+ private final UserSwitchUiCallback mUserSwitchUiCallback;
+
+ UserSwitchUiCallbackReceiver(UserSwitchUiCallback callback) {
+ mUserSwitchUiCallback = callback;
+ }
+
+ @Override
+ public void send(int userId, Bundle unused) throws RemoteException {
+ mUserSwitchUiCallback.showUserSwitchDialog(userId);
+ }
+ }
+
+ /**
* {@code IResultReceiver} used to receive lifecycle events and dispatch to the proper listener.
*/
private class LifecycleResultReceiver extends IResultReceiver.Stub {
@@ -560,4 +594,20 @@
*/
void onEvent(@NonNull UserLifecycleEvent event);
}
+
+ /**
+ * Callback for notifying user switch before switch started.
+ *
+ * <p> It should only be user by Car System UI. The purpose of this callback is notify the
+ * Car System UI to display the user switch UI.
+ *
+ * @hide
+ */
+ public interface UserSwitchUiCallback {
+
+ /**
+ * Called to notify that user switch dialog should be shown now.
+ */
+ void showUserSwitchDialog(@UserIdInt int userId);
+ }
}
diff --git a/service/src/com/android/car/CarPowerManagementService.java b/service/src/com/android/car/CarPowerManagementService.java
index b2331ca..d130c43 100644
--- a/service/src/com/android/car/CarPowerManagementService.java
+++ b/service/src/com/android/car/CarPowerManagementService.java
@@ -878,6 +878,7 @@
@Override
public void scheduleNextWakeupTime(int seconds) {
+ ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_POWER);
if (seconds < 0) {
Log.w(CarLog.TAG_POWER, "Next wake up time is negative. Ignoring!");
return;
@@ -899,6 +900,15 @@
}
}
+ @Override
+ public int getPowerState() {
+ ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_POWER);
+ synchronized (mLock) {
+ return (mCurrentState == null) ? CarPowerStateListener.INVALID
+ : mCurrentState.mCarPowerStateListenerState;
+ }
+ }
+
private void finishedImpl(IBinder binder) {
boolean allAreComplete = false;
synchronized (mLock) {
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index 90c9b7b..6cf9cd8 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -52,6 +52,7 @@
import android.os.IBinder;
import android.os.Looper;
import android.os.UserHandle;
+import android.telephony.Annotation.CallState;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.Log;
@@ -140,13 +141,21 @@
new AudioPolicy.AudioPolicyVolumeCallback() {
@Override
public void onVolumeAdjustment(int adjustment) {
- final int usage = getSuggestedAudioUsage();
- Log.v(CarLog.TAG_AUDIO,
- "onVolumeAdjustment: " + AudioManager.adjustToString(adjustment)
- + " suggested usage: " + AudioAttributes.usageToString(usage));
- // TODO: Pass zone id into this callback.
- final int zoneId = CarAudioManager.PRIMARY_AUDIO_ZONE;
- final int groupId = getVolumeGroupIdForUsage(zoneId, usage);
+ int zoneId = CarAudioManager.PRIMARY_AUDIO_ZONE;
+ @AudioContext int suggestedContext = getSuggestedAudioContext();
+
+ int groupId;
+ synchronized (mImplLock) {
+ groupId = getVolumeGroupIdForAudioContextLocked(zoneId, suggestedContext);
+ }
+
+ if (Log.isLoggable(CarLog.TAG_AUDIO, Log.VERBOSE)) {
+ Log.v(CarLog.TAG_AUDIO, "onVolumeAdjustment: "
+ + AudioManager.adjustToString(adjustment) + " suggested audio context: "
+ + CarAudioContext.toString(suggestedContext) + " suggested volume group: "
+ + groupId);
+ }
+
final int currentVolume = getGroupVolume(zoneId, groupId);
final int flags = AudioManager.FLAG_FROM_KEY | AudioManager.FLAG_SHOW_UI;
switch (adjustment) {
@@ -792,17 +801,22 @@
Preconditions.checkArgumentInRange(zoneId, 0, mCarAudioZones.length - 1,
"zoneId out of range: " + zoneId);
- CarVolumeGroup[] groups = mCarAudioZones[zoneId].getVolumeGroups();
- for (int i = 0; i < groups.length; i++) {
- int[] contexts = groups[i].getContexts();
- for (int context : contexts) {
- if (CarAudioContext.getContextForUsage(usage) == context) {
- return i;
- }
+ @AudioContext int audioContext = CarAudioContext.getContextForUsage(usage);
+ return getVolumeGroupIdForAudioContextLocked(zoneId, audioContext);
+ }
+ }
+
+ private int getVolumeGroupIdForAudioContextLocked(int zoneId, @AudioContext int audioContext) {
+ CarVolumeGroup[] groups = mCarAudioZones[zoneId].getVolumeGroups();
+ for (int i = 0; i < groups.length; i++) {
+ int[] groupAudioContexts = groups[i].getContexts();
+ for (int groupAudioContext : groupAudioContexts) {
+ if (audioContext == groupAudioContext) {
+ return i;
}
}
- return INVALID_VOLUME_GROUP_ID;
}
+ return INVALID_VOLUME_GROUP_ID;
}
@Override
@@ -1113,29 +1127,11 @@
return group.getAudioDevicePortForContext(CarAudioContext.getContextForUsage(usage));
}
- /**
- * @return The suggested {@link AudioAttributes} usage to which the volume key events apply
- */
- private @AudioAttributes.AttributeUsage int getSuggestedAudioUsage() {
- int callState = mTelephonyManager.getCallState();
- if (callState == TelephonyManager.CALL_STATE_RINGING) {
- return AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
- } else if (callState == TelephonyManager.CALL_STATE_OFFHOOK) {
- return AudioAttributes.USAGE_VOICE_COMMUNICATION;
- } else {
- List<AudioPlaybackConfiguration> playbacks = mAudioManager
- .getActivePlaybackConfigurations()
- .stream()
- .filter(AudioPlaybackConfiguration::isActive)
- .collect(Collectors.toList());
- if (!playbacks.isEmpty()) {
- // Get audio usage from active playbacks if there is any, last one if multiple
- return playbacks.get(playbacks.size() - 1).getAudioAttributes().getSystemUsage();
- } else {
- // TODO(b/72695246): Otherwise, get audio usage from foreground activity/window
- return DEFAULT_AUDIO_USAGE;
- }
- }
+ private @AudioContext int getSuggestedAudioContext() {
+ @CallState int callState = mTelephonyManager.getCallState();
+ List<AudioPlaybackConfiguration> configurations =
+ mAudioManager.getActivePlaybackConfigurations();
+ return CarVolume.getSuggestedAudioContext(configurations, callState);
}
/**
@@ -1144,7 +1140,7 @@
* @return volume group id mapped from stream type
*/
private int getVolumeGroupIdForStreamType(int streamType) {
- int groupId = -1;
+ int groupId = INVALID_VOLUME_GROUP_ID;
for (int i = 0; i < CarAudioDynamicRouting.STREAM_TYPES.length; i++) {
if (streamType == CarAudioDynamicRouting.STREAM_TYPES[i]) {
groupId = i;
diff --git a/service/src/com/android/car/audio/CarVolume.java b/service/src/com/android/car/audio/CarVolume.java
new file mode 100644
index 0000000..39f2c59
--- /dev/null
+++ b/service/src/com/android/car/audio/CarVolume.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.audio;
+
+import static com.android.car.audio.CarAudioService.DEFAULT_AUDIO_CONTEXT;
+
+import android.media.AudioAttributes;
+import android.media.AudioAttributes.AttributeUsage;
+import android.media.AudioPlaybackConfiguration;
+import android.telephony.Annotation.CallState;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.util.SparseIntArray;
+
+import com.android.car.audio.CarAudioContext.AudioContext;
+
+import java.util.List;
+
+/**
+ * CarVolume is responsible for determining which audio contexts to prioritize when adjusting volume
+ */
+final class CarVolume {
+ private static final String TAG = CarVolume.class.getSimpleName();
+ private static final int CONTEXT_NOT_PRIORITIZED = -1;
+
+ private static final int[] AUDIO_CONTEXT_VOLUME_PRIORITY = {
+ CarAudioContext.NAVIGATION,
+ CarAudioContext.CALL,
+ CarAudioContext.MUSIC,
+ CarAudioContext.ANNOUNCEMENT,
+ CarAudioContext.VOICE_COMMAND,
+ CarAudioContext.CALL_RING,
+ CarAudioContext.SYSTEM_SOUND,
+ CarAudioContext.SAFETY,
+ CarAudioContext.ALARM,
+ CarAudioContext.NOTIFICATION,
+ CarAudioContext.VEHICLE_STATUS,
+ CarAudioContext.EMERGENCY,
+ // CarAudioContext.INVALID is intentionally not prioritized as it is not routed by
+ // CarAudioService and is not expected to be used.
+ };
+
+ private static final SparseIntArray VOLUME_PRIORITY_BY_AUDIO_CONTEXT = new SparseIntArray();
+
+ static {
+ for (int priority = 0; priority < AUDIO_CONTEXT_VOLUME_PRIORITY.length; priority++) {
+ VOLUME_PRIORITY_BY_AUDIO_CONTEXT.append(AUDIO_CONTEXT_VOLUME_PRIORITY[priority],
+ priority);
+ }
+ }
+
+ /**
+ * Suggests a {@link AudioContext} that should be adjusted based on the current
+ * {@link AudioPlaybackConfiguration}s and {@link CallState}.
+ */
+ static @AudioContext int getSuggestedAudioContext(
+ List<AudioPlaybackConfiguration> configurations, @CallState int callState) {
+ int currentContext = DEFAULT_AUDIO_CONTEXT;
+ int currentPriority = AUDIO_CONTEXT_VOLUME_PRIORITY.length;
+
+ if (callState == TelephonyManager.CALL_STATE_RINGING) {
+ currentContext = CarAudioContext.CALL_RING;
+ currentPriority = VOLUME_PRIORITY_BY_AUDIO_CONTEXT.get(CarAudioContext.CALL_RING);
+ } else if (callState == TelephonyManager.CALL_STATE_OFFHOOK) {
+ currentContext = CarAudioContext.CALL;
+ currentPriority = VOLUME_PRIORITY_BY_AUDIO_CONTEXT.get(CarAudioContext.CALL);
+ }
+
+ for (AudioPlaybackConfiguration configuration : configurations) {
+ if (!configuration.isActive()) {
+ continue;
+ }
+
+ @AttributeUsage int usage = configuration.getAudioAttributes().getSystemUsage();
+ @AudioContext int context = CarAudioContext.getContextForUsage(usage);
+ int priority = VOLUME_PRIORITY_BY_AUDIO_CONTEXT.get(context, CONTEXT_NOT_PRIORITIZED);
+ if (priority == CONTEXT_NOT_PRIORITIZED) {
+ Log.w(TAG, "Usage " + AudioAttributes.usageToString(usage) + " mapped to context "
+ + CarAudioContext.toString(context) + " which is not prioritized");
+ continue;
+ }
+
+ if (priority < currentPriority) {
+ currentContext = context;
+ currentPriority = priority;
+ }
+ }
+
+ return currentContext;
+ }
+}
diff --git a/service/src/com/android/car/hal/HalClient.java b/service/src/com/android/car/hal/HalClient.java
index 5f5febc..bb96957 100644
--- a/service/src/com/android/car/hal/HalClient.java
+++ b/service/src/com/android/car/hal/HalClient.java
@@ -32,6 +32,7 @@
import android.util.Log;
import com.android.car.CarLog;
+import com.android.internal.annotations.VisibleForTesting;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -41,7 +42,11 @@
* Vehicle HAL client. Interacts directly with Vehicle HAL interface {@link IVehicle}. Contains
* some logic for retriable properties, redirects Vehicle notifications into given looper thread.
*/
-class HalClient {
+final class HalClient {
+
+ private static final String TAG = CarLog.TAG_HAL;
+ private static final boolean DEBUG = false;
+
/**
* If call to vehicle HAL returns StatusCode.TRY_AGAIN, than {@link HalClient} will retry to
* invoke that method again for this amount of milliseconds.
@@ -51,8 +56,9 @@
private static final int SLEEP_BETWEEN_RETRIABLE_INVOKES_MS = 50;
private final IVehicle mVehicle;
-
private final IVehicleCallback mInternalCallback;
+ private final int mWaitCapMs;
+ private final int mSleepMs;
/**
* Create HalClient object
@@ -62,9 +68,18 @@
* @param callback to propagate notifications from Vehicle HAL in the provided looper thread
*/
HalClient(IVehicle vehicle, Looper looper, IVehicleCallback callback) {
+ this(vehicle, looper, callback, WAIT_CAP_FOR_RETRIABLE_RESULT_MS,
+ SLEEP_BETWEEN_RETRIABLE_INVOKES_MS);
+ }
+
+ @VisibleForTesting
+ HalClient(IVehicle vehicle, Looper looper, IVehicleCallback callback,
+ int waitCapMs, int sleepMs) {
mVehicle = vehicle;
Handler handler = new CallbackHandler(looper, callback);
mInternalCallback = new VehicleCallback(handler);
+ mWaitCapMs = waitCapMs;
+ mSleepMs = sleepMs;
}
ArrayList<VehiclePropConfig> getAllPropConfigs() throws RemoteException {
@@ -84,45 +99,44 @@
try {
return mVehicle.set(propValue);
} catch (RemoteException e) {
- Log.e(CarLog.TAG_HAL, "Failed to set value", e);
+ Log.e(TAG, getValueErrorMessage("set", propValue), e);
return StatusCode.TRY_AGAIN;
}
- }, WAIT_CAP_FOR_RETRIABLE_RESULT_MS, SLEEP_BETWEEN_RETRIABLE_INVOKES_MS);
+ }, mWaitCapMs, mSleepMs);
if (StatusCode.INVALID_ARG == status) {
- throw new IllegalArgumentException(
- String.format("Failed to set value for: 0x%s, areaId: 0x%s",
- Integer.toHexString(propValue.prop),
- Integer.toHexString(propValue.areaId)));
+ throw new IllegalArgumentException(getValueErrorMessage("set", propValue));
}
if (StatusCode.OK != status) {
- Log.i(CarLog.TAG_HAL, String.format(
- "Failed to set property: 0x%s, areaId: 0x%s, code: %d",
- Integer.toHexString(propValue.prop),
- Integer.toHexString(propValue.areaId),
- status));
+ Log.e(TAG, getPropertyErrorMessage("set", propValue, status));
throw new ServiceSpecificException(status,
"Failed to set property: 0x" + Integer.toHexString(propValue.prop)
+ " in areaId: 0x" + Integer.toHexString(propValue.areaId));
}
}
+ private String getValueErrorMessage(String action, VehiclePropValue propValue) {
+ return String.format("Failed to %s value for: 0x%s, areaId: 0x%s", action,
+ Integer.toHexString(propValue.prop), Integer.toHexString(propValue.areaId));
+ }
+
+ private String getPropertyErrorMessage(String action, VehiclePropValue propValue, int status) {
+ return String.format("Failed to %s property: 0x%s, areaId: 0x%s, code: %d (%s)", action,
+ Integer.toHexString(propValue.prop), Integer.toHexString(propValue.areaId),
+ status, StatusCode.toString(status));
+ }
+
VehiclePropValue getValue(VehiclePropValue requestedPropValue) {
final ObjectWrapper<VehiclePropValue> valueWrapper = new ObjectWrapper<>();
int status = invokeRetriable(() -> {
ValueResult res = internalGet(requestedPropValue);
valueWrapper.object = res.propValue;
return res.status;
- }, WAIT_CAP_FOR_RETRIABLE_RESULT_MS, SLEEP_BETWEEN_RETRIABLE_INVOKES_MS);
+ }, mWaitCapMs, mSleepMs);
- int propId = requestedPropValue.prop;
- int areaId = requestedPropValue.areaId;
if (StatusCode.INVALID_ARG == status) {
- throw new IllegalArgumentException(
- String.format("Failed to get value for: 0x%s, areaId: 0x%s",
- Integer.toHexString(propId),
- Integer.toHexString(areaId)));
+ throw new IllegalArgumentException(getValueErrorMessage("get", requestedPropValue));
}
if (StatusCode.OK != status || valueWrapper.object == null) {
@@ -131,11 +145,7 @@
if (StatusCode.OK == status) {
status = StatusCode.NOT_AVAILABLE;
}
- Log.i(CarLog.TAG_HAL, String.format(
- "Failed to get property: 0x%s, areaId: 0x%s, code: %d",
- Integer.toHexString(requestedPropValue.prop),
- Integer.toHexString(requestedPropValue.areaId),
- status));
+ Log.e(TAG, getPropertyErrorMessage("get", requestedPropValue, status));
throw new ServiceSpecificException(status,
"Failed to get property: 0x" + Integer.toHexString(requestedPropValue.prop)
+ " in areaId: 0x" + Integer.toHexString(requestedPropValue.areaId));
@@ -153,7 +163,7 @@
result.propValue = propValue;
});
} catch (RemoteException e) {
- Log.e(CarLog.TAG_HAL, "Failed to get value from vehicle HAL", e);
+ Log.e(TAG, getValueErrorMessage("get", requestedPropValue), e);
result.status = StatusCode.TRY_AGAIN;
}
@@ -169,28 +179,35 @@
int status = callback.action();
long startTime = elapsedRealtime();
while (StatusCode.TRY_AGAIN == status && (elapsedRealtime() - startTime) < timeoutMs) {
+ if (DEBUG) {
+ Log.d(TAG, "Status before sleeping " + sleepMs + "ms: "
+ + StatusCode.toString(status));
+ }
try {
Thread.sleep(sleepMs);
} catch (InterruptedException e) {
- Log.e(CarLog.TAG_HAL, "Thread was interrupted while waiting for vehicle HAL.", e);
+ Thread.currentThread().interrupt();
+ Log.e(TAG, "Thread was interrupted while waiting for vehicle HAL.", e);
break;
}
status = callback.action();
+ if (DEBUG) Log.d(TAG, "Status after waking up: " + StatusCode.toString(status));
}
+ if (DEBUG) Log.d(TAG, "Returning status: " + StatusCode.toString(status));
return status;
}
- private static class ObjectWrapper<T> {
+ private static final class ObjectWrapper<T> {
T object;
}
- private static class ValueResult {
+ private static final class ValueResult {
int status;
VehiclePropValue propValue;
}
- private static class PropertySetError {
+ private static final class PropertySetError {
final int errorCode;
final int propId;
final int areaId;
@@ -218,7 +235,7 @@
public void handleMessage(Message msg) {
IVehicleCallback callback = mCallback.get();
if (callback == null) {
- Log.i(CarLog.TAG_HAL, "handleMessage null callback");
+ Log.i(TAG, "handleMessage null callback");
return;
}
@@ -235,16 +252,16 @@
callback.onPropertySetError(obj.errorCode, obj.propId, obj.areaId);
break;
default:
- Log.e(CarLog.TAG_HAL, "Unexpected message: " + msg.what);
+ Log.e(TAG, "Unexpected message: " + msg.what);
}
} catch (RemoteException e) {
- Log.e(CarLog.TAG_HAL, "Message failed: " + msg.what);
+ Log.e(TAG, "Message failed: " + msg.what);
}
}
}
- private static class VehicleCallback extends IVehicleCallback.Stub {
- private Handler mHandler;
+ private static final class VehicleCallback extends IVehicleCallback.Stub {
+ private final Handler mHandler;
VehicleCallback(Handler handler) {
mHandler = handler;
diff --git a/service/src/com/android/car/hal/UserHalService.java b/service/src/com/android/car/hal/UserHalService.java
index 7b81e0a..ea5a2e8 100644
--- a/service/src/com/android/car/hal/UserHalService.java
+++ b/service/src/com/android/car/hal/UserHalService.java
@@ -23,7 +23,9 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.car.hardware.property.CarPropertyManager;
+import android.car.user.CarUserManager;
import android.car.userlib.HalCallback;
import android.car.userlib.UserHalHelper;
import android.hardware.automotive.vehicle.V2_0.InitialUserInfoResponse;
@@ -272,11 +274,8 @@
requestId = mNextRequestId++;
EventLog.writeEvent(EventLogTags.CAR_USER_HAL_SWITCH_USER_REQ, requestId,
targetInfo.userId, timeoutMs);
- propRequest = UserHalHelper.createPropRequest(requestId,
- SwitchUserMessageType.ANDROID_SWITCH, SWITCH_USER);
- propRequest.value.int32Values.add(targetInfo.userId);
- propRequest.value.int32Values.add(targetInfo.flags);
- UserHalHelper.addUsersInfo(propRequest, usersInfo);
+ propRequest = getPropRequestForSwitchUserLocked(requestId,
+ SwitchUserMessageType.ANDROID_SWITCH, targetInfo, usersInfo);
addPendingRequestLocked(requestId, SwitchUserResponse.class, callback);
}
@@ -313,11 +312,8 @@
VehiclePropValue propRequest;
synchronized (mLock) {
checkSupportedLocked();
- propRequest = UserHalHelper.createPropRequest(requestId,
- SwitchUserMessageType.ANDROID_POST_SWITCH, SWITCH_USER);
- propRequest.value.int32Values.add(targetInfo.userId);
- propRequest.value.int32Values.add(targetInfo.flags);
- UserHalHelper.addUsersInfo(propRequest, usersInfo);
+ propRequest = getPropRequestForSwitchUserLocked(requestId,
+ SwitchUserMessageType.ANDROID_POST_SWITCH, targetInfo, usersInfo);
}
try {
@@ -329,6 +325,47 @@
}
/**
+ * Calls HAL to switch user after legacy Android user switch. Legacy Android user switch means
+ * user switch is not requested by {@link CarUserManager} or OEM, and user switch is directly
+ * requested by {@link ActivityManager}
+ *
+ * @param targetInfo target user info.
+ * @param usersInfo current state of Android users.
+ */
+ public void legacyUserSwitch(@NonNull UserInfo targetInfo, @NonNull UsersInfo usersInfo) {
+ if (DBG) Log.d(TAG, "userSwitchLegacy(" + targetInfo + ")");
+ Objects.requireNonNull(usersInfo);
+ // TODO(b/150413515): use helper method to check usersInfo is valid
+
+ VehiclePropValue propRequest;
+ synchronized (mLock) {
+ checkSupportedLocked();
+ int requestId = mNextRequestId++;
+ EventLog.writeEvent(EventLogTags.CAR_USER_HAL_LEGACY_SWITCH_USER_REQ, requestId,
+ targetInfo.userId, usersInfo.currentUser.userId);
+ propRequest = getPropRequestForSwitchUserLocked(requestId,
+ SwitchUserMessageType.LEGACY_ANDROID_SWITCH, targetInfo, usersInfo);
+ }
+
+ try {
+ if (DBG) Log.d(TAG, "Calling hal.set(): " + propRequest);
+ mHal.set(propRequest);
+ } catch (ServiceSpecificException e) {
+ Log.w(TAG, "Failed to set LEGACY ANDROID SWITCH", e);
+ }
+ }
+
+ private VehiclePropValue getPropRequestForSwitchUserLocked(int requestId, int requestType,
+ @NonNull UserInfo targetInfo, @NonNull UsersInfo usersInfo) {
+ VehiclePropValue propRequest =
+ UserHalHelper.createPropRequest(requestId, requestType, SWITCH_USER);
+ propRequest.value.int32Values.add(targetInfo.userId);
+ propRequest.value.int32Values.add(targetInfo.flags);
+ UserHalHelper.addUsersInfo(propRequest, usersInfo);
+ return propRequest;
+ }
+
+ /**
* Calls HAL to get the value of the user identifications associated with the given user.
*
* @return HAL response or {@code null} if it was invalid (for example, mismatch on the
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index f193815..2ae56cb 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -36,6 +36,7 @@
import android.car.user.GetUserIdentificationAssociationResponse;
import android.car.user.UserSwitchResult;
import android.car.userlib.CarUserManagerHelper;
+import android.car.userlib.CommonConstants.CarUserServiceConstants;
import android.car.userlib.HalCallback;
import android.car.userlib.UserHalHelper;
import android.content.Context;
@@ -103,13 +104,14 @@
private static final String TAG = TAG_USER;
/** {@code int} extra used to represent a user id in a {@link IResultReceiver} response. */
- public static final String BUNDLE_USER_ID = "user.id";
+ public static final String BUNDLE_USER_ID = CarUserServiceConstants.BUNDLE_USER_ID;
/** {@code int} extra used to represent user flags in a {@link IResultReceiver} response. */
- public static final String BUNDLE_USER_FLAGS = "user.flags";
+ public static final String BUNDLE_USER_FLAGS = CarUserServiceConstants.BUNDLE_USER_FLAGS;
/** {@code String} extra used to represent a user name in a {@link IResultReceiver} response. */
- public static final String BUNDLE_USER_NAME = "user.name";
+ public static final String BUNDLE_USER_NAME = CarUserServiceConstants.BUNDLE_USER_NAME;
/** {@code int} extra used to represent the info action {@link IResultReceiver} response. */
- public static final String BUNDLE_INITIAL_INFO_ACTION = "initial_info.action";
+ public static final String BUNDLE_INITIAL_INFO_ACTION =
+ CarUserServiceConstants.BUNDLE_INITIAL_INFO_ACTION;
private final Context mContext;
private final CarUserManagerHelper mCarUserManagerHelper;
@@ -178,6 +180,8 @@
private UserMetrics mUserMetrics;
+ private IResultReceiver mUserSwitchUiReceiver;
+
/** Interface for callbaks related to passenger activities. */
public interface PassengerCallback {
/** Called when passenger is started at a certain zone. */
@@ -248,6 +252,7 @@
writer.println("*CarUserService*");
String indent = " ";
handleDumpListeners(writer, indent);
+ writer.printf("User switch UI receiver %s\n", mUserSwitchUiReceiver);
synchronized (mLockUser) {
writer.println("User0Unlocked: " + mUser0Unlocked);
writer.println("BackgroundUsersToRestart: " + mBackgroundUsersToRestart);
@@ -820,6 +825,7 @@
try {
switched = mAm.switchUser(targetUserId);
if (switched) {
+ sendUserSwitchUiCallback(targetUserId);
resultStatus = UserSwitchResult.STATUS_SUCCESSFUL;
mRequestIdForUserSwitchInProcess = resp.requestId;
} else {
@@ -846,6 +852,20 @@
});
}
+ private void sendUserSwitchUiCallback(@UserIdInt int targetUserId) {
+ if (mUserSwitchUiReceiver == null) {
+ Log.w(TAG_USER, "No User switch UI receiver.");
+ return;
+ }
+
+ try {
+ EventLog.writeEvent(EventLogTags.CAR_USER_SVC_SWITCH_USER_UI_REQ, targetUserId);
+ mUserSwitchUiReceiver.send(targetUserId, null);
+ } catch (RemoteException e) {
+ Log.e(TAG_USER, "Error calling user switch UI receiver.", e);
+ }
+ }
+
@Override
public GetUserIdentificationAssociationResponse getUserIdentificationAssociation(int[] types) {
Preconditions.checkArgument(!ArrayUtils.isEmpty(types), "must have at least one type");
@@ -930,6 +950,19 @@
return mHal.isSupported();
}
+ /**
+ * Sets a callback which is invoked before user switch.
+ *
+ * <p>
+ * This method should only be called by the Car System UI. The purpose of this call is to notify
+ * Car System UI to show the user switch UI before the user switch.
+ */
+ @Override
+ public void setUserSwitchUiCallback(@NonNull IResultReceiver receiver) {
+ // TODO(b/154958003): check UID, only carSysUI should be allowed to set it.
+ mUserSwitchUiReceiver = receiver;
+ }
+
// TODO(b/144120654): use helper to generate UsersInfo
private UsersInfo getUsersInfo() {
UserInfo currentUser;
@@ -939,6 +972,11 @@
// shouldn't happen
throw new IllegalStateException("Could not get current user: ", e);
}
+ return getUsersInfo(currentUser);
+ }
+
+ // TODO(b/144120654): use helper to generate UsersInfo
+ private UsersInfo getUsersInfo(@NonNull UserInfo currentUser) {
List<UserInfo> existingUsers = mUserManager.getUsers();
int size = existingUsers.size();
@@ -1150,7 +1188,7 @@
// Handle special cases first...
if (eventType == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING) {
- onUserSwitching(userId);
+ onUserSwitching(fromUserId, toUserId);
} else if (eventType == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED) {
onUserUnlocked(userId);
}
@@ -1257,24 +1295,43 @@
t.traceEnd(); // notify-listeners-user-USERID-event-EVENT_TYPE
}
- private void onUserSwitching(@UserIdInt int userId) {
- Log.i(TAG_USER, "onSwitchUser() callback for user " + userId);
+ private void onUserSwitching(@UserIdInt int fromUserId, @UserIdInt int toUserId) {
+ Log.i(TAG_USER, "onSwitchUser() callback for user " + toUserId);
TimingsTraceLog t = new TimingsTraceLog(TAG_USER, Trace.TRACE_TAG_SYSTEM_SERVER);
- t.traceBegin("onUserSwitching-" + userId);
+ t.traceBegin("onUserSwitching-" + toUserId);
- if (!isSystemUser(userId)) {
- mCarUserManagerHelper.setLastActiveUser(userId);
+ // Switch HAL users if user switch is not requested by CarUserService
+ notifyHalLegacySwitch(fromUserId, toUserId);
+
+ if (!isSystemUser(toUserId)) {
+ mCarUserManagerHelper.setLastActiveUser(toUserId);
}
if (mLastPassengerId != UserHandle.USER_NULL) {
stopPassengerInternal(mLastPassengerId, false);
}
if (mEnablePassengerSupport && isPassengerDisplayAvailable()) {
setupPassengerUser();
- startFirstPassenger(userId);
+ startFirstPassenger(toUserId);
}
t.traceEnd();
}
+ private void notifyHalLegacySwitch(@UserIdInt int fromUserId, @UserIdInt int toUserId) {
+ synchronized (mLockUser) {
+ if (mUserIdForUserSwitchInProcess != UserHandle.USER_NULL) return;
+ }
+
+ // switch HAL user
+ UserInfo targetUser = mUserManager.getUserInfo(toUserId);
+ android.hardware.automotive.vehicle.V2_0.UserInfo halTargetUser =
+ new android.hardware.automotive.vehicle.V2_0.UserInfo();
+ halTargetUser.userId = targetUser.id;
+ halTargetUser.flags = UserHalHelper.convertFlags(targetUser);
+ UserInfo currentUser = mUserManager.getUserInfo(fromUserId);
+ UsersInfo usersInfo = getUsersInfo(currentUser);
+ mHal.legacyUserSwitch(halTargetUser, usersInfo);
+ }
+
/**
* Runs the given runnable when user 0 is unlocked. If user 0 is already unlocked, it is
* run inside this call.
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerTest.java
new file mode 100644
index 0000000..354446e
--- /dev/null
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/input/CarInputManagerTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.input;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.car.Car;
+import android.car.input.CarInputManager;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+
+/**
+ * This class contains security permission tests for the {@link CarInputManager}'s system APIs.
+ */
+@RunWith(AndroidJUnit4.class)
+public class CarInputManagerTest {
+ private Car mCar;
+
+ private CarInputManager mCarInputManager;
+
+ @Mock
+ private CarInputManager.CarInputCaptureCallback mMockedCallback;
+
+ @Before
+ public void setUp() throws Exception {
+ mCar = Car.createCar(
+ InstrumentationRegistry.getInstrumentation().getTargetContext());
+ assertThat(mCar).isNotNull();
+ mCarInputManager = (CarInputManager) mCar.getCarManager(Car.CAR_INPUT_SERVICE);
+ assertThat(mCarInputManager).isNotNull();
+ }
+
+ @After
+ public void tearDown() {
+ mCar.disconnect();
+ }
+
+ @Test
+ public void testEnableFeaturePermission() throws Exception {
+ assertThrows(SecurityException.class, () -> mCarInputManager.requestInputEventCapture(
+ mMockedCallback,
+ CarInputManager.TARGET_DISPLAY_TYPE_MAIN,
+ new int[]{CarInputManager.INPUT_TYPE_ROTARY_NAVIGATION}, 0));
+ }
+}
+
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeTest.java
new file mode 100644
index 0000000..06d2299
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.audio;
+
+import static android.media.AudioAttributes.USAGE_ALARM;
+import static android.media.AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE;
+import static android.media.AudioAttributes.USAGE_MEDIA;
+import static android.media.AudioAttributes.USAGE_NOTIFICATION;
+import static android.media.AudioAttributes.USAGE_VIRTUAL_SOURCE;
+import static android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION;
+import static android.telephony.TelephonyManager.CALL_STATE_IDLE;
+import static android.telephony.TelephonyManager.CALL_STATE_OFFHOOK;
+import static android.telephony.TelephonyManager.CALL_STATE_RINGING;
+
+import static com.android.car.audio.CarAudioContext.ALARM;
+import static com.android.car.audio.CarAudioContext.CALL;
+import static com.android.car.audio.CarAudioContext.CALL_RING;
+import static com.android.car.audio.CarAudioContext.NAVIGATION;
+import static com.android.car.audio.CarAudioService.DEFAULT_AUDIO_CONTEXT;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import android.media.AudioAttributes;
+import android.media.AudioAttributes.AttributeUsage;
+import android.media.AudioPlaybackConfiguration;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.car.audio.CarAudioContext.AudioContext;
+
+import com.google.common.collect.ImmutableList;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class CarVolumeTest {
+ @Test
+ public void getSuggestedAudioContext_withNoConfigurationsAndIdleTelephony_returnsDefault() {
+ @AudioContext int suggestedContext = CarVolume.getSuggestedAudioContext(new ArrayList<>(),
+ CALL_STATE_IDLE);
+
+ assertThat(suggestedContext).isEqualTo(CarAudioService.DEFAULT_AUDIO_CONTEXT);
+ }
+
+ @Test
+ public void getSuggestedAudioContext_withOneConfiguration_returnsAssociatedContext() {
+ List<AudioPlaybackConfiguration> configurations = ImmutableList.of(
+ new Builder().setUsage(USAGE_ALARM).build()
+ );
+
+ @AudioContext int suggestedContext = CarVolume.getSuggestedAudioContext(configurations,
+ CALL_STATE_IDLE);
+
+ assertThat(suggestedContext).isEqualTo(ALARM);
+ }
+
+ @Test
+ public void getSuggestedAudioContext_withCallStateOffHook_returnsCallContext() {
+ @AudioContext int suggestedContext = CarVolume.getSuggestedAudioContext(new ArrayList<>(),
+ CALL_STATE_OFFHOOK);
+
+ assertThat(suggestedContext).isEqualTo(CALL);
+ }
+
+ @Test
+ public void getSuggestedAudioContext_withCallStateRinging_returnsCallRingContext() {
+ @AudioContext int suggestedContext = CarVolume.getSuggestedAudioContext(new ArrayList<>(),
+ CALL_STATE_RINGING);
+
+ assertThat(suggestedContext).isEqualTo(CALL_RING);
+ }
+
+ @Test
+ public void getSuggestedAudioContext_withConfigurations_returnsHighestPriorityContext() {
+ List<AudioPlaybackConfiguration> configurations = ImmutableList.of(
+ new Builder().setUsage(USAGE_ALARM).build(),
+ new Builder().setUsage(USAGE_VOICE_COMMUNICATION).build(),
+ new Builder().setUsage(USAGE_NOTIFICATION).build()
+ );
+
+ @AudioContext int suggestedContext = CarVolume.getSuggestedAudioContext(configurations,
+ CALL_STATE_IDLE);
+
+ assertThat(suggestedContext).isEqualTo(CALL);
+ }
+
+ @Test
+ public void getSuggestedAudioContext_ignoresInactiveConfigurations() {
+ List<AudioPlaybackConfiguration> configurations = ImmutableList.of(
+ new Builder().setUsage(USAGE_ALARM).build(),
+ new Builder().setUsage(USAGE_VOICE_COMMUNICATION).setInactive().build(),
+ new Builder().setUsage(USAGE_NOTIFICATION).build()
+ );
+
+ @AudioContext int suggestedContext = CarVolume.getSuggestedAudioContext(configurations,
+ CALL_STATE_IDLE);
+
+ assertThat(suggestedContext).isEqualTo(ALARM);
+ }
+
+ @Test
+ public void getSuggestedAudioContext_withLowerPriorityConfigurationsAndCall_returnsCall() {
+ List<AudioPlaybackConfiguration> configurations = ImmutableList.of(
+ new Builder().setUsage(USAGE_ALARM).build(),
+ new Builder().setUsage(USAGE_NOTIFICATION).build()
+ );
+
+ @AudioContext int suggestedContext = CarVolume.getSuggestedAudioContext(configurations,
+ CALL_STATE_OFFHOOK);
+
+ assertThat(suggestedContext).isEqualTo(CALL);
+ }
+
+ @Test
+ public void getSuggestedAudioContext_withNavigationConfigurationAndCall_returnsNavigation() {
+ List<AudioPlaybackConfiguration> configurations = ImmutableList.of(
+ new Builder().setUsage(USAGE_ASSISTANCE_NAVIGATION_GUIDANCE).build()
+ );
+
+ @AudioContext int suggestedContext = CarVolume.getSuggestedAudioContext(configurations,
+ CALL_STATE_OFFHOOK);
+
+ assertThat(suggestedContext).isEqualTo(NAVIGATION);
+ }
+
+ @Test
+ public void getSuggestedAudioContext_withUnprioritizedUsage_returnsDefault() {
+ List<AudioPlaybackConfiguration> configurations = ImmutableList.of(
+ new Builder().setUsage(USAGE_VIRTUAL_SOURCE).build()
+ );
+
+ @AudioContext int suggestedContext = CarVolume.getSuggestedAudioContext(configurations,
+ CALL_STATE_IDLE);
+
+ assertThat(suggestedContext).isEqualTo(DEFAULT_AUDIO_CONTEXT);
+ }
+
+ private static class Builder {
+ private @AttributeUsage int mUsage = USAGE_MEDIA;
+ private boolean mIsActive = true;
+
+ Builder setUsage(@AttributeUsage int usage) {
+ mUsage = usage;
+ return this;
+ }
+
+ Builder setInactive() {
+ mIsActive = false;
+ return this;
+ }
+
+ AudioPlaybackConfiguration build() {
+ AudioPlaybackConfiguration configuration = mock(AudioPlaybackConfiguration.class);
+ AudioAttributes attributes = new AudioAttributes.Builder().setUsage(mUsage).build();
+ when(configuration.getAudioAttributes()).thenReturn(attributes);
+ when(configuration.isActive()).thenReturn(mIsActive);
+ return configuration;
+ }
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/HalClientUnitTest.java b/tests/carservice_unit_test/src/com/android/car/hal/HalClientUnitTest.java
new file mode 100644
index 0000000..8b18119
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/hal/HalClientUnitTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.car.hal;
+
+import static android.car.test.mocks.CarArgumentMatchers.isProperty;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.expectThrows;
+
+import android.car.test.mocks.AbstractExtendedMockitoTestCase;
+import android.hardware.automotive.vehicle.V2_0.IVehicle;
+import android.hardware.automotive.vehicle.V2_0.IVehicleCallback;
+import android.hardware.automotive.vehicle.V2_0.StatusCode;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+
+public final class HalClientUnitTest extends AbstractExtendedMockitoTestCase {
+
+ private static final int WAIT_CAP_FOR_RETRIABLE_RESULT_MS = 100;
+ private static final int SLEEP_BETWEEN_RETRIABLE_INVOKES_MS = 50;
+
+ private static final int PROP = 42;
+ private static final int AREA_ID = 108;
+
+ private final VehiclePropValue mProp = new VehiclePropValue();
+
+ @Mock IVehicle mIVehicle;
+ @Mock IVehicleCallback mIVehicleCallback;
+
+ private HalClient mClient;
+
+ @Before
+ public void setFixtures() {
+ mClient = new HalClient(mIVehicle, Looper.getMainLooper(), mIVehicleCallback,
+ WAIT_CAP_FOR_RETRIABLE_RESULT_MS, SLEEP_BETWEEN_RETRIABLE_INVOKES_MS);
+ mProp.prop = PROP;
+ mProp.areaId = AREA_ID;
+ }
+
+ @Test
+ public void testSet_remoteExceptionThenFail() throws Exception {
+ when(mIVehicle.set(isProperty(PROP)))
+ .thenThrow(new RemoteException("Never give up, never surrender!"))
+ .thenThrow(new RemoteException("D'OH!"));
+
+ Exception actualException = expectThrows(ServiceSpecificException.class,
+ () -> mClient.setValue(mProp));
+
+ assertThat(actualException).hasMessageThat().contains(Integer.toHexString(PROP));
+ assertThat(actualException).hasMessageThat().contains(Integer.toHexString(AREA_ID));
+ }
+
+ @Test
+ public void testSet_remoteExceptionThenOk() throws Exception {
+ when(mIVehicle.set(isProperty(PROP)))
+ .thenThrow(new RemoteException("Never give up, never surrender!"))
+ .thenReturn(StatusCode.OK);
+
+ mClient.setValue(mProp);
+ }
+
+ @Test
+ public void testSet_invalidArgument() throws Exception {
+ when(mIVehicle.set(isProperty(PROP))).thenReturn(StatusCode.INVALID_ARG);
+
+ Exception actualException = expectThrows(IllegalArgumentException.class,
+ () -> mClient.setValue(mProp));
+
+ assertThat(actualException).hasMessageThat().contains(Integer.toHexString(PROP));
+ assertThat(actualException).hasMessageThat().contains(Integer.toHexString(AREA_ID));
+ }
+
+ @Test
+ public void testSet_otherError() throws Exception {
+ when(mIVehicle.set(isProperty(PROP))).thenReturn(StatusCode.INTERNAL_ERROR);
+
+ Exception actualException = expectThrows(ServiceSpecificException.class,
+ () -> mClient.setValue(mProp));
+
+ assertThat(actualException).hasMessageThat().contains(Integer.toHexString(PROP));
+ assertThat(actualException).hasMessageThat().contains(Integer.toHexString(AREA_ID));
+ }
+
+ @Test
+ public void testSet_ok() throws Exception {
+ when(mIVehicle.set(isProperty(PROP))).thenReturn(StatusCode.OK);
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java b/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java
index 24821ba..71e51f3 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/UserHalServiceTest.java
@@ -465,7 +465,7 @@
callback.assertCalled();
// Make sure the arguments were properly converted
- assertSwitchUserSetRequest(reqCaptor.get(), SwitchUserMessageType.ANDROID_SWITCH, mUser10);
+ assertHALSetRequest(reqCaptor.get(), SwitchUserMessageType.ANDROID_SWITCH, mUser10);
// Assert response
assertCallbackStatus(callback, HalCallback.STATUS_WRONG_HAL_RESPONSE);
@@ -488,7 +488,7 @@
callback.assertCalled();
// Make sure the arguments were properly converted
- assertSwitchUserSetRequest(reqCaptor.get(), SwitchUserMessageType.ANDROID_SWITCH, mUser10);
+ assertHALSetRequest(reqCaptor.get(), SwitchUserMessageType.ANDROID_SWITCH, mUser10);
// Assert response
assertCallbackStatus(callback, HalCallback.STATUS_OK);
@@ -513,7 +513,7 @@
callback.assertCalled();
// Make sure the arguments were properly converted
- assertSwitchUserSetRequest(reqCaptor.get(), SwitchUserMessageType.ANDROID_SWITCH, mUser10);
+ assertHALSetRequest(reqCaptor.get(), SwitchUserMessageType.ANDROID_SWITCH, mUser10);
// Assert response
assertCallbackStatus(callback, HalCallback.STATUS_OK);
@@ -556,7 +556,7 @@
callback.assertCalled();
// Make sure the arguments were properly converted
- assertSwitchUserSetRequest(reqCaptor.get(), SwitchUserMessageType.ANDROID_SWITCH, mUser10);
+ assertHALSetRequest(reqCaptor.get(), SwitchUserMessageType.ANDROID_SWITCH, mUser10);
// Assert response
assertCallbackStatus(callback, HalCallback.STATUS_WRONG_HAL_RESPONSE);
@@ -576,7 +576,24 @@
ArgumentCaptor.forClass(VehiclePropValue.class);
verify(mVehicleHal).set(propCaptor.capture());
VehiclePropValue prop = propCaptor.getValue();
- assertPostSwitchResponseSetRequest(prop, SwitchUserMessageType.ANDROID_POST_SWITCH,
+ assertHALSetRequest(prop, SwitchUserMessageType.ANDROID_POST_SWITCH,
+ mUser10);
+ }
+
+ @Test
+ public void testUserSwitchLegacy_noUsersInfo() {
+ assertThrows(NullPointerException.class,
+ () -> mUserHalService.legacyUserSwitch(mUser10, null));
+ }
+
+ @Test
+ public void testUserSwitchLegacy_HalCalledWithCorrectProp() {
+ mUserHalService.legacyUserSwitch(mUser10, mUsersInfo);
+ ArgumentCaptor<VehiclePropValue> propCaptor =
+ ArgumentCaptor.forClass(VehiclePropValue.class);
+ verify(mVehicleHal).set(propCaptor.capture());
+ VehiclePropValue prop = propCaptor.getValue();
+ assertHALSetRequest(prop, SwitchUserMessageType.LEGACY_ANDROID_SWITCH,
mUser10);
}
@@ -779,17 +796,7 @@
assertUsersInfo(req, mUsersInfo, 2);
}
- private void assertSwitchUserSetRequest(VehiclePropValue req, int messageType,
- UserInfo targetUserInfo) {
- assertThat(req.value.int32Values.get(1)).isEqualTo(messageType);
- assertWithMessage("targetuser.id mismatch").that(req.value.int32Values.get(2))
- .isEqualTo(targetUserInfo.userId);
- assertWithMessage("targetuser.flags mismatch").that(req.value.int32Values.get(3))
- .isEqualTo(targetUserInfo.flags);
- assertUsersInfo(req, mUsersInfo, 4);
- }
-
- private void assertPostSwitchResponseSetRequest(VehiclePropValue req, int messageType,
+ private void assertHALSetRequest(VehiclePropValue req, int messageType,
UserInfo targetUserInfo) {
assertThat(req.value.int32Values.get(1)).isEqualTo(messageType);
assertWithMessage("targetuser.id mismatch").that(req.value.int32Values.get(2))
diff --git a/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceControllerTest.java b/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceControllerTest.java
index ff1abb9..3be64a5 100644
--- a/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceControllerTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/pm/VendorServiceControllerTest.java
@@ -174,6 +174,9 @@
// Switch user to foreground
mockGetCurrentUser(FG_USER_ID);
+ // TODO(b/155918094): Update this test,
+ UserInfo nullUser = new UserInfo(UserHandle.USER_NULL, "null user", 0);
+ when(mUserManager.getUserInfo(UserHandle.USER_NULL)).thenReturn(nullUser);
sendUserLifecycleEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING, FG_USER_ID);
// Expect only services with ASAP trigger to be started
diff --git a/tests/carservice_unit_test/src/com/android/car/user/CarUserManagerUnitTest.java b/tests/carservice_unit_test/src/com/android/car/user/CarUserManagerUnitTest.java
index 23e1038..abfd48d 100644
--- a/tests/carservice_unit_test/src/com/android/car/user/CarUserManagerUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/user/CarUserManagerUnitTest.java
@@ -22,11 +22,13 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.notNull;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
@@ -37,6 +39,7 @@
import android.car.ICarUserService;
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
import android.car.user.CarUserManager;
+import android.car.user.CarUserManager.UserSwitchUiCallback;
import android.car.user.GetUserIdentificationAssociationResponse;
import android.car.user.UserSwitchResult;
import android.content.pm.UserInfo;
@@ -140,6 +143,20 @@
}
@Test
+ public void testSetSwitchUserUICallback_success() throws Exception {
+ UserSwitchUiCallback callback = (u)-> { };
+
+ mMgr.setUserSwitchUiCallback(callback);
+
+ verify(mService).setUserSwitchUiCallback(any());
+ }
+
+ @Test
+ public void testSetSwitchUserUICallback_nullCallback() throws Exception {
+ assertThrows(IllegalArgumentException.class, () -> mMgr.setUserSwitchUiCallback(null));
+ }
+
+ @Test
public void testGetUserIdentificationAssociation_nullTypes() throws Exception {
assertThrows(IllegalArgumentException.class,
() -> mMgr.getUserIdentificationAssociation(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 73a6296..f52e48d 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
@@ -93,6 +93,7 @@
import com.android.car.hal.UserHalService;
import com.android.internal.R;
import com.android.internal.infra.AndroidFuture;
+import com.android.internal.os.IResultReceiver;
import com.android.internal.util.Preconditions;
import org.junit.Before;
@@ -139,6 +140,7 @@
@Mock private Resources mMockedResources;
@Mock private Drawable mMockedDrawable;
@Mock private UserMetrics mUserMetrics;
+ @Mock IResultReceiver mSwitchUserUiReceiver;
private final BlockingUserLifecycleListener mUserLifecycleListener =
BlockingUserLifecycleListener.newDefaultListener();
@@ -219,13 +221,14 @@
public void testOnUserLifecycleEvent_nofityListener() throws Exception {
// Arrange
mCarUserService.addUserLifecycleListener(mUserLifecycleListener);
+ mockExistingUsers();
// Act
- int userId = 11;
- sendUserLifecycleEvent(userId, CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
+ sendUserSwitchingEvent(mAdminUser.id, mRegularUser.id);
// Verify
- verifyListenerOnEventInvoked(userId, CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
+ verifyListenerOnEventInvoked(mRegularUser.id,
+ CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
}
@Test
@@ -236,16 +239,17 @@
doThrow(new RuntimeException("Failed onEvent invocation")).when(
failureListener).onEvent(any(UserLifecycleEvent.class));
mCarUserService.addUserLifecycleListener(failureListener);
+ mockExistingUsers();
// Adding the non-failure listener later.
mCarUserService.addUserLifecycleListener(mUserLifecycleListener);
// Act
- int userId = 11;
- sendUserLifecycleEvent(userId, CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
+ sendUserSwitchingEvent(mAdminUser.id, mRegularUser.id);
// Verify
- verifyListenerOnEventInvoked(userId, CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
+ verifyListenerOnEventInvoked(mRegularUser.id,
+ CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
}
private void verifyListenerOnEventInvoked(int expectedNewUserId, int expectedEventType)
@@ -270,14 +274,10 @@
* Test that the {@link CarUserService} updates last active user on user switch.
*/
@Test
- public void testLastActiveUserUpdatedOnUserSwitch() {
- int lastActiveUserId = 99;
- UserInfo persistentUser = new UserInfo(lastActiveUserId, "persistent user",
- NO_USER_INFO_FLAGS);
- doReturn(persistentUser).when(mMockedUserManager).getUserInfo(lastActiveUserId);
- sendUserSwitchingEvent(lastActiveUserId);
-
- verify(mMockedCarUserManagerHelper).setLastActiveUser(lastActiveUserId);
+ public void testLastActiveUserUpdatedOnUserSwitch() throws Exception {
+ mockExistingUsers();
+ sendUserSwitchingEvent(mAdminUser.id, mRegularUser.id);
+ verify(mMockedCarUserManagerHelper).setLastActiveUser(mRegularUser.id);
}
/**
@@ -908,6 +908,51 @@
}
@Test
+ public void testHalUserSwitchOnAndroidSwitch_successfulNoExitingUserSwitch() {
+ mockExistingUsers();
+
+ sendUserSwitchingEvent(mAdminUser.id, mRegularUser.id);
+
+ ArgumentCaptor<android.hardware.automotive.vehicle.V2_0.UserInfo> targetUser =
+ ArgumentCaptor.forClass(android.hardware.automotive.vehicle.V2_0.UserInfo.class);
+ ArgumentCaptor<UsersInfo> usersInfo = ArgumentCaptor.forClass(UsersInfo.class);
+ verify(mUserHal).legacyUserSwitch(targetUser.capture(), usersInfo.capture());
+ assertThat(targetUser.getValue().userId).isEqualTo(mRegularUser.id);
+ assertThat(usersInfo.getValue().currentUser.userId).isEqualTo(mAdminUser.id);
+ }
+
+ @Test
+ public void testHalUserSwitchOnAndroidSwitch_failureExitingUserSwitch() throws Exception {
+ mockExistingUsersAndCurrentUser(mAdminUser);
+ int requestId = 42;
+ mSwitchUserResponse.status = SwitchUserStatus.SUCCESS;
+ mSwitchUserResponse.requestId = requestId;
+ mockHalSwitch(mAdminUser.id, mGuestUser, mSwitchUserResponse);
+ mockAmSwitchUser(mGuestUser, true);
+ mCarUserService.switchUser(mGuestUser.id, mAsyncCallTimeoutMs, mUserSwitchFuture);
+
+ sendUserSwitchingEvent(mAdminUser.id, mGuestUser.id);
+
+ verify(mUserHal, never()).legacyUserSwitch(any(), any());
+ }
+
+ @Test
+ public void testSetSwitchUserUI_receiverSetAndCalled() throws Exception {
+ mockExistingUsersAndCurrentUser(mAdminUser);
+ int requestId = 42;
+ mSwitchUserResponse.status = SwitchUserStatus.SUCCESS;
+ mSwitchUserResponse.requestId = requestId;
+ mockHalSwitch(mAdminUser.id, mGuestUser, mSwitchUserResponse);
+ mockAmSwitchUser(mGuestUser, true);
+
+ mCarUserService.setUserSwitchUiCallback(mSwitchUserUiReceiver);
+ mCarUserService.switchUser(mGuestUser.id, mAsyncCallTimeoutMs, mUserSwitchFuture);
+
+ // update current user due to successful user switch
+ verify(mSwitchUserUiReceiver).send(mGuestUser.id, null);
+ }
+
+ @Test
public void testGetUserInfo_nullReceiver() throws Exception {
assertThrows(NullPointerException.class, () -> mCarUserService
.getInitialUserInfo(mGetUserInfoRequestType, mAsyncCallTimeoutMs, null));
@@ -1091,12 +1136,12 @@
}
@Test
- public void testUserMetric_SendEvent() {
- int userId = 99;
- sendUserSwitchingEvent(userId);
+ public void testUserMetric_SendEvent() throws Exception {
+ mockExistingUsersAndCurrentUser(mAdminUser);
+ sendUserSwitchingEvent(mAdminUser.id, mRegularUser.id);
verify(mUserMetrics).onEvent(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
- 0, UserHandle.USER_NULL, userId);
+ 0, mAdminUser.id, mRegularUser.id);
}
@Test
@@ -1419,18 +1464,19 @@
}
}
- private void sendUserLifecycleEvent(@UserIdInt int userId,
+ private void sendUserLifecycleEvent(@UserIdInt int fromUserId, @UserIdInt int toUserId,
@UserLifecycleEventType int eventType) {
- mCarUserService.onUserLifecycleEvent(eventType, /* timestampMs= */ 0,
- /* fromUserId= */ UserHandle.USER_NULL, userId);
+ mCarUserService.onUserLifecycleEvent(eventType, /* timestampMs= */ 0, fromUserId, toUserId);
}
private void sendUserUnlockedEvent(@UserIdInt int userId) {
- sendUserLifecycleEvent(userId, CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED);
+ sendUserLifecycleEvent(/* fromUser */ 0, userId,
+ CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED);
}
- private void sendUserSwitchingEvent(@UserIdInt int userId) {
- sendUserLifecycleEvent(userId, CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
+ private void sendUserSwitchingEvent(@UserIdInt int fromUserId, @UserIdInt int toUserId) {
+ sendUserLifecycleEvent(fromUserId, toUserId,
+ CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING);
}
@NonNull
diff --git a/user/car-user-lib/src/android/car/userlib/CommonConstants.java b/user/car-user-lib/src/android/car/userlib/CommonConstants.java
new file mode 100644
index 0000000..ef9a354
--- /dev/null
+++ b/user/car-user-lib/src/android/car/userlib/CommonConstants.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.car.userlib;
+
+/**
+ * Provides constants used by both CarService and CarServiceHelper packages.
+ */
+public final class CommonConstants {
+
+ private CommonConstants() {
+ throw new UnsupportedOperationException("contains only static constants");
+ }
+
+ /**
+ * Constants used on {@link android.os.Bundle bundles} sent by
+ * {@link com.android.car.user.CarUserService} on binder calls.
+ */
+ public static final class CarUserServiceConstants {
+
+ public static final String BUNDLE_USER_ID = "user.id";
+ public static final String BUNDLE_USER_FLAGS = "user.flags";
+ public static final String BUNDLE_USER_NAME = "user.name";
+ public static final String BUNDLE_INITIAL_INFO_ACTION = "initial_info.action";
+
+ private CarUserServiceConstants() {
+ throw new UnsupportedOperationException("contains only static constants");
+ }
+ }
+}
diff --git a/watchdog/server/src/IoPerfCollection.cpp b/watchdog/server/src/IoPerfCollection.cpp
index a97858b..2799417 100644
--- a/watchdog/server/src/IoPerfCollection.cpp
+++ b/watchdog/server/src/IoPerfCollection.cpp
@@ -55,6 +55,7 @@
using android::base::Result;
using android::base::Split;
using android::base::StringAppendF;
+using android::base::StringPrintf;
using android::base::WriteStringToFd;
using android::content::pm::IPackageManagerNative;
@@ -76,6 +77,24 @@
const std::string kDumpMajorDelimiter = std::string(100, '-') + "\n";
+constexpr const char* kHelpText =
+ "\nCustom I/O performance data collection dump options:\n"
+ "%s: Starts custom I/O performance data collection. Customize the collection behavior with "
+ "the following optional arguments:\n"
+ "\t%s <seconds>: Modifies the collection interval. Default behavior is to collect once "
+ "every %lld seconds.\n"
+ "\t%s <seconds>: Modifies the maximum collection duration. Default behavior is to collect "
+ "until %ld minutes before automatically stopping the custom collection and discarding "
+ "the collected data.\n"
+ "\t%s <package name>,<package, name>,...: Comma-separated value containing package names. "
+ "When provided, the results are filtered only to the provided package names. Default "
+ "behavior is to list the results for the top %d packages.\n"
+ "%s: Stops custom I/O performance data collection and generates a dump of "
+ "the collection report.\n\n"
+ "When no options are specified, the carwatchdog report contains the I/O performance "
+ "data collected during boot-time and over the last %ld minutes before the report "
+ "generation.";
+
double percentage(uint64_t numer, uint64_t denom) {
return denom == 0 ? 0.0 : (static_cast<double>(numer) / static_cast<double>(denom)) * 100.0;
}
@@ -406,8 +425,8 @@
if (args[0] == String16(kStartCustomCollectionFlag)) {
if (args.size() > 7) {
- return Error(INVALID_OPERATION) << "Number of arguments to start custom "
- << "I/O performance data collection cannot exceed 7";
+ return Error(BAD_VALUE) << "Number of arguments to start custom I/O performance data "
+ << "collection cannot exceed 7";
}
std::chrono::nanoseconds interval = kCustomCollectionInterval;
std::chrono::nanoseconds maxDuration = kCustomCollectionDuration;
@@ -416,7 +435,7 @@
if (args[i] == String16(kIntervalFlag)) {
const auto& ret = parseSecondsFlag(args, i + 1);
if (!ret) {
- return Error(FAILED_TRANSACTION)
+ return Error(BAD_VALUE)
<< "Failed to parse " << kIntervalFlag << ": " << ret.error();
}
interval = std::chrono::duration_cast<std::chrono::nanoseconds>(*ret);
@@ -426,7 +445,7 @@
if (args[i] == String16(kMaxDurationFlag)) {
const auto& ret = parseSecondsFlag(args, i + 1);
if (!ret) {
- return Error(FAILED_TRANSACTION)
+ return Error(BAD_VALUE)
<< "Failed to parse " << kMaxDurationFlag << ": " << ret.error();
}
maxDuration = std::chrono::duration_cast<std::chrono::nanoseconds>(*ret);
@@ -435,7 +454,7 @@
}
if (args[i] == String16(kFilterPackagesFlag)) {
if (args.size() < i + 1) {
- return Error(FAILED_TRANSACTION)
+ return Error(BAD_VALUE)
<< "Must provide value for '" << kFilterPackagesFlag << "' flag";
}
std::vector<std::string> packages =
@@ -447,12 +466,13 @@
}
ALOGW("Unknown flag %s provided to start custom I/O performance data collection",
String8(args[i]).string());
- return Error(INVALID_OPERATION) << "Unknown flag " << String8(args[i]).string()
- << " provided to start custom I/O performance data "
- << "collection";
+ return Error(BAD_VALUE) << "Unknown flag " << String8(args[i]).string()
+ << " provided to start custom I/O performance data "
+ << "collection";
}
const auto& ret = startCustomCollection(interval, maxDuration, filterPackages);
if (!ret) {
+ WriteStringToFd(ret.error().message(), fd);
return ret;
}
return {};
@@ -460,19 +480,37 @@
if (args[0] == String16(kEndCustomCollectionFlag)) {
if (args.size() != 1) {
- ALOGW("Number of arguments to end custom I/O performance data collection cannot "
- "exceed 1");
+ ALOGW("Number of arguments to stop custom I/O performance data collection cannot "
+ "exceed 1. Stopping the data collection.");
+ WriteStringToFd("Number of arguments to stop custom I/O performance data collection "
+ "cannot exceed 1. Stopping the data collection.",
+ fd);
}
- const auto& ret = endCustomCollection(fd);
- if (!ret) {
- return ret;
- }
- return {};
+ return endCustomCollection(fd);
}
- return Error(INVALID_OPERATION)
- << "Dump arguments start neither with " << kStartCustomCollectionFlag << " nor with "
- << kEndCustomCollectionFlag << " flags";
+ return Error(BAD_VALUE) << "I/O perf collection dump arguments start neither with "
+ << kStartCustomCollectionFlag << " nor with "
+ << kEndCustomCollectionFlag << " flags";
+}
+
+bool IoPerfCollection::dumpHelpText(int fd) {
+ long periodicCacheMinutes =
+ (std::chrono::duration_cast<std::chrono::seconds>(mPeriodicCollection.interval)
+ .count() *
+ mPeriodicCollection.maxCacheSize) /
+ 60;
+ return WriteStringToFd(StringPrintf(kHelpText, kStartCustomCollectionFlag, kIntervalFlag,
+ std::chrono::duration_cast<std::chrono::seconds>(
+ kCustomCollectionInterval)
+ .count(),
+ kMaxDurationFlag,
+ std::chrono::duration_cast<std::chrono::minutes>(
+ kCustomCollectionDuration)
+ .count(),
+ kFilterPackagesFlag, mTopNStatsPerCategory,
+ kEndCustomCollectionFlag, periodicCacheMinutes),
+ fd);
}
Result<void> IoPerfCollection::dumpCollection(int fd) {
diff --git a/watchdog/server/src/IoPerfCollection.h b/watchdog/server/src/IoPerfCollection.h
index 9cde199..4cccb05 100644
--- a/watchdog/server/src/IoPerfCollection.h
+++ b/watchdog/server/src/IoPerfCollection.h
@@ -190,6 +190,9 @@
// Returns any error observed during the dump generation.
virtual android::base::Result<void> dump(int fd, const Vector<String16>& args);
+ // Dumps the help text.
+ bool dumpHelpText(int fd);
+
private:
// Generates a dump from the boot-time and periodic collection events.
android::base::Result<void> dumpCollection(int fd);
diff --git a/watchdog/server/src/WatchdogBinderMediator.cpp b/watchdog/server/src/WatchdogBinderMediator.cpp
index da4ea29..0a482fd 100644
--- a/watchdog/server/src/WatchdogBinderMediator.cpp
+++ b/watchdog/server/src/WatchdogBinderMediator.cpp
@@ -18,6 +18,7 @@
#include "WatchdogBinderMediator.h"
+#include <android-base/file.h>
#include <android-base/parseint.h>
#include <android-base/stringprintf.h>
#include <android/automotive/watchdog/BootPhase.h>
@@ -38,10 +39,19 @@
using android::base::ParseUint;
using android::base::Result;
using android::base::StringPrintf;
+using android::base::WriteStringToFd;
using android::binder::Status;
namespace {
+constexpr const char* kHelpFlag = "--help";
+constexpr const char* kHelpShortFlag = "-h";
+constexpr const char* kHelpText =
+ "CarWatchdog daemon dumpsys help page:\n"
+ "Format: dumpsys android.automotive.watchdog.ICarWatchdog/default [options]\n\n"
+ "%s or %s: Displays this help text.\n"
+ "When no options are specified, carwatchdog report is generated.\n";
+
Status checkSystemPermission() {
if (IPCThreadState::self()->getCallingUid() != AID_SYSTEM) {
return Status::fromExceptionCode(Status::EX_SECURITY,
@@ -94,21 +104,48 @@
}
return OK;
}
+
+ if (args[0] == String16(kHelpFlag) || args[0] == String16(kHelpShortFlag)) {
+ if (!dumpHelpText(fd, "")) {
+ ALOGW("Failed to write help text to fd");
+ return FAILED_TRANSACTION;
+ }
+ return OK;
+ }
+
if (args[0] == String16(kStartCustomCollectionFlag) ||
args[0] == String16(kEndCustomCollectionFlag)) {
auto ret = mIoPerfCollection->dump(fd, args);
- std::string mode = args[0] == String16(kStartCustomCollectionFlag) ? "start" : "end";
if (!ret.ok()) {
- ALOGW("Failed to %s custom I/O perf collection: %s", mode.c_str(),
- ret.error().message().c_str());
+ std::string mode = args[0] == String16(kStartCustomCollectionFlag) ? "start" : "end";
+ std::string errorMsg = StringPrintf("Failed to %s custom I/O perf collection: %s",
+ mode.c_str(), ret.error().message().c_str());
+ if (ret.error().code() == BAD_VALUE) {
+ dumpHelpText(fd, errorMsg);
+ } else {
+ ALOGW("%s", errorMsg.c_str());
+ }
return ret.error().code();
}
return OK;
}
- ALOGW("Invalid dump arguments");
+ dumpHelpText(fd, "Invalid dump arguments");
return INVALID_OPERATION;
}
+bool WatchdogBinderMediator::dumpHelpText(int fd, std::string errorMsg) {
+ if (!errorMsg.empty()) {
+ ALOGW("Error: %s", errorMsg.c_str());
+ if (!WriteStringToFd(StringPrintf("Error: %s\n\n", errorMsg.c_str()), fd)) {
+ ALOGW("Failed to write error message to fd");
+ return false;
+ }
+ }
+
+ return WriteStringToFd(StringPrintf(kHelpText, kHelpFlag, kHelpShortFlag), fd) &&
+ mIoPerfCollection->dumpHelpText(fd);
+}
+
Status WatchdogBinderMediator::registerMediator(const sp<ICarWatchdogClient>& mediator) {
Status status = checkSystemPermission();
if (!status.isOk()) {
diff --git a/watchdog/server/src/WatchdogBinderMediator.h b/watchdog/server/src/WatchdogBinderMediator.h
index a3b19a2..530659b 100644
--- a/watchdog/server/src/WatchdogBinderMediator.h
+++ b/watchdog/server/src/WatchdogBinderMediator.h
@@ -83,6 +83,8 @@
void binderDied(const android::wp<IBinder>& who) override {
return mWatchdogProcessService->binderDied(who);
}
+ bool dumpHelpText(int fd, std::string errorMsg);
+
android::sp<WatchdogProcessService> mWatchdogProcessService;
android::sp<IoPerfCollection> mIoPerfCollection;