Merge "Adding unit tests to BlockingAnswer" into sc-dev
diff --git a/car-lib/api/test-current.txt b/car-lib/api/test-current.txt
index b82b7b7..3be98c4 100644
--- a/car-lib/api/test-current.txt
+++ b/car-lib/api/test-current.txt
@@ -31,6 +31,7 @@
public final class CarDevicePolicyManager {
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.car.admin.CreateUserResult createUser(@Nullable String, int);
method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.car.admin.RemoveUserResult removeUser(@NonNull android.os.UserHandle);
+ method @NonNull @RequiresPermission(anyOf={android.Manifest.permission.MANAGE_USERS, android.Manifest.permission.CREATE_USERS}) public android.car.admin.StartUserInBackgroundResult startUserInBackground(@NonNull android.os.UserHandle);
field public static final int USER_TYPE_ADMIN = 1; // 0x1
field public static final int USER_TYPE_GUEST = 2; // 0x2
field public static final int USER_TYPE_REGULAR = 0; // 0x0
@@ -57,6 +58,15 @@
field public static final int STATUS_SUCCESS_SET_EPHEMERAL = 3; // 0x3
}
+ public final class StartUserInBackgroundResult {
+ method public int getStatus();
+ method public boolean isSuccess();
+ field public static final int STATUS_FAILURE_GENERIC = 100; // 0x64
+ field public static final int STATUS_FAILURE_USER_DOES_NOT_EXIST = 3; // 0x3
+ field public static final int STATUS_SUCCESS = 1; // 0x1
+ field public static final int STATUS_SUCCESS_CURRENT_USER = 2; // 0x2
+ }
+
}
package android.car.content.pm {
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index d74d44b..db85829 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -54,6 +54,7 @@
import android.car.navigation.CarNavigationStatusManager;
import android.car.occupantawareness.OccupantAwarenessManager;
import android.car.storagemonitoring.CarStorageMonitoringManager;
+import android.car.telemetry.CarTelemetryManager;
import android.car.test.CarTestManagerBinderWrapper;
import android.car.user.CarUserManager;
import android.car.vms.VmsClientManager;
@@ -364,6 +365,14 @@
@SystemApi
public static final String CAR_EVS_SERVICE = "car_evs_service";
+ /**
+ * Service name for {@link android.car.telemetry.CarTelemetryManager}
+ *
+ * @hide
+ */
+ @OptionalFeature
+ public static final String CAR_TELEMETRY_SERVICE = "car_telemetry_service";
+
/** Permission necessary to access car's mileage information.
* @hide
*/
@@ -816,6 +825,14 @@
"android.car.permission.MONITOR_CAR_EVS_STATUS";
/**
+ * Permission necessary to use the CarTelemetryService.
+ *
+ * @hide
+ */
+ public static final String PERMISSION_USE_CAR_TELEMETRY_SERVICE =
+ "android.car.permission.USE_CAR_TELEMETRY_SERVICE";
+
+ /**
* Type of car connection: platform runs directly in car.
*
* @deprecated connection type constants are no longer used
@@ -1880,6 +1897,9 @@
case CAR_EVS_SERVICE:
manager = new CarEvsManager(this, binder);
break;
+ case CAR_TELEMETRY_SERVICE:
+ manager = new CarTelemetryManager(this, binder);
+ break;
default:
// Experimental or non-existing
String className = null;
diff --git a/car-lib/src/android/car/admin/CarDevicePolicyManager.java b/car-lib/src/android/car/admin/CarDevicePolicyManager.java
index 4ed2833..e60d60d 100644
--- a/car-lib/src/android/car/admin/CarDevicePolicyManager.java
+++ b/car-lib/src/android/car/admin/CarDevicePolicyManager.java
@@ -28,6 +28,7 @@
import android.car.CarManagerBase;
import android.car.user.UserCreationResult;
import android.car.user.UserRemovalResult;
+import android.car.user.UserStartResult;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -203,6 +204,45 @@
}
}
+ /**
+ * Starts a user in the background.
+ *
+ * @param user identification of the user to be started.
+ *
+ * @return whether the user was successfully started.
+ *
+ * @hide
+ */
+ @TestApi
+ @RequiresPermission(anyOf = {android.Manifest.permission.MANAGE_USERS,
+ android.Manifest.permission.CREATE_USERS})
+ @NonNull
+ public StartUserInBackgroundResult startUserInBackground(@NonNull UserHandle user) {
+ Objects.requireNonNull(user, "user cannot be null");
+
+ int userId = user.getIdentifier();
+ int uid = myUid();
+ EventLog.writeEvent(EventLogTags.CAR_DP_MGR_START_USER_IN_BACKGROUND_REQ, uid, userId);
+ int status = StartUserInBackgroundResult.STATUS_FAILURE_GENERIC;
+ try {
+ AndroidFuture<UserStartResult> future = new AndroidFuture<>();
+ mService.startUserInBackground(userId, future);
+ UserStartResult result = future.get(DEVICE_POLICY_MANAGER_TIMEOUT_MS,
+ TimeUnit.MILLISECONDS);
+ status = result.getStatus();
+ return new StartUserInBackgroundResult(status);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ return new StartUserInBackgroundResult(status);
+ } catch (ExecutionException | TimeoutException e) {
+ return new StartUserInBackgroundResult(status);
+ } catch (RemoteException e) {
+ return handleRemoteExceptionFromCarService(e, new StartUserInBackgroundResult(status));
+ } finally {
+ EventLog.writeEvent(EventLogTags.CAR_DP_MGR_START_USER_IN_BACKGROUND_RESP, uid, status);
+ }
+ }
+
/** @hide */
@Override
public void onCarDisconnected() {
diff --git a/car-lib/src/android/car/admin/ICarDevicePolicyService.aidl b/car-lib/src/android/car/admin/ICarDevicePolicyService.aidl
index 3d9bec1..88b42b1 100644
--- a/car-lib/src/android/car/admin/ICarDevicePolicyService.aidl
+++ b/car-lib/src/android/car/admin/ICarDevicePolicyService.aidl
@@ -16,12 +16,16 @@
package android.car.admin;
-import android.car.user.UserRemovalResult;
import android.car.user.UserCreationResult;
+import android.car.user.UserRemovalResult;
+import android.car.user.UserStartResult;
+import android.car.user.UserStopResult;
import com.android.internal.infra.AndroidFuture;
/** @hide */
interface ICarDevicePolicyService {
void removeUser(int userId, in AndroidFuture<UserRemovalResult> receiver);
void createUser(String name, int flags, in AndroidFuture<UserCreationResult> receiver);
+ void startUserInBackground(int userId, in AndroidFuture<UserStartResult> receiver);
+ void stopUser(int userId, in AndroidFuture<UserStopResult> receiver);
}
diff --git a/car-lib/src/android/car/admin/StartUserInBackgroundResult.java b/car-lib/src/android/car/admin/StartUserInBackgroundResult.java
new file mode 100644
index 0000000..11d276e
--- /dev/null
+++ b/car-lib/src/android/car/admin/StartUserInBackgroundResult.java
@@ -0,0 +1,115 @@
+/*
+ * 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.admin;
+
+import android.annotation.IntDef;
+import android.annotation.TestApi;
+import android.car.user.UserStartResult;
+import android.util.DebugUtils;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Result of a {@link CarDevicePolicyManager#startUserInBackground operation.
+ *
+ * @hide
+ */
+@TestApi
+public final class StartUserInBackgroundResult {
+
+ /**
+ * User was started.
+ */
+ public static final int STATUS_SUCCESS = 1;
+
+ /**
+ * User was the current user.
+ */
+ public static final int STATUS_SUCCESS_CURRENT_USER = 2;
+
+ /**
+ * User was not started because it does not exist.
+ */
+ public static final int STATUS_FAILURE_USER_DOES_NOT_EXIST = 3;
+
+ /**
+ * User was not started for some other reason not described above.
+ */
+ public static final int STATUS_FAILURE_GENERIC = 100;
+
+ /** @hide */
+ @IntDef(prefix = "STATUS_", value = {
+ STATUS_SUCCESS,
+ STATUS_SUCCESS_CURRENT_USER,
+ STATUS_FAILURE_USER_DOES_NOT_EXIST,
+ STATUS_FAILURE_GENERIC
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface Status {
+ }
+
+ private final @Status int mStatus;
+
+ /** @hide */
+ @VisibleForTesting
+ public StartUserInBackgroundResult(@UserStartResult.Status int status) {
+ switch(status) {
+ case UserStartResult.STATUS_SUCCESSFUL:
+ mStatus = STATUS_SUCCESS;
+ break;
+ case UserStartResult.STATUS_SUCCESSFUL_USER_IS_CURRENT_USER:
+ mStatus = STATUS_SUCCESS_CURRENT_USER;
+ break;
+ case UserStartResult.STATUS_USER_DOES_NOT_EXIST:
+ mStatus = STATUS_FAILURE_USER_DOES_NOT_EXIST;
+ break;
+ default:
+ mStatus = STATUS_FAILURE_GENERIC;
+ }
+ }
+
+ /**
+ * Gets the specific result of the operation.
+ *
+ * @return either {@link StartUserInBackgroundResult#STATUS_SUCCESS},
+ * {@link StartUserInBackgroundResult#STATUS_SUCCESS_CURRENT_USER},
+ * {@link StartUserInBackgroundResult#STATUS_FAILURE_USER_DOES_NOT_EXIST}, or
+ * {@link StartUserInBackgroundResult#STATUS_FAILURE_GENERIC}.
+ */
+ public @Status int getStatus() {
+ return mStatus;
+ }
+
+ /**
+ * Gets whether the operation was successful or not.
+ */
+ public boolean isSuccess() {
+ return mStatus == STATUS_SUCCESS || mStatus == STATUS_SUCCESS_CURRENT_USER;
+ }
+
+ @Override
+ public String toString() {
+ return "StartUserInBackgroundResult[" + statusToString(mStatus) + "]";
+ }
+
+ private static String statusToString(int status) {
+ return DebugUtils.valueToString(StartUserInBackgroundResult.class, "STATUS_", status);
+ }
+}
diff --git a/car-lib/src/android/car/telemetry/CarTelemetryManager.java b/car-lib/src/android/car/telemetry/CarTelemetryManager.java
new file mode 100644
index 0000000..24d787b
--- /dev/null
+++ b/car-lib/src/android/car/telemetry/CarTelemetryManager.java
@@ -0,0 +1,172 @@
+/*
+ * 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.telemetry;
+
+import android.annotation.CallbackExecutor;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.car.Car;
+import android.car.CarManagerBase;
+import android.car.annotation.RequiredFeature;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.lang.ref.WeakReference;
+import java.util.concurrent.Executor;
+
+/**
+ * Provides an application interface for interacting with the Car Telemetry Service.
+ *
+ * @hide
+ */
+@RequiredFeature(Car.CAR_TELEMETRY_SERVICE)
+public final class CarTelemetryManager extends CarManagerBase {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = CarTelemetryManager.class.getSimpleName();
+
+ private final CarTelemetryServiceListener mCarTelemetryServiceListener =
+ new CarTelemetryServiceListener(this);
+ private final ICarTelemetryService mService;
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private CarTelemetryResultsListener mResultsListener;
+ @GuardedBy("mLock")
+ private Executor mExecutor;
+
+ /**
+ * Application registers {@link CarTelemetryResultsListener} object to receive data from
+ * {@link com.android.car.telemetry.CarTelemetryService}.
+ *
+ * @hide
+ */
+ public interface CarTelemetryResultsListener {
+ /**
+ * Called by {@link com.android.car.telemetry.CarTelemetryService} to send data to
+ * the client.
+ * TODO(b/184964661): Publish the documentation for the format of the results.
+ *
+ * @param data the serialized car telemetry results.
+ */
+ void onDataReceived(@NonNull byte[] data);
+ }
+
+ /**
+ * Class implementing the listener interface
+ * {@link com.android.car.ICarTelemetryServiceListener} to receive telemetry results.
+ */
+ private static final class CarTelemetryServiceListener
+ extends ICarTelemetryServiceListener.Stub {
+ private WeakReference<CarTelemetryManager> mManager;
+
+ private CarTelemetryServiceListener(CarTelemetryManager manager) {
+ mManager = new WeakReference<>(manager);
+ }
+
+ @Override
+ public void onDataReceived(@NonNull byte[] data) {
+ CarTelemetryManager manager = mManager.get();
+ if (manager == null) {
+ return;
+ }
+ manager.onDataReceived(data);
+ }
+ }
+
+ private void onDataReceived(byte[] data) {
+ synchronized (mLock) {
+ mExecutor.execute(() -> mResultsListener.onDataReceived(data));
+ }
+ }
+
+ /**
+ * Gets an instance of CarTelemetryManager.
+ *
+ * CarTelemetryManager manages {@link com.android.car.telemetry.CarTelemetryService} and
+ * provides APIs so the client can use the car telemetry service.
+ *
+ * There is only one client to this manager, which is OEM's cloud application. It uses the
+ * APIs to send config to and receive data from CarTelemetryService.
+ *
+ * @hide
+ */
+ public CarTelemetryManager(Car car, IBinder service) {
+ super(car);
+ mService = ICarTelemetryService.Stub.asInterface(service);
+ if (DEBUG) {
+ Slog.d(TAG, "starting car telemetry manager");
+ }
+ }
+
+ /** @hide */
+ @Override
+ public void onCarDisconnected() {
+ synchronized (mLock) {
+ mResultsListener = null;
+ mExecutor = null;
+ }
+ }
+
+ /**
+ * Registers a listener with {@link com.android.car.telemetry.CarTelemetryService} for client
+ * to receive script execution results.
+ *
+ * @param listener to received data from {@link com.android.car.telemetry.CarTelemetryService}.
+ * @throws IllegalStateException if the listener is already set.
+ *
+ * @hide
+ */
+ @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
+ public void setListener(@NonNull @CallbackExecutor Executor executor,
+ @NonNull CarTelemetryResultsListener listener) {
+ synchronized (mLock) {
+ if (mResultsListener != null) {
+ throw new IllegalStateException(
+ "Attempting to set a listener that is already set.");
+ }
+ mExecutor = executor;
+ mResultsListener = listener;
+ }
+ try {
+ mService.setListener(mCarTelemetryServiceListener);
+ } catch (RemoteException e) {
+ handleRemoteExceptionFromCarService(e);
+ }
+ }
+
+ /**
+ * Unregisters the listener from {@link com.android.car.telemetry.CarTelemetryService}.
+ *
+ * @hide
+ */
+ @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
+ public void clearListener() {
+ synchronized (mLock) {
+ mResultsListener = null;
+ mExecutor = null;
+ }
+ try {
+ mService.clearListener();
+ } catch (RemoteException e) {
+ handleRemoteExceptionFromCarService(e);
+ }
+ }
+}
diff --git a/car-lib/src/android/car/telemetry/ICarTelemetryService.aidl b/car-lib/src/android/car/telemetry/ICarTelemetryService.aidl
new file mode 100644
index 0000000..e2eb3e9
--- /dev/null
+++ b/car-lib/src/android/car/telemetry/ICarTelemetryService.aidl
@@ -0,0 +1,21 @@
+package android.car.telemetry;
+
+import android.car.telemetry.ICarTelemetryServiceListener;
+
+/**
+ * Internal binder interface for {@code CarTelemetryService}, used by {@code CarTelemetryManager}.
+ *
+ * @hide
+ */
+interface ICarTelemetryService {
+
+ /**
+ * Registers a listener with CarTelemetryService for the service to send data to cloud app.
+ */
+ void setListener(in ICarTelemetryServiceListener listener);
+
+ /**
+ * Clears the listener registered with CarTelemetryService.
+ */
+ void clearListener();
+}
\ No newline at end of file
diff --git a/car-lib/src/android/car/telemetry/ICarTelemetryServiceListener.aidl b/car-lib/src/android/car/telemetry/ICarTelemetryServiceListener.aidl
new file mode 100644
index 0000000..6ee574f
--- /dev/null
+++ b/car-lib/src/android/car/telemetry/ICarTelemetryServiceListener.aidl
@@ -0,0 +1,37 @@
+/*
+ * 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.telemetry;
+
+import java.util.List;
+
+/**
+ * Binder interface implemented by {@code CarTelemetryManager}. Enables sending results from
+ * {@code CarTelemetryService} to {@code CarTelemetryManager}.
+ *
+ * @hide
+ */
+oneway interface ICarTelemetryServiceListener {
+
+ /**
+ * Called by {@code CarTelemetryService} when there is a list of data to send to the
+ * cloud app.
+ *
+ * @param data the serialized bytes of a message, e.g. the script execution results
+ * or errors.
+ */
+ void onDataReceived(in byte[] data);
+}
\ No newline at end of file
diff --git a/car-lib/src/android/car/telemetry/IScriptExecutor.aidl b/car-lib/src/android/car/telemetry/IScriptExecutor.aidl
new file mode 100644
index 0000000..d7b967b
--- /dev/null
+++ b/car-lib/src/android/car/telemetry/IScriptExecutor.aidl
@@ -0,0 +1,43 @@
+/*
+ * 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.telemetry;
+
+import android.car.telemetry.IScriptExecutorListener;
+import android.os.Bundle;
+
+/**
+ * An internal API provided by isolated Script Executor process
+ * for executing Lua scripts in a sandboxed environment
+ *
+ * @hide
+ */
+interface IScriptExecutor {
+ /**
+ * Executes a specified function in provided Lua script with given input arguments.
+ *
+ * @param scriptBody complete body of Lua script that also contains the function to be invoked
+ * @param functionName the name of the function to execute
+ * @param publishedData input data provided by the source which the function handles
+ * @param savedState key-value pairs preserved from the previous invocation of the function
+ * @param listener callback for the sandboxed environent to report back script execution results, errors, and logs
+ */
+ void invokeScript(String scriptBody,
+ String functionName,
+ in byte[] publishedData,
+ in @nullable Bundle savedState,
+ in IScriptExecutorListener listener);
+}
diff --git a/car-lib/src/android/car/telemetry/IScriptExecutorListener.aidl b/car-lib/src/android/car/telemetry/IScriptExecutorListener.aidl
new file mode 100644
index 0000000..d751a61
--- /dev/null
+++ b/car-lib/src/android/car/telemetry/IScriptExecutorListener.aidl
@@ -0,0 +1,73 @@
+/*
+ * 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.telemetry;
+
+import android.os.Bundle;
+
+/**
+ * Listener for {@code IScriptExecutor#invokeScript}.
+ *
+ * An invocation of a script by Script Executor will result in a call of only one
+ * of the three methods below. If a script fully completes its objective, onScriptFinished
+ * is called. If a script's invocation completes normally, onSuccess is called.
+ * onError is called if any error happens before or during script execution and we
+ * should abandon this run of the script.
+ */
+interface IScriptExecutorListener {
+ /**
+ * Called by ScriptExecutor when the script declares itself as "finished".
+ *
+ * @param result final results of the script that will be uploaded.
+ */
+ void onScriptFinished(in byte[] result);
+
+ /**
+ * Called by ScriptExecutor when a function completes successfully and also provides
+ * optional state that the script wants CarTelemetryService to persist.
+ *
+ * @param stateToPersist key-value pairs to persist
+ */
+ void onSuccess(in @nullable Bundle stateToPersist);
+
+ /**
+ * Default error type.
+ */
+ const int ERROR_TYPE_UNSPECIFIED = 0;
+
+ /**
+ * Used when an error occurs in the ScriptExecutor code.
+ */
+ const int ERROR_TYPE_SCRIPT_EXECUTOR_ERROR = 1;
+
+ /**
+ * Used when an error occurs while executing the Lua script (such as
+ * errors returned by lua_pcall)
+ */
+ const int ERROR_TYPE_LUA_RUNTIME_ERROR = 2;
+
+
+ /**
+ * Called by ScriptExecutor to report errors that prevented the script
+ * from running or completing execution successfully.
+ *
+ * @param errorType type of the error message as defined in this aidl file.
+ * @param messsage the human-readable message containing information helpful for analysis or debugging.
+ * @param stackTrace the stack trace of the error if available.
+ */
+ void onError(int errorType, String message, @nullable String stackTrace);
+}
+
diff --git a/car-lib/src/android/car/user/UserStartResult.aidl b/car-lib/src/android/car/user/UserStartResult.aidl
new file mode 100644
index 0000000..d5333ec
--- /dev/null
+++ b/car-lib/src/android/car/user/UserStartResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.user;
+
+parcelable UserStartResult;
diff --git a/car-lib/src/android/car/user/UserStartResult.java b/car-lib/src/android/car/user/UserStartResult.java
new file mode 100644
index 0000000..d1a5d32
--- /dev/null
+++ b/car-lib/src/android/car/user/UserStartResult.java
@@ -0,0 +1,242 @@
+/*
+ * 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.user;
+
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+/**
+ * User start results.
+ *
+ * @hide
+ */
+@DataClass(
+ genToString = true,
+ genHiddenConstructor = true,
+ genHiddenConstDefs = true)
+public final class UserStartResult implements Parcelable, OperationResult {
+
+ /**
+ * When user start is successful.
+ *
+ * @hide
+ */
+ public static final int STATUS_SUCCESSFUL = CommonResults.STATUS_SUCCESSFUL;
+
+ /**
+ * When user start failed.
+ *
+ * @hide
+ */
+ public static final int STATUS_ANDROID_FAILURE = CommonResults.STATUS_ANDROID_FAILURE;
+
+ /**
+ * When user to start is same as current user.
+ *
+ * @hide
+ */
+ public static final int STATUS_SUCCESSFUL_USER_IS_CURRENT_USER =
+ CommonResults.LAST_COMMON_STATUS + 1;
+
+ /**
+ * When user to start does not exist.
+ *
+ * @hide
+ */
+ public static final int STATUS_USER_DOES_NOT_EXIST = CommonResults.LAST_COMMON_STATUS + 2;
+
+ /**
+ * Gets the user start result status.
+ *
+ * @return either {@link UserStartRsult#STATUS_SUCCESSFUL},
+ * {@link UserStartResult#STATUS_SUCCESSFUL_USER_IS_CURRENT_USER},
+ * {@link UserStartResult#STATUS_ANDROID_FAILURE},
+ * {@link UserStartResult#STATUS_USER_DOES_NOT_EXIST}, or
+ */
+ private final @Status int mStatus;
+
+ @Override
+ public boolean isSuccess() {
+ return mStatus == STATUS_SUCCESSFUL || mStatus == STATUS_SUCCESSFUL_USER_IS_CURRENT_USER;
+ }
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/packages/services/Car/car-lib/src/android/car/user/UserStartResult.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /** @hide */
+ @android.annotation.IntDef(prefix = "STATUS_", value = {
+ STATUS_SUCCESSFUL,
+ STATUS_ANDROID_FAILURE,
+ STATUS_SUCCESSFUL_USER_IS_CURRENT_USER,
+ STATUS_USER_DOES_NOT_EXIST
+ })
+ @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface Status {}
+
+ /** @hide */
+ @DataClass.Generated.Member
+ public static String statusToString(@Status int value) {
+ switch (value) {
+ case STATUS_SUCCESSFUL:
+ return "STATUS_SUCCESSFUL";
+ case STATUS_ANDROID_FAILURE:
+ return "STATUS_ANDROID_FAILURE";
+ case STATUS_SUCCESSFUL_USER_IS_CURRENT_USER:
+ return "STATUS_SUCCESSFUL_USER_IS_CURRENT_USER";
+ case STATUS_USER_DOES_NOT_EXIST:
+ return "STATUS_USER_DOES_NOT_EXIST";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ /**
+ * Creates a new UserStartResult.
+ *
+ * @param status
+ * Gets the user start result status.
+ *
+ * @return either {@link UserStartRsult#STATUS_SUCCESSFUL},
+ * {@link UserStartResult#STATUS_ANDROID_FAILURE},
+ * {@link UserStartResult#STATUS_SUCCESSFUL_USER_IS_CURRENT_USER},
+ * {@link UserStartResult#STATUS_USER_DOES_NOT_EXIST}, or
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public UserStartResult(
+ @Status int status) {
+ this.mStatus = status;
+
+ if (!(mStatus == STATUS_SUCCESSFUL)
+ && !(mStatus == STATUS_ANDROID_FAILURE)
+ && !(mStatus == STATUS_SUCCESSFUL_USER_IS_CURRENT_USER)
+ && !(mStatus == STATUS_USER_DOES_NOT_EXIST)) {
+ throw new java.lang.IllegalArgumentException(
+ "status was " + mStatus + " but must be one of: "
+ + "STATUS_SUCCESSFUL(" + STATUS_SUCCESSFUL + "), "
+ + "STATUS_ANDROID_FAILURE(" + STATUS_ANDROID_FAILURE + "), "
+ + "STATUS_SUCCESSFUL_USER_IS_CURRENT_USER(" + STATUS_SUCCESSFUL_USER_IS_CURRENT_USER + "), "
+ + "STATUS_USER_DOES_NOT_EXIST(" + STATUS_USER_DOES_NOT_EXIST + ")");
+ }
+
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Gets the user start result status.
+ *
+ * @return either {@link UserStartRsult#STATUS_SUCCESSFUL},
+ * {@link UserStartResult#STATUS_ANDROID_FAILURE},
+ * {@link UserStartResult#STATUS_SUCCESSFUL_USER_IS_CURRENT_USER},
+ * {@link UserStartResult#STATUS_USER_DOES_NOT_EXIST}, or
+ */
+ @DataClass.Generated.Member
+ public @Status int getStatus() {
+ return mStatus;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "UserStartResult { " +
+ "status = " + statusToString(mStatus) +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeInt(mStatus);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ UserStartResult(@android.annotation.NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int status = in.readInt();
+
+ this.mStatus = status;
+
+ if (!(mStatus == STATUS_SUCCESSFUL)
+ && !(mStatus == STATUS_ANDROID_FAILURE)
+ && !(mStatus == STATUS_SUCCESSFUL_USER_IS_CURRENT_USER)
+ && !(mStatus == STATUS_USER_DOES_NOT_EXIST)) {
+ throw new java.lang.IllegalArgumentException(
+ "status was " + mStatus + " but must be one of: "
+ + "STATUS_SUCCESSFUL(" + STATUS_SUCCESSFUL + "), "
+ + "STATUS_ANDROID_FAILURE(" + STATUS_ANDROID_FAILURE + "), "
+ + "STATUS_SUCCESSFUL_USER_IS_CURRENT_USER(" + STATUS_SUCCESSFUL_USER_IS_CURRENT_USER + "), "
+ + "STATUS_USER_DOES_NOT_EXIST(" + STATUS_USER_DOES_NOT_EXIST + ")");
+ }
+
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @android.annotation.NonNull Parcelable.Creator<UserStartResult> CREATOR
+ = new Parcelable.Creator<UserStartResult>() {
+ @Override
+ public UserStartResult[] newArray(int size) {
+ return new UserStartResult[size];
+ }
+
+ @Override
+ public UserStartResult createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+ return new UserStartResult(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1618957103487L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/services/Car/car-lib/src/android/car/user/UserStartResult.java",
+ inputSignatures = "public static final int STATUS_SUCCESSFUL\npublic static final int STATUS_ANDROID_FAILURE\npublic static final int STATUS_SUCCESSFUL_USER_IS_CURRENT_USER\npublic static final int STATUS_USER_DOES_NOT_EXIST\nprivate final @android.car.user.UserStartResult.Status int mStatus\npublic @java.lang.Override boolean isSuccess()\nclass UserStartResult extends java.lang.Object implements [android.os.Parcelable, android.car.user.OperationResult]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/car-lib/src/android/car/user/UserStopResult.aidl b/car-lib/src/android/car/user/UserStopResult.aidl
new file mode 100644
index 0000000..d90eb46
--- /dev/null
+++ b/car-lib/src/android/car/user/UserStopResult.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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.user;
+
+parcelable UserStopResult;
\ No newline at end of file
diff --git a/car-lib/src/android/car/user/UserStopResult.java b/car-lib/src/android/car/user/UserStopResult.java
new file mode 100644
index 0000000..5f17220
--- /dev/null
+++ b/car-lib/src/android/car/user/UserStopResult.java
@@ -0,0 +1,262 @@
+/*
+ * 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.user;
+
+import android.annotation.IntDef;
+import android.os.Parcelable;
+
+import com.android.internal.util.DataClass;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * User remove result.
+ *
+ * @hide
+ */
+@DataClass(
+ genToString = true,
+ genHiddenConstructor = true,
+ genHiddenConstDefs = true)
+public final class UserStopResult implements Parcelable, OperationResult {
+
+ /**
+ * When user stop is successful.
+ */
+ public static final int STATUS_SUCCESSFUL = CommonResults.STATUS_SUCCESSFUL;
+
+ /**
+ * When user stop fails.
+ */
+ public static final int STATUS_ANDROID_FAILURE = CommonResults.STATUS_ANDROID_FAILURE;
+
+ /**
+ * When user to stop doesn't exits.
+ */
+ public static final int STATUS_USER_DOES_NOT_EXIST = CommonResults.LAST_COMMON_STATUS + 1;
+
+ /**
+ * When user to stop is the system user.
+ */
+ public static final int STATUS_FAILURE_SYSTEM_USER = CommonResults.LAST_COMMON_STATUS + 2;
+
+ /**
+ * When user to stop is the current user.
+ */
+ public static final int STATUS_FAILURE_CURRENT_USER = CommonResults.LAST_COMMON_STATUS + 3;
+
+ /**
+ * Gets the user switch result status.
+ *
+ * @return either {@link UserStopResult#STATUS_SUCCESSFUL},
+ * {@link UserStopResult#STATUS_ANDROID_FAILURE},
+ * {@link UserStopResult#STATUS_USER_DOES_NOT_EXIST},
+ * {@link UserStopResult#STATUS_FAILURE_SYSTEM_USER}, or
+ * {@link UserStopResult#STATUS_FAILURE_CURRENT_USER}.
+ */
+ private final @Status int mStatus;
+
+ /**
+ * Checks if the {@code status} represents a success status.
+ *
+ * @param status to check
+ * @return true for a success status
+ */
+ public static boolean isSuccess(@Status int status) {
+ return status == STATUS_SUCCESSFUL;
+ }
+
+ @Override
+ public boolean isSuccess() {
+ return isSuccess(mStatus);
+ }
+
+
+ // Code below generated by codegen v1.0.23.
+ //
+ // DO NOT MODIFY!
+ // CHECKSTYLE:OFF Generated code
+ //
+ // To regenerate run:
+ // $ codegen $ANDROID_BUILD_TOP/packages/services/Car/car-lib/src/android/car/user/UserStopResult.java
+ //
+ // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+ // Settings > Editor > Code Style > Formatter Control
+ //@formatter:off
+
+
+ /** @hide */
+ @IntDef(prefix = "STATUS_", value = {
+ STATUS_SUCCESSFUL,
+ STATUS_ANDROID_FAILURE,
+ STATUS_USER_DOES_NOT_EXIST,
+ STATUS_FAILURE_SYSTEM_USER,
+ STATUS_FAILURE_CURRENT_USER
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @DataClass.Generated.Member
+ public @interface Status {}
+
+ /** @hide */
+ @DataClass.Generated.Member
+ public static String statusToString(@Status int value) {
+ switch (value) {
+ case STATUS_SUCCESSFUL:
+ return "STATUS_SUCCESSFUL";
+ case STATUS_ANDROID_FAILURE:
+ return "STATUS_ANDROID_FAILURE";
+ case STATUS_USER_DOES_NOT_EXIST:
+ return "STATUS_USER_DOES_NOT_EXIST";
+ case STATUS_FAILURE_SYSTEM_USER:
+ return "STATUS_FAILURE_SYSTEM_USER";
+ case STATUS_FAILURE_CURRENT_USER:
+ return "STATUS_FAILURE_CURRENT_USER";
+ default: return Integer.toHexString(value);
+ }
+ }
+
+ /**
+ * Creates a new UserStopResult.
+ *
+ * @param status
+ * Gets the user switch result status.
+ *
+ * @return either {@link UserStopResult#STATUS_SUCCESSFUL},
+ * {@link UserStopResult#STATUS_ANDROID_FAILURE},
+ * {@link UserStopResult#STATUS_USER_DOES_NOT_EXIST},
+ * {@link UserStopResult#STATUS_FAILURE_SYSTEM_USER}, or
+ * {@link UserStopResult#STATUS_FAILURE_CURRENT_USER}.
+ * @hide
+ */
+ @DataClass.Generated.Member
+ public UserStopResult(
+ @Status int status) {
+ this.mStatus = status;
+
+ if (!(mStatus == STATUS_SUCCESSFUL)
+ && !(mStatus == STATUS_ANDROID_FAILURE)
+ && !(mStatus == STATUS_USER_DOES_NOT_EXIST)
+ && !(mStatus == STATUS_FAILURE_SYSTEM_USER)
+ && !(mStatus == STATUS_FAILURE_CURRENT_USER)) {
+ throw new java.lang.IllegalArgumentException(
+ "status was " + mStatus + " but must be one of: "
+ + "STATUS_SUCCESSFUL(" + STATUS_SUCCESSFUL + "), "
+ + "STATUS_ANDROID_FAILURE(" + STATUS_ANDROID_FAILURE + "), "
+ + "STATUS_USER_DOES_NOT_EXIST(" + STATUS_USER_DOES_NOT_EXIST + "), "
+ + "STATUS_FAILURE_SYSTEM_USER(" + STATUS_FAILURE_SYSTEM_USER + "), "
+ + "STATUS_FAILURE_CURRENT_USER(" + STATUS_FAILURE_CURRENT_USER + ")");
+ }
+
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ /**
+ * Gets the user switch result status.
+ *
+ * @return either {@link UserStopResult#STATUS_SUCCESSFUL},
+ * {@link UserStopResult#STATUS_ANDROID_FAILURE},
+ * {@link UserStopResult#STATUS_USER_DOES_NOT_EXIST},
+ * {@link UserStopResult#STATUS_FAILURE_SYSTEM_USER}, or
+ * {@link UserStopResult#STATUS_FAILURE_CURRENT_USER}.
+ */
+ @DataClass.Generated.Member
+ public @Status int getStatus() {
+ return mStatus;
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public String toString() {
+ // You can override field toString logic by defining methods like:
+ // String fieldNameToString() { ... }
+
+ return "UserStopResult { " +
+ "status = " + statusToString(mStatus) +
+ " }";
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public void writeToParcel(@android.annotation.NonNull android.os.Parcel dest, int flags) {
+ // You can override field parcelling by defining methods like:
+ // void parcelFieldName(Parcel dest, int flags) { ... }
+
+ dest.writeInt(mStatus);
+ }
+
+ @Override
+ @DataClass.Generated.Member
+ public int describeContents() { return 0; }
+
+ /** @hide */
+ @SuppressWarnings({"unchecked", "RedundantCast"})
+ @DataClass.Generated.Member
+ /* package-private */ UserStopResult(@android.annotation.NonNull android.os.Parcel in) {
+ // You can override field unparcelling by defining methods like:
+ // static FieldType unparcelFieldName(Parcel in) { ... }
+
+ int status = in.readInt();
+
+ this.mStatus = status;
+
+ if (!(mStatus == STATUS_SUCCESSFUL)
+ && !(mStatus == STATUS_ANDROID_FAILURE)
+ && !(mStatus == STATUS_USER_DOES_NOT_EXIST)
+ && !(mStatus == STATUS_FAILURE_SYSTEM_USER)
+ && !(mStatus == STATUS_FAILURE_CURRENT_USER)) {
+ throw new java.lang.IllegalArgumentException(
+ "status was " + mStatus + " but must be one of: "
+ + "STATUS_SUCCESSFUL(" + STATUS_SUCCESSFUL + "), "
+ + "STATUS_ANDROID_FAILURE(" + STATUS_ANDROID_FAILURE + "), "
+ + "STATUS_USER_DOES_NOT_EXIST(" + STATUS_USER_DOES_NOT_EXIST + "), "
+ + "STATUS_FAILURE_SYSTEM_USER(" + STATUS_FAILURE_SYSTEM_USER + "), "
+ + "STATUS_FAILURE_CURRENT_USER(" + STATUS_FAILURE_CURRENT_USER + ")");
+ }
+
+
+ // onConstructed(); // You can define this method to get a callback
+ }
+
+ @DataClass.Generated.Member
+ public static final @android.annotation.NonNull Parcelable.Creator<UserStopResult> CREATOR
+ = new Parcelable.Creator<UserStopResult>() {
+ @Override
+ public UserStopResult[] newArray(int size) {
+ return new UserStopResult[size];
+ }
+
+ @Override
+ public UserStopResult createFromParcel(@android.annotation.NonNull android.os.Parcel in) {
+ return new UserStopResult(in);
+ }
+ };
+
+ @DataClass.Generated(
+ time = 1619209981496L,
+ codegenVersion = "1.0.23",
+ sourceFile = "packages/services/Car/car-lib/src/android/car/user/UserStopResult.java",
+ inputSignatures = "public static final int STATUS_SUCCESSFUL\npublic static final int STATUS_ANDROID_FAILURE\npublic static final int STATUS_USER_DOES_NOT_EXIST\npublic static final int STATUS_FAILURE_SYSTEM_USER\npublic static final int STATUS_FAILURE_CURRENT_USER\nprivate final @android.car.user.UserStopResult.Status int mStatus\npublic static boolean isSuccess(int)\npublic @java.lang.Override boolean isSuccess()\nclass UserStopResult extends java.lang.Object implements [android.os.Parcelable, android.car.user.OperationResult]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+ @Deprecated
+ private void __metadata() {}
+
+
+ //@formatter:on
+ // End of generated code
+
+}
diff --git a/car-lib/src/android/car/watchdog/CarWatchdogManager.java b/car-lib/src/android/car/watchdog/CarWatchdogManager.java
index 588ac13..05cba98 100644
--- a/car-lib/src/android/car/watchdog/CarWatchdogManager.java
+++ b/car-lib/src/android/car/watchdog/CarWatchdogManager.java
@@ -694,6 +694,7 @@
* @param resourceOveruseFlag Flag to indicate the types of resource overuse configurations to
* return.
*
+ * @throws IllegalStateException if the system is in an invalid state.
* @hide
*/
@SystemApi
diff --git a/car-lib/src/com/android/car/internal/common/EventLogTags.logtags b/car-lib/src/com/android/car/internal/common/EventLogTags.logtags
index 31ed2b1..f781285 100644
--- a/car-lib/src/com/android/car/internal/common/EventLogTags.logtags
+++ b/car-lib/src/com/android/car/internal/common/EventLogTags.logtags
@@ -90,6 +90,8 @@
150122 car_user_svc_notify_internal_lifecycle_listener (listener_name|3),(event_type|1),(from_user_id|1),(to_user_id|1)
150123 car_user_svc_pre_creation_requested (number_users|1),(number_guests|1)
150124 car_user_svc_pre_creation_status (number_existing_users|1),(number_users_to_add|1),(number_users_to_remove|1),(number_existing_guests|1),(number_guests_to_add|1),(number_guests_to_remove|1),(number_invalid_users_to_remove|1)
+150125 car_user_svc_start_user_in_background_req (user_id|1)
+150126 car_user_svc_start_user_in_background_resp (user_id|1),(result|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),(user_locales|3)
@@ -127,3 +129,5 @@
150201 car_dp_mgr_remove_user_resp (uid|1),(status|1)
150202 car_dp_mgr_create_user_req (uid|1),(safe_name|3),(flags|1)
150203 car_dp_mgr_create_user_resp (uid|1),(status|1)
+150204 car_dp_mgr_start_user_in_background_req (uid|1),(user_id|1)
+150205 car_dp_mgr_start_user_in_background_resp (uid|1),(status|1)
diff --git a/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java b/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
index aaf12a7..9430723 100644
--- a/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
+++ b/car-test-lib/src/android/car/test/mocks/JavaMockitoHelper.java
@@ -89,8 +89,7 @@
* {@value #ASYNC_TIMEOUT_MS} ms.
*/
@NonNull
- public static <T> T getResult(@NonNull Future<T> future)
- throws InterruptedException, ExecutionException {
+ public static <T> T getResult(@NonNull Future<T> future) {
return getResult(future, ASYNC_TIMEOUT_MS);
}
diff --git a/car-test-lib/src/android/car/testapi/CarTelemetryController.java b/car-test-lib/src/android/car/testapi/CarTelemetryController.java
new file mode 100644
index 0000000..1f57497
--- /dev/null
+++ b/car-test-lib/src/android/car/testapi/CarTelemetryController.java
@@ -0,0 +1,30 @@
+/*
+ * 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.testapi;
+
+/**
+ * Controller to manipulate and verify {@link android.car.telemetry.CarTelemetryManager} in
+ * unit tests.
+ */
+public interface CarTelemetryController {
+ /**
+ * Returns {@code true} if a {@link
+ * android.car.telemetry.CarTelemetryManager.CarTelemetryResultsListener} is
+ * registered with the manager, otherwise returns {@code false}.
+ */
+ boolean isListenerSet();
+}
diff --git a/car-test-lib/src/android/car/testapi/FakeCar.java b/car-test-lib/src/android/car/testapi/FakeCar.java
index 4a3a98a..0508ed0 100644
--- a/car-test-lib/src/android/car/testapi/FakeCar.java
+++ b/car-test-lib/src/android/car/testapi/FakeCar.java
@@ -24,7 +24,6 @@
import android.car.diagnostic.ICarDiagnostic;
import android.car.drivingstate.ICarDrivingState;
import android.car.hardware.power.ICarPower;
-import android.car.media.ICarAudio;
import android.car.storagemonitoring.ICarStorageMonitoring;
import android.content.Context;
import android.os.IBinder;
@@ -128,8 +127,15 @@
return mService.mCarUxRestrictionService;
}
+ /**
+ * Returns a test controller that can modify and query the underlying service for the {@link
+ * android.car.telemetry.CarTelemetryManager}.
+ */
+ public CarTelemetryController getCarTelemetryController() {
+ return mService.mCarTelemetry;
+ }
+
private static class FakeCarService extends ICar.Stub {
- @Mock ICarAudio.Stub mCarAudio;
@Mock ICarPackageManager.Stub mCarPackageManager;
@Mock ICarDiagnostic.Stub mCarDiagnostic;
@Mock ICarPower.Stub mCarPower;
@@ -138,19 +144,23 @@
@Mock ICarStorageMonitoring.Stub mCarStorageMonitoring;
@Mock ICarDrivingState.Stub mCarDrivingState;
+ private final FakeCarAudioService mCarAudio;
private final FakeAppFocusService mAppFocus;
private final FakeCarPropertyService mCarProperty;
private final FakeCarProjectionService mCarProjection;
private final FakeInstrumentClusterNavigation mInstrumentClusterNavigation;
private final FakeCarUxRestrictionsService mCarUxRestrictionService;
+ private final FakeCarTelemetryService mCarTelemetry;
FakeCarService(Context context) {
MockitoAnnotations.initMocks(this);
+ mCarAudio = new FakeCarAudioService();
mAppFocus = new FakeAppFocusService(context);
mCarProperty = new FakeCarPropertyService();
mCarProjection = new FakeCarProjectionService(context);
mInstrumentClusterNavigation = new FakeInstrumentClusterNavigation();
mCarUxRestrictionService = new FakeCarUxRestrictionsService();
+ mCarTelemetry = new FakeCarTelemetryService();
}
@Override
@@ -193,6 +203,8 @@
return mCarDrivingState;
case Car.CAR_UX_RESTRICTION_SERVICE:
return mCarUxRestrictionService;
+ case Car.CAR_TELEMETRY_SERVICE:
+ return mCarTelemetry;
default:
Log.w(TAG, "getCarService for unknown service:" + serviceName);
return null;
diff --git a/car-test-lib/src/android/car/testapi/FakeCarAudioService.java b/car-test-lib/src/android/car/testapi/FakeCarAudioService.java
new file mode 100644
index 0000000..7b0bb09
--- /dev/null
+++ b/car-test-lib/src/android/car/testapi/FakeCarAudioService.java
@@ -0,0 +1,146 @@
+/*
+ * 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.testapi;
+
+import android.car.media.CarAudioPatchHandle;
+import android.car.media.ICarAudio;
+import android.media.AudioDeviceAttributes;
+import android.os.IBinder;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Fake service that is used by {@link FakeCar} to provide an implementation of {@link ICarAudio}.
+ * The reason we couldn't use a mock version of this service is that {@link AudioDeviceAttributes}
+ * is annotated with @hide, and Mockito fails to create a mock instance.
+ */
+final class FakeCarAudioService extends ICarAudio.Stub {
+ @Override
+ public boolean isAudioFeatureEnabled(int feature) {
+ return false;
+ }
+
+ @Override
+ public void setGroupVolume(int zoneId, int groupId, int index, int flags) {
+ }
+
+ @Override
+ public int getGroupMaxVolume(int zoneId, int groupId) {
+ return 0;
+ }
+
+ @Override
+ public int getGroupMinVolume(int zoneId, int groupId) {
+ return 0;
+ }
+
+ @Override
+ public int getGroupVolume(int zoneId, int groupId) {
+ return 0;
+ }
+
+ @Override
+ public void setFadeTowardFront(float value) {
+ }
+
+ @Override
+ public void setBalanceTowardRight(float value) {
+ }
+
+ @Override
+ public String[] getExternalSources() {
+ return new String[] {};
+ }
+
+ @Override
+ public CarAudioPatchHandle createAudioPatch(String sourceAddress, int usage,
+ int gainInMillibels) {
+ return null;
+ }
+
+ @Override
+ public void releaseAudioPatch(CarAudioPatchHandle patch) {
+ }
+
+ @Override
+ public int getVolumeGroupCount(int zoneId) {
+ return 0;
+ }
+
+ @Override
+ public int getVolumeGroupIdForUsage(int zoneId, int usage) {
+ return 0;
+ }
+
+ @Override
+ public int[] getUsagesForVolumeGroupId(int zoneId, int groupId) {
+ return new int[] {};
+ }
+
+ @Override
+ public int[] getAudioZoneIds() {
+ return new int[] {};
+ }
+
+ @Override
+ public int getZoneIdForUid(int uid) {
+ return 0;
+ }
+
+ @Override
+ public boolean setZoneIdForUid(int zoneId, int uid) {
+ return false;
+ }
+
+ @Override
+ public boolean clearZoneIdForUid(int uid) {
+ return false;
+ }
+
+ @Override
+ public boolean isVolumeGroupMuted(int zoneId, int groupId) {
+ return false;
+ }
+
+ @Override
+ public void setVolumeGroupMute(int zoneId, int groupId, boolean mute, int flags) {
+ }
+
+ @Override
+ public String getOutputDeviceAddressForUsage(int zoneId, int usage) {
+ return "";
+ }
+
+ @Override
+ public List<AudioDeviceAttributes> getInputDevicesForZoneId(int zoneId) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public boolean isPlaybackOnVolumeGroupActive(int volumeGroupId, int audioZoneId) {
+ return false;
+ }
+
+ @Override
+ public void registerVolumeCallback(IBinder binder) {
+ }
+
+ @Override
+ public void unregisterVolumeCallback(IBinder binder) {
+ }
+}
diff --git a/car-test-lib/src/android/car/testapi/FakeCarTelemetryService.java b/car-test-lib/src/android/car/testapi/FakeCarTelemetryService.java
new file mode 100644
index 0000000..af8a2b4
--- /dev/null
+++ b/car-test-lib/src/android/car/testapi/FakeCarTelemetryService.java
@@ -0,0 +1,48 @@
+/*
+ * 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.testapi;
+
+import android.car.telemetry.ICarTelemetryService;
+import android.car.telemetry.ICarTelemetryServiceListener;
+
+/**
+ * A fake implementation of {@link ICarTelemetryService.Stub} to facilitate the use of
+ * {@link android.car.telemetry.CarTelemetryManager} in external unit tests.
+ *
+ * @hide
+ */
+public class FakeCarTelemetryService extends ICarTelemetryService.Stub implements
+ CarTelemetryController {
+
+ private ICarTelemetryServiceListener mListener;
+
+ @Override
+ public void setListener(ICarTelemetryServiceListener listener) {
+ mListener = listener;
+ }
+
+ @Override
+ public void clearListener() {
+ mListener = null;
+ }
+
+ /**************************** CarTelemetryController impl ********************************/
+ @Override
+ public boolean isListenerSet() {
+ return mListener != null;
+ }
+}
diff --git a/car_product/build/car_base.mk b/car_product/build/car_base.mk
index 41408eb..2f9b344 100644
--- a/car_product/build/car_base.mk
+++ b/car_product/build/car_base.mk
@@ -65,6 +65,9 @@
# EVS service
include packages/services/Car/cpp/evs/manager/evsmanager.mk
+# Automotive Telemetry services
+include packages/services/Car/cpp/telemetry/products/telemetry.mk
+
# EVS manager overrides cameraserver on automotive implementations so
# we need to configure Camera API to not connect to it
PRODUCT_PROPERTY_OVERRIDES += config.disable_cameraservice=true
diff --git a/car_product/overlay/frameworks/base/core/res/res/values/config.xml b/car_product/overlay/frameworks/base/core/res/res/values/config.xml
index 52739bf..f675d4e 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values/config.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values/config.xml
@@ -146,4 +146,7 @@
<!-- The name of the package that will hold the system cluster service role. -->
<string name="config_systemAutomotiveCluster" translatable="false">android.car.cluster</string>
+ <!-- Whether this device is supporting the microphone toggle -->
+ <bool name="config_supportsMicToggle">true</bool>
+
</resources>
diff --git a/cpp/computepipe/router/1.0/RouterSvc.cpp b/cpp/computepipe/router/1.0/RouterSvc.cpp
index 0b296e6..0edd0ef 100644
--- a/cpp/computepipe/router/1.0/RouterSvc.cpp
+++ b/cpp/computepipe/router/1.0/RouterSvc.cpp
@@ -17,7 +17,7 @@
#include <android/binder_interface_utils.h>
#include <android/binder_manager.h>
-#include <binder/IServiceManager.h>
+#include <log/log.h>
#include "PipeQuery.h"
#include "PipeRegistration.h"
diff --git a/cpp/evs/apps/default/config.json b/cpp/evs/apps/default/config.json
index 16148a7..dc2771c 100644
--- a/cpp/evs/apps/default/config.json
+++ b/cpp/evs/apps/default/config.json
@@ -8,7 +8,7 @@
},
"displays" : [
{
- "displayPort" : 136,
+ "displayPort" : 129,
"frontRange" : 100,
"rearRange" : 100
},
diff --git a/cpp/evs/manager/1.1/VirtualCamera.cpp b/cpp/evs/manager/1.1/VirtualCamera.cpp
index 731b0cc..427666c 100644
--- a/cpp/evs/manager/1.1/VirtualCamera.cpp
+++ b/cpp/evs/manager/1.1/VirtualCamera.cpp
@@ -155,19 +155,9 @@
} else if (mCaptureThread.joinable()) {
// Keep forwarding frames as long as a capture thread is alive
if (mFramesHeld.size() > 0 && mStream_1_1 != nullptr) {
- // Pass this buffer through to our client
- hardware::hidl_vec<BufferDesc_1_1> frames;
- frames.resize(1);
- auto pHwCamera = mHalCamera.begin()->second.promote();
- if (pHwCamera != nullptr) {
- frames[0] = mFramesHeld[mHalCamera.begin()->first].back();
- }
-
// Notify a new frame receipt
- {
- std::lock_guard<std::mutex> lock(mFrameDeliveryMutex);
- mSourceCameras.erase(bufDesc.deviceId);
- }
+ std::lock_guard<std::mutex> lock(mFrameDeliveryMutex);
+ mSourceCameras.erase(bufDesc.deviceId);
mFramesReadySignal.notify_all();
}
}
diff --git a/cpp/powerpolicy/server/src/CarPowerPolicyServer.cpp b/cpp/powerpolicy/server/src/CarPowerPolicyServer.cpp
index 8331612..4c9b260 100644
--- a/cpp/powerpolicy/server/src/CarPowerPolicyServer.cpp
+++ b/cpp/powerpolicy/server/src/CarPowerPolicyServer.cpp
@@ -495,8 +495,14 @@
mPendingPowerPolicyId = policyId;
return {};
}
+ bool isPolicyApplied = isPowerPolicyAppliedLocked();
+ if (isPolicyApplied && mCurrentPowerPolicyMeta.powerPolicy->policyId == policyId) {
+ ALOGI("Applying policy skipped: the given policy(ID: %s) is the current policy",
+ policyId.c_str());
+ return {};
+ }
if (policyMeta->isPreemptive) {
- if (isPowerPolicyAppliedLocked() && !mCurrentPowerPolicyMeta.isPreemptive) {
+ if (isPolicyApplied && !mCurrentPowerPolicyMeta.isPreemptive) {
mPendingPowerPolicyId = mCurrentPowerPolicyMeta.powerPolicy->policyId;
}
mIsPowerPolicyLocked = true;
diff --git a/cpp/surround_view/app/Android.bp b/cpp/surround_view/app/Android.bp
index 5145c5e..76753d3 100644
--- a/cpp/surround_view/app/Android.bp
+++ b/cpp/surround_view/app/Android.bp
@@ -23,6 +23,7 @@
name: "sv_app",
srcs: [
+ "SurroundViewAppCommon.cpp",
"SurroundViewServiceCallback.cpp",
"shader.cpp",
"sv_app.cpp",
diff --git a/cpp/surround_view/app/SurroundViewAppCommon.cpp b/cpp/surround_view/app/SurroundViewAppCommon.cpp
new file mode 100644
index 0000000..b1a2792
--- /dev/null
+++ b/cpp/surround_view/app/SurroundViewAppCommon.cpp
@@ -0,0 +1,191 @@
+/*
+ * Copyright 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.
+ */
+
+#include "SurroundViewAppCommon.h"
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace sv {
+namespace app {
+
+bool run2dSurroundView(sp<ISurroundViewService> pSurroundViewService, sp<IEvsDisplay> pDisplay) {
+ LOG(INFO) << "Run 2d Surround View demo";
+
+ // Call HIDL API "start2dSession"
+ sp<ISurroundView2dSession> surroundView2dSession;
+
+ SvResult svResult;
+ pSurroundViewService->start2dSession(
+ [&surroundView2dSession, &svResult](const sp<ISurroundView2dSession>& session,
+ SvResult result) {
+ surroundView2dSession = session;
+ svResult = result;
+ });
+
+ if (surroundView2dSession == nullptr || svResult != SvResult::OK) {
+ LOG(ERROR) << "Failed to start2dSession";
+ return false;
+ } else {
+ LOG(INFO) << "start2dSession succeeded";
+ }
+
+ sp<SurroundViewServiceCallback> sv2dCallback =
+ new SurroundViewServiceCallback(pDisplay, surroundView2dSession);
+
+ // Start 2d stream with callback with default quality and resolution.
+ // The quality is defaulted to be HIGH_QUALITY, and the default resolution
+ // is set in the sv config file.
+ if (surroundView2dSession->startStream(sv2dCallback) != SvResult::OK) {
+ LOG(ERROR) << "Failed to start 2d stream";
+ return false;
+ }
+
+ const int kTotalViewingTimeSecs = 10;
+
+ // Let the SV algorithm run for HIGH_QUALITY until the wait time finishes
+ std::this_thread::sleep_for(std::chrono::seconds(kTotalViewingTimeSecs));
+
+ // Switch to low quality and lower resolution
+ Sv2dConfig config = {.width = kLowResolutionWidth, .blending = SvQuality::LOW};
+ if (surroundView2dSession->set2dConfig(config) != SvResult::OK) {
+ LOG(ERROR) << "Failed to set2dConfig";
+ return false;
+ }
+
+ // Let the SV algorithm run for LOW_QUALITY until the wait time finishes
+ std::this_thread::sleep_for(std::chrono::seconds(kTotalViewingTimeSecs));
+
+ // TODO(b/150412555): wait for the last frame
+ // Stop the 2d stream and session
+ surroundView2dSession->stopStream();
+
+ pSurroundViewService->stop2dSession(surroundView2dSession);
+ surroundView2dSession = nullptr;
+
+ LOG(INFO) << "SV 2D session finished.";
+
+ return true;
+};
+
+// Given a valid sv 3d session and pose, viewid and hfov parameters, sets the view.
+bool setView(sp<ISurroundView3dSession> surroundView3dSession, uint32_t viewId, uint32_t poseIndex,
+ float hfov) {
+ const View3d view3d = {
+ .viewId = viewId,
+ .pose =
+ {
+ .rotation = {.x = kPoseRot[poseIndex][0],
+ .y = kPoseRot[poseIndex][1],
+ .z = kPoseRot[poseIndex][2],
+ .w = kPoseRot[poseIndex][3]},
+ .translation = {.x = kPoseTrans[poseIndex][0],
+ .y = kPoseTrans[poseIndex][1],
+ .z = kPoseTrans[poseIndex][2]},
+ },
+ .horizontalFov = hfov,
+ };
+
+ const std::vector<View3d> views = {view3d};
+ if (surroundView3dSession->setViews(views) != SvResult::OK) {
+ return false;
+ }
+ return true;
+}
+
+bool run3dSurroundView(sp<ISurroundViewService> pSurroundViewService, sp<IEvsDisplay> pDisplay) {
+ LOG(INFO) << "Run 3d Surround View demo";
+
+ // Call HIDL API "start3dSession"
+ sp<ISurroundView3dSession> surroundView3dSession;
+
+ SvResult svResult;
+ pSurroundViewService->start3dSession(
+ [&surroundView3dSession, &svResult](const sp<ISurroundView3dSession>& session,
+ SvResult result) {
+ surroundView3dSession = session;
+ svResult = result;
+ });
+
+ if (surroundView3dSession == nullptr || svResult != SvResult::OK) {
+ LOG(ERROR) << "Failed to start3dSession";
+ return false;
+ } else {
+ LOG(INFO) << "start3dSession succeeded";
+ }
+
+ sp<SurroundViewServiceCallback> sv3dCallback =
+ new SurroundViewServiceCallback(pDisplay, surroundView3dSession);
+
+ // A view must be set before the 3d stream is started.
+ if (!setView(surroundView3dSession, /*viewId=*/0, /*poseIndex=*/0, kHorizontalFov)) {
+ LOG(ERROR) << "Failed to setView of pose index :" << 0;
+ return false;
+ }
+
+ // Start 3d stream with callback with default quality and resolution.
+ // The quality is defaulted to be HIGH_QUALITY, and the default resolution
+ // is set in the sv config file.
+ if (surroundView3dSession->startStream(sv3dCallback) != SvResult::OK) {
+ LOG(ERROR) << "Failed to start 3d stream";
+ return false;
+ }
+
+ // Let the SV algorithm run for 10 seconds for HIGH_QUALITY
+ const int kTotalViewingTimeSecs = 10;
+ const std::chrono::milliseconds perPoseSleepTimeMs(kTotalViewingTimeSecs * 1000 / kPoseCount);
+ // Iterate through the pre-set views.
+ for (uint32_t i = 0; i < kPoseCount; i++) {
+ if (!setView(surroundView3dSession, /*viewId=*/i, /*poseIndex=*/i, kHorizontalFov)) {
+ LOG(WARNING) << "Failed to setView of pose index :" << i;
+ }
+ std::this_thread::sleep_for(perPoseSleepTimeMs);
+ }
+
+ // Switch to low quality and lower resolution
+ Sv3dConfig config = {.width = kLowResolutionWidth,
+ .height = kLowResolutionHeight,
+ .carDetails = SvQuality::LOW};
+
+ if (surroundView3dSession->set3dConfig(config) != SvResult::OK) {
+ LOG(ERROR) << "Failed to set3dConfig";
+ return false;
+ }
+
+ // Let the SV algorithm run for 10 seconds for LOW_QUALITY
+ for (uint32_t i = 0; i < kPoseCount; i++) {
+ if (!setView(surroundView3dSession, i + kPoseCount, i, kHorizontalFov)) {
+ LOG(WARNING) << "Failed to setView of pose index :" << i;
+ }
+ std::this_thread::sleep_for(perPoseSleepTimeMs);
+ }
+
+ // TODO(b/150412555): wait for the last frame
+ // Stop the 3d stream and session
+ surroundView3dSession->stopStream();
+
+ pSurroundViewService->stop3dSession(surroundView3dSession);
+ surroundView3dSession = nullptr;
+
+ LOG(DEBUG) << "SV 3D session finished.";
+
+ return true;
+};
+} // namespace app
+} // namespace sv
+} // namespace automotive
+} // namespace hardware
+} // namespace android
diff --git a/cpp/surround_view/app/SurroundViewAppCommon.h b/cpp/surround_view/app/SurroundViewAppCommon.h
new file mode 100644
index 0000000..04e59fa
--- /dev/null
+++ b/cpp/surround_view/app/SurroundViewAppCommon.h
@@ -0,0 +1,106 @@
+/*
+ * Copyright 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.
+ */
+#pragma once
+
+#include "SurroundViewServiceCallback.h"
+
+#include <android-base/logging.h>
+#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
+#include <android/hardware/automotive/sv/1.0/ISurroundView2dSession.h>
+#include <android/hardware/automotive/sv/1.0/ISurroundView3dSession.h>
+#include <android/hardware/automotive/sv/1.0/ISurroundViewService.h>
+#include <hidl/HidlTransportSupport.h>
+#include <utils/Log.h>
+#include <utils/StrongPointer.h>
+
+#include <stdio.h>
+
+#include <thread>
+
+using namespace android::hardware::automotive::sv::V1_0;
+using namespace android::hardware::automotive::evs::V1_1;
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace sv {
+namespace app {
+
+const int kLowResolutionWidth = 120;
+const int kLowResolutionHeight = 90;
+
+enum DemoMode {
+ UNKNOWN,
+ DEMO_2D,
+ DEMO_3D,
+};
+
+const float kHorizontalFov = 90;
+
+// Number of views to generate.
+const uint32_t kPoseCount = 16;
+
+// Set of pose rotations expressed in quaternions.
+// Views are generated about a circle at a height about the car, point towards the center.
+const float kPoseRot[kPoseCount][4] = {{-0.251292, -0.251292, -0.660948, 0.660948},
+ {0.197439, 0.295488, 0.777193, -0.519304},
+ {0.135998, 0.328329, 0.86357, -0.357702},
+ {0.0693313, 0.348552, 0.916761, -0.182355},
+ {-7.76709e-09, 0.355381, 0.934722, 2.0429e-08},
+ {-0.0693313, 0.348552, 0.916761, 0.182355},
+ {-0.135998, 0.328329, 0.86357, 0.357702},
+ {-0.197439, 0.295488, 0.777193, 0.519304},
+ {-0.251292, 0.251292, 0.660948, 0.660948},
+ {-0.295488, 0.197439, 0.519304, 0.777193},
+ {-0.328329, 0.135998, 0.357702, 0.86357},
+ {-0.348552, 0.0693313, 0.182355, 0.916761},
+ {-0.355381, -2.11894e-09, -5.57322e-09, 0.934722},
+ {-0.348552, -0.0693313, -0.182355, 0.916761},
+ {-0.328329, -0.135998, -0.357702, 0.86357},
+ {-0.295488, -0.197439, -0.519304, 0.777193}};
+
+// Set of pose translations i.e. positions of the views.
+// Views are generated about a circle at a height about the car, point towards the center.
+const float kPoseTrans[kPoseCount][4] = {{4, 0, 2.5},
+ {3.69552, 1.53073, 2.5},
+ {2.82843, 2.82843, 2.5},
+ {1.53073, 3.69552, 2.5},
+ {-1.74846e-07, 4, 2.5},
+ {-1.53073, 3.69552, 2.5},
+ {-2.82843, 2.82843, 2.5},
+ {-3.69552, 1.53073, 2.5},
+ {-4, -3.49691e-07, 2.5},
+ {-3.69552, -1.53073, 2.5},
+ {-2.82843, -2.82843, 2.5},
+ {-1.53073, -3.69552, 2.5},
+ {4.76995e-08, -4, 2.5},
+ {1.53073, -3.69552, 2.5},
+ {2.82843, -2.82843, 2.5},
+ {3.69552, -1.53073, 2.5}};
+
+bool run2dSurroundView(sp<ISurroundViewService> pSurroundViewService, sp<IEvsDisplay> pDisplay);
+
+bool run3dSurroundView(sp<ISurroundViewService> pSurroundViewService, sp<IEvsDisplay> pDisplay);
+
+// Given a valid sv 3d session and pose, viewid and hfov parameters, sets the view.
+bool setView(sp<ISurroundView3dSession> surroundView3dSession, uint32_t viewId, uint32_t poseIndex,
+ float hfov);
+
+} // namespace app
+} // namespace sv
+} // namespace automotive
+} // namespace hardware
+} // namespace android
diff --git a/cpp/surround_view/app/SurroundViewServiceCallback.cpp b/cpp/surround_view/app/SurroundViewServiceCallback.cpp
index 12dba98..a3ce519 100644
--- a/cpp/surround_view/app/SurroundViewServiceCallback.cpp
+++ b/cpp/surround_view/app/SurroundViewServiceCallback.cpp
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
#include "SurroundViewServiceCallback.h"
#include <android-base/logging.h>
@@ -441,15 +440,6 @@
LOG(INFO) << "Successfully attached render target";
}
- // Call HIDL API "doneWithFrames" to return the ownership
- // back to SV service
- if (mSession == nullptr) {
- LOG(ERROR) << "SurroundViewSession in callback is invalid";
- return {};
- } else {
- mSession->doneWithFrames(svFramesDesc);
- }
-
// Render frame to EVS display
LOG(INFO) << "Rendering to display buffer";
sp<GraphicBuffer> graphicBuffer =
diff --git a/cpp/surround_view/app/SurroundViewServiceCallback.h b/cpp/surround_view/app/SurroundViewServiceCallback.h
index b2ab831..467c69a 100644
--- a/cpp/surround_view/app/SurroundViewServiceCallback.h
+++ b/cpp/surround_view/app/SurroundViewServiceCallback.h
@@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+#pragma once
+
#include <stdio.h>
#include <utils/StrongPointer.h>
diff --git a/cpp/surround_view/app/sv_app.cpp b/cpp/surround_view/app/sv_app.cpp
index 43369c2..d07671d 100644
--- a/cpp/surround_view/app/sv_app.cpp
+++ b/cpp/surround_view/app/sv_app.cpp
@@ -14,17 +14,7 @@
* limitations under the License.
*/
-#include <android-base/logging.h>
-#include <android/hardware/automotive/evs/1.1/IEvsEnumerator.h>
-#include <android/hardware/automotive/sv/1.0/ISurroundViewService.h>
-#include <android/hardware/automotive/sv/1.0/ISurroundView2dSession.h>
-#include <android/hardware/automotive/sv/1.0/ISurroundView3dSession.h>
-#include <hidl/HidlTransportSupport.h>
-#include <stdio.h>
-#include <utils/StrongPointer.h>
-#include <utils/Log.h>
-#include <thread>
-
+#include "SurroundViewAppCommon.h"
#include "SurroundViewServiceCallback.h"
// libhidl:
@@ -40,220 +30,7 @@
using namespace android::hardware::automotive::sv::V1_0;
using namespace android::hardware::automotive::evs::V1_1;
-
-const int kLowResolutionWidth = 120;
-const int kLowResolutionHeight = 90;
-
-enum DemoMode {
- UNKNOWN,
- DEMO_2D,
- DEMO_3D,
-};
-
-const float kHorizontalFov = 90;
-
-// Number of views to generate.
-const uint32_t kPoseCount = 16;
-
-// Set of pose rotations expressed in quaternions.
-// Views are generated about a circle at a height about the car, point towards the center.
-const float kPoseRot[kPoseCount][4] = {
- {-0.251292, -0.251292, -0.660948, 0.660948},
- {0.197439, 0.295488, 0.777193, -0.519304},
- {0.135998, 0.328329, 0.86357, -0.357702},
- {0.0693313, 0.348552, 0.916761, -0.182355},
- {-7.76709e-09, 0.355381, 0.934722, 2.0429e-08},
- {-0.0693313, 0.348552, 0.916761, 0.182355},
- {-0.135998, 0.328329, 0.86357, 0.357702},
- {-0.197439, 0.295488, 0.777193, 0.519304},
- {-0.251292, 0.251292, 0.660948, 0.660948},
- {-0.295488, 0.197439, 0.519304, 0.777193},
- {-0.328329, 0.135998, 0.357702, 0.86357},
- {-0.348552, 0.0693313, 0.182355, 0.916761},
- {-0.355381, -2.11894e-09, -5.57322e-09, 0.934722},
- {-0.348552, -0.0693313, -0.182355, 0.916761},
- {-0.328329, -0.135998, -0.357702, 0.86357},
- {-0.295488, -0.197439, -0.519304, 0.777193}
-};
-
-// Set of pose translations i.e. positions of the views.
-// Views are generated about a circle at a height about the car, point towards the center.
-const float kPoseTrans[kPoseCount][4] = {
- {4, 0, 2.5},
- {3.69552, 1.53073, 2.5},
- {2.82843, 2.82843, 2.5},
- {1.53073, 3.69552, 2.5},
- {-1.74846e-07, 4, 2.5},
- {-1.53073, 3.69552, 2.5},
- {-2.82843, 2.82843, 2.5},
- {-3.69552, 1.53073, 2.5},
- {-4, -3.49691e-07, 2.5},
- {-3.69552, -1.53073, 2.5},
- {-2.82843, -2.82843, 2.5},
- {-1.53073, -3.69552, 2.5},
- {4.76995e-08, -4, 2.5},
- {1.53073, -3.69552, 2.5},
- {2.82843, -2.82843, 2.5},
- {3.69552, -1.53073, 2.5}
-};
-
-bool run2dSurroundView(sp<ISurroundViewService> pSurroundViewService,
- sp<IEvsDisplay> pDisplay) {
- LOG(INFO) << "Run 2d Surround View demo";
-
- // Call HIDL API "start2dSession"
- sp<ISurroundView2dSession> surroundView2dSession;
-
- SvResult svResult;
- pSurroundViewService->start2dSession(
- [&surroundView2dSession, &svResult](
- const sp<ISurroundView2dSession>& session, SvResult result) {
- surroundView2dSession = session;
- svResult = result;
- });
-
- if (surroundView2dSession == nullptr || svResult != SvResult::OK) {
- LOG(ERROR) << "Failed to start2dSession";
- return false;
- } else {
- LOG(INFO) << "start2dSession succeeded";
- }
-
- sp<SurroundViewServiceCallback> sv2dCallback
- = new SurroundViewServiceCallback(pDisplay, surroundView2dSession);
-
- // Start 2d stream with callback
- if (surroundView2dSession->startStream(sv2dCallback) != SvResult::OK) {
- LOG(ERROR) << "Failed to start 2d stream";
- return false;
- }
-
- // Let the SV algorithm run for 10 seconds for HIGH_QUALITY
- std::this_thread::sleep_for(std::chrono::seconds(10));
-
- // Switch to low quality and lower resolution
- Sv2dConfig config;
- config.width = kLowResolutionWidth;
- config.blending = SvQuality::LOW;
- if (surroundView2dSession->set2dConfig(config) != SvResult::OK) {
- LOG(ERROR) << "Failed to set2dConfig";
- return false;
- }
-
- // Let the SV algorithm run for 10 seconds for LOW_QUALITY
- std::this_thread::sleep_for(std::chrono::seconds(10));
-
- // TODO(b/150412555): wait for the last frame
- // Stop the 2d stream and session
- surroundView2dSession->stopStream();
-
- pSurroundViewService->stop2dSession(surroundView2dSession);
- surroundView2dSession = nullptr;
-
- LOG(INFO) << "SV 2D session finished.";
-
- return true;
-};
-
-// Given a valid sv 3d session and pose, viewid and hfov parameters, sets the view.
-bool setView(sp<ISurroundView3dSession> surroundView3dSession, uint32_t viewId,
- uint32_t poseIndex, float hfov)
-{
- const View3d view3d = {
- .viewId = viewId,
- .pose = {
- .rotation = {.x=kPoseRot[poseIndex][0], .y=kPoseRot[poseIndex][1],
- .z=kPoseRot[poseIndex][2], .w=kPoseRot[poseIndex][3]},
- .translation = {.x=kPoseTrans[poseIndex][0], .y=kPoseTrans[poseIndex][1],
- .z=kPoseTrans[poseIndex][2]},
- },
- .horizontalFov = hfov,
- };
-
- const std::vector<View3d> views = {view3d};
- if (surroundView3dSession->setViews(views) != SvResult::OK) {
- return false;
- }
- return true;
-}
-
-bool run3dSurroundView(sp<ISurroundViewService> pSurroundViewService,
- sp<IEvsDisplay> pDisplay) {
- LOG(INFO) << "Run 3d Surround View demo";
-
- // Call HIDL API "start3dSession"
- sp<ISurroundView3dSession> surroundView3dSession;
-
- SvResult svResult;
- pSurroundViewService->start3dSession(
- [&surroundView3dSession, &svResult](
- const sp<ISurroundView3dSession>& session, SvResult result) {
- surroundView3dSession = session;
- svResult = result;
- });
-
- if (surroundView3dSession == nullptr || svResult != SvResult::OK) {
- LOG(ERROR) << "Failed to start3dSession";
- return false;
- } else {
- LOG(INFO) << "start3dSession succeeded";
- }
-
- sp<SurroundViewServiceCallback> sv3dCallback
- = new SurroundViewServiceCallback(pDisplay, surroundView3dSession);
-
- // A view must be set before the 3d stream is started.
- if (!setView(surroundView3dSession, 0, 0, kHorizontalFov)) {
- LOG(ERROR) << "Failed to setView of pose index :" << 0;
- return false;
- }
-
- // Start 3d stream with callback
- if (surroundView3dSession->startStream(sv3dCallback) != SvResult::OK) {
- LOG(ERROR) << "Failed to start 3d stream";
- return false;
- }
-
- // Let the SV algorithm run for 10 seconds for HIGH_QUALITY
- const int totalViewingTimeSecs = 10;
- const std::chrono::milliseconds
- perPoseSleepTimeMs(totalViewingTimeSecs * 1000 / kPoseCount);
- for(uint32_t i = 1; i < kPoseCount; i++) {
- if (!setView(surroundView3dSession, i, i, kHorizontalFov)) {
- LOG(WARNING) << "Failed to setView of pose index :" << i;
- }
- std::this_thread::sleep_for(perPoseSleepTimeMs);
- }
-
- // Switch to low quality and lower resolution
- Sv3dConfig config;
- config.width = kLowResolutionWidth;
- config.height = kLowResolutionHeight;
- config.carDetails = SvQuality::LOW;
- if (surroundView3dSession->set3dConfig(config) != SvResult::OK) {
- LOG(ERROR) << "Failed to set3dConfig";
- return false;
- }
-
- // Let the SV algorithm run for 10 seconds for LOW_QUALITY
- for(uint32_t i = 0; i < kPoseCount; i++) {
- if(!setView(surroundView3dSession, i + kPoseCount, i, kHorizontalFov)) {
- LOG(WARNING) << "Failed to setView of pose index :" << i;
- }
- std::this_thread::sleep_for(perPoseSleepTimeMs);
- }
-
- // TODO(b/150412555): wait for the last frame
- // Stop the 3d stream and session
- surroundView3dSession->stopStream();
-
- pSurroundViewService->stop3dSession(surroundView3dSession);
- surroundView3dSession = nullptr;
-
- LOG(DEBUG) << "SV 3D session finished.";
-
- return true;
-};
+using namespace android::hardware::automotive::sv::app;
// Main entry point
int main(int argc, char** argv) {
diff --git a/cpp/surround_view/service-impl/test_data/sv_sample_config.xml b/cpp/surround_view/service-impl/test_data/sv_sample_config.xml
index 9a4b124..1a92bee 100644
--- a/cpp/surround_view/service-impl/test_data/sv_sample_config.xml
+++ b/cpp/surround_view/service-impl/test_data/sv_sample_config.xml
@@ -21,8 +21,8 @@
<Sv2dEnabled>true</Sv2dEnabled>
<Sv2dParams>
<OutputResolution>
- <Width>768</Width>
- <Height>1024</Height>
+ <Width>1200</Width>
+ <Height>1600</Height>
</OutputResolution>
<GroundMapping>
<Width>9.0</Width>
@@ -44,7 +44,7 @@
<HighQuality>multiband</HighQuality>
<LowQuality>alpha</LowQuality>
</BlendingType>
- <GpuAccelerationEnabled>false</GpuAccelerationEnabled>
+ <GpuAccelerationEnabled>true</GpuAccelerationEnabled>
</Sv2dParams>
<Sv3dEnabled>true</Sv3dEnabled>
diff --git a/cpp/telemetry/ARCHITECTURE.md b/cpp/telemetry/ARCHITECTURE.md
new file mode 100644
index 0000000..75dd194
--- /dev/null
+++ b/cpp/telemetry/ARCHITECTURE.md
@@ -0,0 +1,22 @@
+# Architecture of Car Telemetry Service
+
+## Names
+
+- C++ namespace `android.automotive.telemetry` - for all the car telemetry related projects.
+- android.telemetry.ICarTelemetry - AIDL interface for collecting car data.
+- cartelemetryd (android.automotive.telemetryd) - a daemon that implements `ICarTelemetry`
+ interface.
+- CarTelemetryService - a part of CarService that executes scrits. Located in car services dir.
+
+## Structure
+
+```
+aidl/ - Internal AIDL declerations, for public AIDLs, please see
+ //frameworks/hardware/interfaces/automotive/telemetry
+products/ - AAOS Telemetry product, it's included in car_base.mk
+sepolicy - SELinux policies
+src/ - Source code
+ TelemetryServer.h - The main class.
+*.rc - rc file to start services
+*.xml - VINTF manifest (TODO: needed?)
+```
diff --git a/cpp/telemetry/Android.bp b/cpp/telemetry/Android.bp
new file mode 100644
index 0000000..4e6c1c0
--- /dev/null
+++ b/cpp/telemetry/Android.bp
@@ -0,0 +1,94 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_defaults {
+ name: "cartelemetryd_defaults",
+ cflags: [
+ "-Werror",
+ "-Wall",
+ "-Wno-unused-parameter",
+ ],
+ shared_libs: [
+ "android.automotive.telemetry.internal-ndk_platform",
+ "android.frameworks.automotive.telemetry-V1-ndk_platform",
+ "libbase",
+ "libbinder_ndk",
+ "liblog",
+ "libutils",
+ ],
+ product_variables: {
+ debuggable: {
+ cflags: [
+ "-DCARTELEMETRYD_DEBUG=true",
+ ]
+ }
+ },
+}
+
+cc_library {
+ name: "android.automotive.telemetryd@1.0-impl",
+ defaults: [
+ "cartelemetryd_defaults",
+ ],
+ srcs: [
+ "src/CarTelemetryImpl.cpp",
+ "src/CarTelemetryInternalImpl.cpp",
+ "src/RingBuffer.cpp",
+ "src/TelemetryServer.cpp",
+ ],
+ // Allow dependents to use the header files.
+ export_include_dirs: [
+ "src",
+ ],
+}
+
+cc_test {
+ name: "cartelemetryd_impl_test",
+ defaults: [
+ "cartelemetryd_defaults",
+ ],
+ test_suites: ["general-tests"],
+ srcs: [
+ "tests/CarTelemetryImplTest.cpp",
+ "tests/CarTelemetryInternalImplTest.cpp",
+ "tests/RingBufferTest.cpp",
+ ],
+ // Statically link only in tests, for portability reason.
+ static_libs: [
+ "android.automotive.telemetryd@1.0-impl",
+ "android.automotive.telemetry.internal-ndk_platform",
+ "android.frameworks.automotive.telemetry-V1-ndk_platform",
+ "libgmock",
+ "libgtest",
+ ],
+}
+
+cc_binary {
+ name: "android.automotive.telemetryd@1.0",
+ defaults: [
+ "cartelemetryd_defaults",
+ ],
+ srcs: [
+ "src/main.cpp"
+ ],
+ init_rc: ["android.automotive.telemetryd@1.0.rc"],
+ vintf_fragments: ["android.automotive.telemetryd@1.0.xml"],
+ shared_libs: [
+ "android.automotive.telemetryd@1.0-impl"
+ ],
+}
diff --git a/cpp/telemetry/OWNERS b/cpp/telemetry/OWNERS
new file mode 100644
index 0000000..80794e1
--- /dev/null
+++ b/cpp/telemetry/OWNERS
@@ -0,0 +1,3 @@
+sgurun@google.com
+zhomart@google.com
+mdashouk@google.com
diff --git a/cpp/telemetry/README.md b/cpp/telemetry/README.md
new file mode 100644
index 0000000..a13116d
--- /dev/null
+++ b/cpp/telemetry/README.md
@@ -0,0 +1,19 @@
+# Automotive Telemetry Service
+
+A structured log collection service for CarTelemetryService. See ARCHITECTURE.md to learn internals.
+
+## Useful Commands
+
+**Dump service information**
+
+`adb shell dumpsys android.automotive.telemetry.internal.ICarTelemetryInternal/default`
+
+**Starting emulator**
+
+`aae emulator run -selinux permissive -writable-system`
+
+**Running tests**
+
+`atest cartelemetryd_impl_test:CarTelemetryInternalImplTest#TestSetListenerReturnsOk`
+
+`atest cartelemetryd_impl_test`
diff --git a/cpp/telemetry/aidl/Android.bp b/cpp/telemetry/aidl/Android.bp
new file mode 100644
index 0000000..b4cd9c7
--- /dev/null
+++ b/cpp/telemetry/aidl/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aidl_interface {
+ name: "android.automotive.telemetry.internal",
+ unstable: true,
+ vendor_available: false,
+ srcs: [
+ "android/automotive/telemetry/internal/*.aidl",
+ ],
+ backend: {
+ ndk: {
+ enabled: true,
+ },
+ java: {
+ platform_apis: true,
+ enabled: true,
+ },
+ }
+}
diff --git a/cpp/telemetry/aidl/android/automotive/telemetry/internal/CarDataInternal.aidl b/cpp/telemetry/aidl/android/automotive/telemetry/internal/CarDataInternal.aidl
new file mode 100644
index 0000000..bf31169
--- /dev/null
+++ b/cpp/telemetry/aidl/android/automotive/telemetry/internal/CarDataInternal.aidl
@@ -0,0 +1,32 @@
+/*
+ * 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.automotive.telemetry.internal;
+
+/**
+ * Wrapper for {@code android.frameworks.automotive.telemetry.CarData}.
+ */
+parcelable CarDataInternal {
+ /**
+ * Must be a valid id. Scripts subscribe to data using this id.
+ */
+ int id;
+
+ /**
+ * Content corresponding to the schema defined by the id.
+ */
+ byte[] content;
+}
diff --git a/cpp/telemetry/aidl/android/automotive/telemetry/internal/ICarDataListener.aidl b/cpp/telemetry/aidl/android/automotive/telemetry/internal/ICarDataListener.aidl
new file mode 100644
index 0000000..48ab6f9
--- /dev/null
+++ b/cpp/telemetry/aidl/android/automotive/telemetry/internal/ICarDataListener.aidl
@@ -0,0 +1,35 @@
+/*
+ * 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.automotive.telemetry.internal;
+
+import android.automotive.telemetry.internal.CarDataInternal;
+
+/**
+ * Listener for {@code ICarTelemetryInternal#registerListener}.
+ */
+oneway interface ICarDataListener {
+ /**
+ * Called by ICarTelemetry when the data are available to be consumed. ICarTelemetry removes
+ * the delivered data when the callback succeeds.
+ *
+ * <p>If the collected data is too large, it will send only chunk of the data, and the callback
+ * will be fired again.
+ *
+ * @param dataList the pushed data.
+ */
+ void onCarDataReceived(in CarDataInternal[] dataList);
+}
diff --git a/cpp/telemetry/aidl/android/automotive/telemetry/internal/ICarTelemetryInternal.aidl b/cpp/telemetry/aidl/android/automotive/telemetry/internal/ICarTelemetryInternal.aidl
new file mode 100644
index 0000000..b8938ff
--- /dev/null
+++ b/cpp/telemetry/aidl/android/automotive/telemetry/internal/ICarTelemetryInternal.aidl
@@ -0,0 +1,38 @@
+/*
+ * 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.automotive.telemetry.internal;
+
+import android.automotive.telemetry.internal.ICarDataListener;
+
+/**
+ * An internal API provided by cartelemetryd for receiving the collected data.
+ */
+interface ICarTelemetryInternal {
+ /**
+ * Sets a listener for CarData. If there are existing CarData in the buffer, the daemon will
+ * start pushing them to the listener. There can be only a single registered listener at a time.
+ *
+ * @param listener the only listener.
+ * @throws IllegalStateException if someone is already registered or the listener is dead.
+ */
+ void setListener(in ICarDataListener listener);
+
+ /**
+ * Clears the listener if exists. Silently ignores if there is no listener.
+ */
+ void clearListener();
+}
diff --git a/cpp/telemetry/android.automotive.telemetryd@1.0.rc b/cpp/telemetry/android.automotive.telemetryd@1.0.rc
new file mode 100644
index 0000000..1513fa8
--- /dev/null
+++ b/cpp/telemetry/android.automotive.telemetryd@1.0.rc
@@ -0,0 +1,9 @@
+service cartelemetryd_service /system/bin/android.automotive.telemetryd@1.0
+ class core
+ user system
+ group system
+ disabled # starts below on early-init
+
+on early-init
+ # Start the service only after initializing the properties.
+ start cartelemetryd_service
diff --git a/cpp/telemetry/android.automotive.telemetryd@1.0.xml b/cpp/telemetry/android.automotive.telemetryd@1.0.xml
new file mode 100644
index 0000000..8156466
--- /dev/null
+++ b/cpp/telemetry/android.automotive.telemetryd@1.0.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright 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.
+-->
+<!-- Required VINTF manifest for AIDL servers. -->
+<manifest version="1.0" type="framework">
+ <hal format="aidl">
+ <name>android.frameworks.automotive.telemetry</name>
+ <version>1</version>
+ <fqname>ICarTelemetry/default</fqname>
+ </hal>
+</manifest>
diff --git a/cpp/telemetry/products/telemetry.mk b/cpp/telemetry/products/telemetry.mk
new file mode 100644
index 0000000..634c9a9
--- /dev/null
+++ b/cpp/telemetry/products/telemetry.mk
@@ -0,0 +1,22 @@
+# Copyright (C) 2021 The Android Open Source Project
+
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# cartelemetryd service
+PRODUCT_PACKAGES += android.automotive.telemetryd@1.0
+
+# Selinux public policies for cartelemetry
+PRODUCT_PUBLIC_SEPOLICY_DIRS += packages/services/Car/cpp/telemetry/sepolicy/public
+
+# Selinux private policies for cartelemetry
+PRODUCT_PRIVATE_SEPOLICY_DIRS += packages/services/Car/cpp/telemetry/sepolicy/private
diff --git a/cpp/telemetry/sampleclient/Android.bp b/cpp/telemetry/sampleclient/Android.bp
new file mode 100644
index 0000000..17fdee0
--- /dev/null
+++ b/cpp/telemetry/sampleclient/Android.bp
@@ -0,0 +1,37 @@
+// 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.
+
+// Sample client for ICarTelemetry service.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+ name: "android.automotive.telemetryd-sampleclient",
+ srcs: [
+ "main.cpp",
+ ],
+ vendor: true,
+ cflags: [
+ "-Werror",
+ "-Wall",
+ "-Wno-unused-parameter",
+ ],
+ shared_libs: [
+ "android.frameworks.automotive.telemetry-V1-ndk_platform",
+ "libbase",
+ "libbinder_ndk",
+ "libutils",
+ ],
+}
diff --git a/cpp/telemetry/sampleclient/README.md b/cpp/telemetry/sampleclient/README.md
new file mode 100644
index 0000000..ebbaf16
--- /dev/null
+++ b/cpp/telemetry/sampleclient/README.md
@@ -0,0 +1,63 @@
+# ICarTelemetry Sample Client
+
+This is a sample vendor service that sends `CarData` to car telemetry service.
+
+## Running
+
+**1. Quick mode - under root**
+
+```
+m -j android.automotive.telemetryd-sampleclient
+
+adb remount # make sure run "adb disable-verity" before remounting
+adb push $ANDROID_PRODUCT_OUT/vendor/bin/android.automotive.telemetryd-sampleclient /system/bin/
+
+adb shell /system/bin/android.automotive.telemetryd-sampleclient
+
+# Then check logcat and dumpsys to verify the results.
+```
+
+**2. Under vendor**
+
+To include it in the final image, add
+`PRODUCT_PACKAGES += android.automotive.telemetryd-sampleclient` to
+`//packages/services/Car/cpp/telemetry/products/telemetry.mk` (or other suitable mk file).
+
+```
+# this goes to products/telemetry.mk
+
+PRODUCT_PACKAGES += android.automotive.telemetryd-sampleclient
+```
+
+The sampleclient doesn't automatically start during boot, to start manually, run:
+`adb shell /vendor/bin/android.automotive.telemetryd-sampleclient`.
+
+If you want to test it by running `init`, add these SELinux rules:
+
+```
+# this goes to sepolicy/private/cartelemetryd.te
+
+type cartelemetryd_sample, domain;
+type cartelemetryd_sample_exec, vendor_file_type, exec_type, file_type;
+init_daemon_domain(cartelemetryd_sample)
+```
+
+```
+# this goes to sepolicy/private/file_contexts
+
+/vendor/bin/android\.automotive\.telemetryd-sampleclient u:object_r:cartelemetryd_sample_exec:s0
+```
+
+And create an `.rc` file:
+
+```
+# File: cartelemetryd-sampleclient.rc
+# Don't forget to add `init_rc: ["cartelemetryd-sampleclient.rc"],` to the Android.bp
+
+service cartelemetryd_sample /vendor/bin/android.automotive.telemetryd-sampleclient
+ class hal
+ user system
+ group system
+ oneshot # run once, otherwise init keeps restarting it
+ disabled # do not start automatically
+```
diff --git a/cpp/telemetry/sampleclient/main.cpp b/cpp/telemetry/sampleclient/main.cpp
new file mode 100644
index 0000000..e45dc65
--- /dev/null
+++ b/cpp/telemetry/sampleclient/main.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "cartelemetryd_sample"
+
+#include <aidl/android/frameworks/automotive/telemetry/ICarTelemetry.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android/binder_manager.h>
+#include <utils/SystemClock.h>
+
+using ::aidl::android::frameworks::automotive::telemetry::CarData;
+using ::aidl::android::frameworks::automotive::telemetry::ICarTelemetry;
+using ::android::base::StringPrintf;
+
+int main(int argc, char* argv[]) {
+ const auto started_at_millis = android::elapsedRealtime();
+
+ // The name of the service is described in
+ // https://source.android.com/devices/architecture/aidl/aidl-hals#instance-names
+ const std::string instance = StringPrintf("%s/default", ICarTelemetry::descriptor);
+ LOG(INFO) << "Obtaining: " << instance;
+ std::shared_ptr<ICarTelemetry> service = ICarTelemetry::fromBinder(
+ ndk::SpAIBinder(AServiceManager_getService(instance.c_str())));
+ if (!service) {
+ LOG(ERROR) << "ICarTelemetry service not found, may be still initializing?";
+ return 1;
+ }
+
+ LOG(INFO) << "Building a CarData message, delta_since_start: "
+ << android::elapsedRealtime() - started_at_millis << " millis";
+
+ // Build a CarData message
+ // TODO(b/174608802): set a correct data ID and content
+ CarData msg;
+ msg.id = 101;
+ msg.content = {1, 0, 1, 0};
+
+ LOG(INFO) << "Sending the car data, delta_since_start: "
+ << android::elapsedRealtime() - started_at_millis << " millis";
+
+ // Send the data
+ ndk::ScopedAStatus writeStatus = service->write({msg});
+
+ if (!writeStatus.isOk()) {
+ LOG(WARNING) << "Failed to write to the service: " << writeStatus.getMessage();
+ }
+
+ // Note: On a device the delta_since_start was between 1ms to 4ms
+ // (service side was not fully implemented yet during the test).
+ LOG(INFO) << "Finished, delta_since_start: " << android::elapsedRealtime() - started_at_millis
+ << " millis";
+
+ return 0;
+}
diff --git a/cpp/telemetry/sampleinternalclient/Android.bp b/cpp/telemetry/sampleinternalclient/Android.bp
new file mode 100644
index 0000000..d04ac09
--- /dev/null
+++ b/cpp/telemetry/sampleinternalclient/Android.bp
@@ -0,0 +1,36 @@
+// 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.
+
+// Sample client for ICarTelemetry service.
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+ name: "android.automotive.telemetryd-sampleinternalclient",
+ srcs: [
+ "main.cpp",
+ ],
+ cflags: [
+ "-Werror",
+ "-Wall",
+ "-Wno-unused-parameter",
+ ],
+ shared_libs: [
+ "android.automotive.telemetry.internal-ndk_platform",
+ "libbase",
+ "libbinder_ndk",
+ "libutils",
+ ],
+}
diff --git a/cpp/telemetry/sampleinternalclient/main.cpp b/cpp/telemetry/sampleinternalclient/main.cpp
new file mode 100644
index 0000000..06ca549
--- /dev/null
+++ b/cpp/telemetry/sampleinternalclient/main.cpp
@@ -0,0 +1,78 @@
+/*
+ * 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.
+ */
+
+// This is a sample reader client for ICarTelemetryInternal.
+// TODO(b/186017953): remove this client when CarTelemetryService is implemented.
+//
+// adb remount # make sure run "adb disable-verity" before remounting
+// adb push $ANDROID_PRODUCT_OUT/system/bin/android.automotive.telemetryd-sampleinternalclient
+// /system/bin/
+//
+// adb shell /system/bin/android.automotive.telemetryd-sampleinternalclient
+
+#define LOG_TAG "cartelemetryd_sampleint"
+
+#include <aidl/android/automotive/telemetry/internal/BnCarDataListener.h>
+#include <aidl/android/automotive/telemetry/internal/CarDataInternal.h>
+#include <aidl/android/automotive/telemetry/internal/ICarTelemetryInternal.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+using ::aidl::android::automotive::telemetry::internal::BnCarDataListener;
+using ::aidl::android::automotive::telemetry::internal::CarDataInternal;
+using ::aidl::android::automotive::telemetry::internal::ICarDataListener;
+using ::aidl::android::automotive::telemetry::internal::ICarTelemetryInternal;
+using ::android::base::StringPrintf;
+
+class CarDataListenerImpl : public BnCarDataListener {
+public:
+ ::ndk::ScopedAStatus onCarDataReceived(
+ const std::vector<CarDataInternal>& in_dataList) override;
+};
+
+::ndk::ScopedAStatus CarDataListenerImpl::onCarDataReceived(
+ const std::vector<CarDataInternal>& dataList) {
+ LOG(INFO) << "Received data size = " << dataList.size();
+ for (const auto data : dataList) {
+ LOG(INFO) << "data.id = " << data.id;
+ }
+ return ::ndk::ScopedAStatus::ok();
+}
+
+int main(int argc, char* argv[]) {
+ // The name of the service is described in
+ // https://source.android.com/devices/architecture/aidl/aidl-hals#instance-names
+ const std::string instance = StringPrintf("%s/default", ICarTelemetryInternal::descriptor);
+ LOG(INFO) << "Obtaining: " << instance;
+ std::shared_ptr<ICarTelemetryInternal> service = ICarTelemetryInternal::fromBinder(
+ ndk::SpAIBinder(AServiceManager_getService(instance.c_str())));
+ if (!service) {
+ LOG(FATAL) << "ICarTelemetryInternal service not found, may be still initializing?";
+ }
+
+ LOG(INFO) << "Setting the listener";
+ std::shared_ptr<CarDataListenerImpl> listener = ndk::SharedRefBase::make<CarDataListenerImpl>();
+ auto status = service->setListener(listener);
+ if (!status.isOk()) {
+ LOG(FATAL) << "Failed to set the listener";
+ }
+
+ ::ABinderProcess_startThreadPool();
+ ::ABinderProcess_joinThreadPool();
+ return 1; // not reachable
+}
diff --git a/cpp/telemetry/sepolicy/private/cartelemetryd.te b/cpp/telemetry/sepolicy/private/cartelemetryd.te
new file mode 100644
index 0000000..3baefb1
--- /dev/null
+++ b/cpp/telemetry/sepolicy/private/cartelemetryd.te
@@ -0,0 +1,9 @@
+### See //system/sepolicy/public/te_macros to learn about some SELinux macros.
+
+type cartelemetryd_exec, system_file_type, exec_type, file_type;
+
+binder_use(cartelemetryd)
+
+add_service(cartelemetryd, cartelemetryd_service)
+
+init_daemon_domain(cartelemetryd)
diff --git a/cpp/telemetry/sepolicy/private/file_contexts b/cpp/telemetry/sepolicy/private/file_contexts
new file mode 100644
index 0000000..0b0915d
--- /dev/null
+++ b/cpp/telemetry/sepolicy/private/file_contexts
@@ -0,0 +1 @@
+/system/bin/android\.automotive\.telemetryd@1\.0 u:object_r:cartelemetryd_exec:s0
diff --git a/cpp/telemetry/sepolicy/private/service_contexts b/cpp/telemetry/sepolicy/private/service_contexts
new file mode 100644
index 0000000..f26b5f8
--- /dev/null
+++ b/cpp/telemetry/sepolicy/private/service_contexts
@@ -0,0 +1,2 @@
+android.frameworks.automotive.telemetry.ICarTelemetry/default u:object_r:cartelemetryd_service:s0
+android.automotive.telemetry.internal.ICarTelemetryInternal/default u:object_r:cartelemetryd_service:s0
diff --git a/cpp/telemetry/sepolicy/public/cartelemetryd.te b/cpp/telemetry/sepolicy/public/cartelemetryd.te
new file mode 100644
index 0000000..fab4d58
--- /dev/null
+++ b/cpp/telemetry/sepolicy/public/cartelemetryd.te
@@ -0,0 +1 @@
+type cartelemetryd, domain, coredomain;
diff --git a/cpp/telemetry/sepolicy/public/service.te b/cpp/telemetry/sepolicy/public/service.te
new file mode 100644
index 0000000..f0beee8
--- /dev/null
+++ b/cpp/telemetry/sepolicy/public/service.te
@@ -0,0 +1 @@
+type cartelemetryd_service, service_manager_type;
diff --git a/cpp/telemetry/src/BufferedCarData.h b/cpp/telemetry/src/BufferedCarData.h
new file mode 100644
index 0000000..0c6ff4d
--- /dev/null
+++ b/cpp/telemetry/src/BufferedCarData.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CPP_TELEMETRY_SRC_BUFFEREDCARDATA_H_
+#define CPP_TELEMETRY_SRC_BUFFEREDCARDATA_H_
+
+#include <stdint.h>
+
+#include <tuple>
+#include <vector>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+// Internally stored `CarData` with some extras.
+struct BufferedCarData {
+ BufferedCarData(BufferedCarData&& other) = default;
+ BufferedCarData(const BufferedCarData&) = default;
+ BufferedCarData& operator=(BufferedCarData&& other) = default;
+ BufferedCarData& operator=(const BufferedCarData&) = default;
+
+ inline bool operator==(const BufferedCarData& rhs) const {
+ return std::tie(mId, mContent, mPublisherUid) ==
+ std::tie(rhs.mId, rhs.mContent, rhs.mPublisherUid);
+ }
+
+ // Returns the size of the stored data. Note that it's not the exact size of the struct.
+ int32_t contentSizeInBytes() const { return mContent.size(); }
+
+ const int32_t mId;
+ const std::vector<uint8_t> mContent;
+
+ // The uid of the logging client.
+ const uid_t mPublisherUid;
+};
+
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
+
+#endif // CPP_TELEMETRY_SRC_BUFFEREDCARDATA_H_
diff --git a/cpp/telemetry/src/CarTelemetryImpl.cpp b/cpp/telemetry/src/CarTelemetryImpl.cpp
new file mode 100644
index 0000000..34a4e60
--- /dev/null
+++ b/cpp/telemetry/src/CarTelemetryImpl.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "CarTelemetryImpl.h"
+
+#include "BufferedCarData.h"
+
+#include <aidl/android/frameworks/automotive/telemetry/CarData.h>
+#include <android/binder_ibinder.h>
+
+#include <stdio.h>
+
+#include <memory>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+using ::aidl::android::frameworks::automotive::telemetry::CarData;
+
+CarTelemetryImpl::CarTelemetryImpl(RingBuffer* buffer) : mRingBuffer(buffer) {}
+
+// TODO(b/174608802): Add 10kb size check for the `dataList`, see the AIDL for the limits
+ndk::ScopedAStatus CarTelemetryImpl::write(const std::vector<CarData>& dataList) {
+ uid_t publisherUid = ::AIBinder_getCallingUid();
+ for (auto&& data : dataList) {
+ mRingBuffer->push({.mId = data.id,
+ .mContent = std::move(data.content),
+ .mPublisherUid = publisherUid});
+ }
+ return ndk::ScopedAStatus::ok();
+}
+
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
diff --git a/cpp/telemetry/src/CarTelemetryImpl.h b/cpp/telemetry/src/CarTelemetryImpl.h
new file mode 100644
index 0000000..a3bb6c1
--- /dev/null
+++ b/cpp/telemetry/src/CarTelemetryImpl.h
@@ -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.
+ */
+
+#ifndef CPP_TELEMETRY_SRC_CARTELEMETRYIMPL_H_
+#define CPP_TELEMETRY_SRC_CARTELEMETRYIMPL_H_
+
+#include "RingBuffer.h"
+
+#include <aidl/android/frameworks/automotive/telemetry/BnCarTelemetry.h>
+#include <aidl/android/frameworks/automotive/telemetry/CarData.h>
+#include <utils/String16.h>
+#include <utils/Vector.h>
+
+#include <vector>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+// Implementation of android.frameworks.automotive.telemetry.ICarTelemetry.
+class CarTelemetryImpl : public aidl::android::frameworks::automotive::telemetry::BnCarTelemetry {
+public:
+ // Doesn't own `buffer`.
+ explicit CarTelemetryImpl(RingBuffer* buffer);
+
+ ndk::ScopedAStatus write(
+ const std::vector<aidl::android::frameworks::automotive::telemetry::CarData>& dataList)
+ override;
+
+private:
+ RingBuffer* mRingBuffer; // not owned
+};
+
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
+
+#endif // CPP_TELEMETRY_SRC_CARTELEMETRYIMPL_H_
diff --git a/cpp/telemetry/src/CarTelemetryInternalImpl.cpp b/cpp/telemetry/src/CarTelemetryInternalImpl.cpp
new file mode 100644
index 0000000..7a3b141
--- /dev/null
+++ b/cpp/telemetry/src/CarTelemetryInternalImpl.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "CarTelemetryInternalImpl.h"
+
+#include <aidl/android/automotive/telemetry/internal/BnCarDataListener.h>
+#include <aidl/android/automotive/telemetry/internal/CarDataInternal.h>
+#include <aidl/android/automotive/telemetry/internal/ICarDataListener.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/strings.h>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+using ::aidl::android::automotive::telemetry::internal::BnCarDataListener;
+using ::aidl::android::automotive::telemetry::internal::CarDataInternal;
+using ::aidl::android::automotive::telemetry::internal::ICarDataListener;
+using ::android::base::StringPrintf;
+
+CarTelemetryInternalImpl::CarTelemetryInternalImpl(RingBuffer* buffer) :
+ mRingBuffer(buffer),
+ mBinderDeathRecipient(
+ ::AIBinder_DeathRecipient_new(CarTelemetryInternalImpl::listenerBinderDied)) {}
+
+ndk::ScopedAStatus CarTelemetryInternalImpl::setListener(
+ const std::shared_ptr<ICarDataListener>& listener) {
+ const std::scoped_lock<std::mutex> lock(mMutex);
+
+ if (mCarDataListener != nullptr) {
+ return ndk::ScopedAStatus::fromExceptionCodeWithMessage(::EX_ILLEGAL_STATE,
+ "ICarDataListener is already set.");
+ }
+
+ // If passed a local binder, AIBinder_linkToDeath will do nothing and return
+ // STATUS_INVALID_OPERATION. We ignore this case because we only use local binders in tests
+ // where this is not an error.
+ if (listener->isRemote()) {
+ auto status = ndk::ScopedAStatus::fromStatus(
+ ::AIBinder_linkToDeath(listener->asBinder().get(), mBinderDeathRecipient.get(),
+ this));
+ if (!status.isOk()) {
+ return ndk::ScopedAStatus::fromExceptionCodeWithMessage(::EX_ILLEGAL_STATE,
+ status.getMessage());
+ }
+ }
+
+ mCarDataListener = listener;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus CarTelemetryInternalImpl::clearListener() {
+ const std::scoped_lock<std::mutex> lock(mMutex);
+ if (mCarDataListener == nullptr) {
+ LOG(INFO) << __func__ << ": No ICarDataListener, ignoring the call";
+ return ndk::ScopedAStatus::ok();
+ }
+ auto status = ndk::ScopedAStatus::fromStatus(
+ ::AIBinder_unlinkToDeath(mCarDataListener->asBinder().get(),
+ mBinderDeathRecipient.get(), this));
+ if (!status.isOk()) {
+ LOG(WARNING) << __func__
+ << ": unlinkToDeath failed, continuing anyway: " << status.getMessage();
+ }
+ mCarDataListener = nullptr;
+ return ndk::ScopedAStatus::ok();
+}
+
+binder_status_t CarTelemetryInternalImpl::dump(int fd, const char** args, uint32_t numArgs) {
+ dprintf(fd, "ICarTelemetryInternal:\n");
+ mRingBuffer->dump(fd);
+ return ::STATUS_OK;
+}
+
+// Removes the listener if its binder dies.
+void CarTelemetryInternalImpl::listenerBinderDiedImpl() {
+ LOG(WARNING) << "A ICarDataListener died, removing the listener.";
+ const std::scoped_lock<std::mutex> lock(mMutex);
+ mCarDataListener = nullptr;
+}
+
+void CarTelemetryInternalImpl::listenerBinderDied(void* cookie) {
+ auto thiz = static_cast<CarTelemetryInternalImpl*>(cookie);
+ thiz->listenerBinderDiedImpl();
+}
+
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
diff --git a/cpp/telemetry/src/CarTelemetryInternalImpl.h b/cpp/telemetry/src/CarTelemetryInternalImpl.h
new file mode 100644
index 0000000..12ad5cd
--- /dev/null
+++ b/cpp/telemetry/src/CarTelemetryInternalImpl.h
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CPP_TELEMETRY_SRC_CARTELEMETRYINTERNALIMPL_H_
+#define CPP_TELEMETRY_SRC_CARTELEMETRYINTERNALIMPL_H_
+
+#include "RingBuffer.h"
+
+#include <aidl/android/automotive/telemetry/internal/BnCarTelemetryInternal.h>
+#include <aidl/android/automotive/telemetry/internal/CarDataInternal.h>
+#include <aidl/android/automotive/telemetry/internal/ICarDataListener.h>
+#include <android/binder_status.h>
+#include <utils/Mutex.h>
+#include <utils/String16.h>
+#include <utils/Vector.h>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+// Implementation of android.automotive.telemetry.ICarTelemetryInternal.
+class CarTelemetryInternalImpl :
+ public aidl::android::automotive::telemetry::internal::BnCarTelemetryInternal {
+public:
+ // Doesn't own `buffer`.
+ explicit CarTelemetryInternalImpl(RingBuffer* buffer);
+
+ ndk::ScopedAStatus setListener(
+ const std::shared_ptr<aidl::android::automotive::telemetry::internal::ICarDataListener>&
+ listener) override;
+
+ ndk::ScopedAStatus clearListener() override;
+
+ binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
+
+private:
+ // Death recipient callback that is called when ICarDataListener dies.
+ // The cookie is a pointer to a CarTelemetryInternalImpl object.
+ static void listenerBinderDied(void* cookie);
+
+ void listenerBinderDiedImpl();
+
+ RingBuffer* mRingBuffer; // not owned
+ ndk::ScopedAIBinder_DeathRecipient mBinderDeathRecipient;
+ std::mutex mMutex; // a mutex for the whole instance
+
+ std::shared_ptr<aidl::android::automotive::telemetry::internal::ICarDataListener>
+ mCarDataListener GUARDED_BY(mMutex);
+};
+
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
+
+#endif // CPP_TELEMETRY_SRC_CARTELEMETRYINTERNALIMPL_H_
diff --git a/cpp/telemetry/src/RingBuffer.cpp b/cpp/telemetry/src/RingBuffer.cpp
new file mode 100644
index 0000000..36de3f8
--- /dev/null
+++ b/cpp/telemetry/src/RingBuffer.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RingBuffer.h"
+
+#include <android-base/logging.h>
+
+#include <inttypes.h> // for PRIu64 and friends
+
+#include <memory>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+RingBuffer::RingBuffer(int32_t limit) : mSizeLimit(limit) {}
+
+void RingBuffer::push(BufferedCarData&& data) {
+ const std::scoped_lock<std::mutex> lock(mMutex);
+ mList.push_back(std::move(data));
+ while (mList.size() > mSizeLimit) {
+ mList.pop_front();
+ mTotalDroppedDataCount += 1;
+ }
+}
+
+BufferedCarData RingBuffer::popFront() {
+ const std::scoped_lock<std::mutex> lock(mMutex);
+ auto result = std::move(mList.front());
+ mList.pop_front();
+ return result;
+}
+
+void RingBuffer::dump(int fd) const {
+ const std::scoped_lock<std::mutex> lock(mMutex);
+ dprintf(fd, "RingBuffer:\n");
+ dprintf(fd, " mSizeLimit=%d\n", mSizeLimit);
+ dprintf(fd, " mList.size=%zu\n", mList.size());
+ dprintf(fd, " mTotalDroppedDataCount=%" PRIu64 "\n", mTotalDroppedDataCount);
+}
+
+int32_t RingBuffer::size() const {
+ const std::scoped_lock<std::mutex> lock(mMutex);
+ return mList.size();
+}
+
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
diff --git a/cpp/telemetry/src/RingBuffer.h b/cpp/telemetry/src/RingBuffer.h
new file mode 100644
index 0000000..07ce709
--- /dev/null
+++ b/cpp/telemetry/src/RingBuffer.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CPP_TELEMETRY_SRC_RINGBUFFER_H_
+#define CPP_TELEMETRY_SRC_RINGBUFFER_H_
+
+#include "BufferedCarData.h"
+
+#include <list>
+#include <mutex>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+// A ring buffer that holds BufferedCarData. It drops old data if it's full.
+// Thread-safe.
+class RingBuffer {
+public:
+ // RingBuffer limits the number of elements in the buffer to the given param `sizeLimit`.
+ // Doesn't pre-allocate the memory.
+ explicit RingBuffer(int32_t sizeLimit);
+
+ // Not copyable or movable
+ RingBuffer(const RingBuffer&) = delete;
+ RingBuffer& operator=(const RingBuffer&) = delete;
+ RingBuffer(RingBuffer&&) = delete;
+ RingBuffer& operator=(RingBuffer&&) = delete;
+
+ // Pushes the data to the buffer. If the buffer is full, it removes the oldest data.
+ // Supports moving the data to the RingBuffer.
+ void push(BufferedCarData&& data);
+
+ // Returns the oldest element from the ring buffer and removes it from the buffer.
+ BufferedCarData popFront();
+
+ // Dumps the current state for dumpsys.
+ void dump(int fd) const;
+
+ // Returns the number of elements in the buffer.
+ int32_t size() const;
+
+private:
+ mutable std::mutex mMutex; // a mutex for the whole instance
+
+ const int32_t mSizeLimit;
+
+ // TODO(b/174608802): Improve dropped CarData handling, see ag/13818937 for details.
+ int64_t mTotalDroppedDataCount;
+
+ // Linked list that holds all the data and allows deleting old data when the buffer is full.
+ std::list<BufferedCarData> mList;
+};
+
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
+
+#endif // CPP_TELEMETRY_SRC_RINGBUFFER_H_
diff --git a/cpp/telemetry/src/TelemetryServer.cpp b/cpp/telemetry/src/TelemetryServer.cpp
new file mode 100644
index 0000000..54cd3c4
--- /dev/null
+++ b/cpp/telemetry/src/TelemetryServer.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TelemetryServer.h"
+
+#include "CarTelemetryImpl.h"
+#include "RingBuffer.h"
+
+#include <android-base/chrono_utils.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android/binder_interface_utils.h>
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+
+#include <inttypes.h> // for PRIu64 and friends
+
+#include <memory>
+#include <thread> // NOLINT(build/c++11)
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+using ::android::automotive::telemetry::RingBuffer;
+
+constexpr const char kCarTelemetryServiceName[] =
+ "android.frameworks.automotive.telemetry.ICarTelemetry/default";
+constexpr const char kCarTelemetryInternalServiceName[] =
+ "android.automotive.telemetry.internal.ICarTelemetryInternal/default";
+
+// TODO(b/183444070): make it configurable using sysprop
+// CarData count limit in the RingBuffer. In worst case it will use kMaxBufferSize * 10Kb memory,
+// which is ~ 1MB.
+const int kMaxBufferSize = 100;
+
+TelemetryServer::TelemetryServer() : mRingBuffer(kMaxBufferSize) {}
+
+void TelemetryServer::registerServices() {
+ std::shared_ptr<CarTelemetryImpl> telemetry =
+ ndk::SharedRefBase::make<CarTelemetryImpl>(&mRingBuffer);
+ std::shared_ptr<CarTelemetryInternalImpl> telemetryInternal =
+ ndk::SharedRefBase::make<CarTelemetryInternalImpl>(&mRingBuffer);
+
+ // Wait for the service manager before starting ICarTelemetry service.
+ while (android::base::GetProperty("init.svc.servicemanager", "") != "running") {
+ // Poll frequent enough so the writer clients can connect to the service during boot.
+ std::this_thread::sleep_for(250ms);
+ }
+
+ LOG(VERBOSE) << "Registering " << kCarTelemetryServiceName;
+ binder_exception_t exception =
+ ::AServiceManager_addService(telemetry->asBinder().get(), kCarTelemetryServiceName);
+ if (exception != ::EX_NONE) {
+ LOG(FATAL) << "Unable to register " << kCarTelemetryServiceName
+ << ", exception=" << exception;
+ }
+
+ LOG(VERBOSE) << "Registering " << kCarTelemetryInternalServiceName;
+ exception = ::AServiceManager_addService(telemetryInternal->asBinder().get(),
+ kCarTelemetryInternalServiceName);
+ if (exception != ::EX_NONE) {
+ LOG(FATAL) << "Unable to register " << kCarTelemetryInternalServiceName
+ << ", exception=" << exception;
+ }
+}
+
+void TelemetryServer::startAndJoinThreadPool() {
+ ::ABinderProcess_startThreadPool(); // Starts the default 15 binder threads.
+ ::ABinderProcess_joinThreadPool();
+}
+
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
diff --git a/cpp/telemetry/src/TelemetryServer.h b/cpp/telemetry/src/TelemetryServer.h
new file mode 100644
index 0000000..0b400a1
--- /dev/null
+++ b/cpp/telemetry/src/TelemetryServer.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef CPP_TELEMETRY_SRC_TELEMETRYSERVER_H_
+#define CPP_TELEMETRY_SRC_TELEMETRYSERVER_H_
+
+#include "CarTelemetryImpl.h"
+#include "CarTelemetryInternalImpl.h"
+
+#include <utils/Errors.h>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+class TelemetryServer {
+public:
+ TelemetryServer();
+
+ // Registers all the implemented AIDL services. Waits until `servicemanager` is available.
+ // Aborts the process if fails.
+ void registerServices();
+
+ // Blocks the thread.
+ void startAndJoinThreadPool();
+
+private:
+ RingBuffer mRingBuffer;
+};
+
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
+
+#endif // CPP_TELEMETRY_SRC_TELEMETRYSERVER_H_
diff --git a/cpp/telemetry/src/main.cpp b/cpp/telemetry/src/main.cpp
new file mode 100644
index 0000000..1bd0dde
--- /dev/null
+++ b/cpp/telemetry/src/main.cpp
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2021, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TelemetryServer.h"
+
+#include <android-base/logging.h>
+
+using ::android::automotive::telemetry::TelemetryServer;
+
+// TODO(b/174608802): handle SIGQUIT/SIGTERM
+
+int main(void) {
+ LOG(INFO) << "Starting cartelemetryd";
+
+ TelemetryServer server;
+
+ // Register AIDL services. Aborts the server if fails.
+ server.registerServices();
+
+ LOG(VERBOSE) << "Service is created, joining the threadpool";
+ server.startAndJoinThreadPool();
+ return 1; // never reaches
+}
diff --git a/cpp/telemetry/tests/CarTelemetryImplTest.cpp b/cpp/telemetry/tests/CarTelemetryImplTest.cpp
new file mode 100644
index 0000000..0286477
--- /dev/null
+++ b/cpp/telemetry/tests/CarTelemetryImplTest.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "CarTelemetryImpl.h"
+#include "RingBuffer.h"
+
+#include <aidl/android/frameworks/automotive/telemetry/CarData.h>
+#include <aidl/android/frameworks/automotive/telemetry/ICarTelemetry.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <unistd.h>
+
+#include <memory>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+using ::aidl::android::frameworks::automotive::telemetry::CarData;
+using ::aidl::android::frameworks::automotive::telemetry::ICarTelemetry;
+using ::testing::ContainerEq;
+
+const size_t kMaxBufferSize = 5;
+
+CarData buildCarData(int id, const std::vector<uint8_t>& content) {
+ CarData msg;
+ msg.id = id;
+ msg.content = content;
+ return msg;
+}
+
+BufferedCarData buildBufferedCarData(const CarData& data, uid_t publisherUid) {
+ return {.mId = data.id, .mContent = std::move(data.content), .mPublisherUid = publisherUid};
+}
+
+class CarTelemetryImplTest : public ::testing::Test {
+protected:
+ CarTelemetryImplTest() :
+ mBuffer(RingBuffer(kMaxBufferSize)),
+ mTelemetry(ndk::SharedRefBase::make<CarTelemetryImpl>(&mBuffer)) {}
+
+ RingBuffer mBuffer;
+ std::shared_ptr<ICarTelemetry> mTelemetry;
+};
+
+TEST_F(CarTelemetryImplTest, WriteReturnsOkStatus) {
+ CarData msg = buildCarData(101, {1, 0, 1, 0});
+
+ auto status = mTelemetry->write({msg});
+
+ EXPECT_TRUE(status.isOk()) << status.getMessage();
+}
+
+TEST_F(CarTelemetryImplTest, WriteAddsCarDataToRingBuffer) {
+ CarData msg = buildCarData(101, {1, 0, 1, 0});
+
+ mTelemetry->write({msg});
+
+ EXPECT_EQ(mBuffer.popFront(), buildBufferedCarData(msg, getuid()));
+}
+
+TEST_F(CarTelemetryImplTest, WriteBuffersOnlyLimitedAmount) {
+ RingBuffer buffer(/* sizeLimit= */ 3);
+ auto telemetry = ndk::SharedRefBase::make<CarTelemetryImpl>(&buffer);
+
+ CarData msg101_2 = buildCarData(101, {1, 0});
+ CarData msg101_4 = buildCarData(101, {1, 0, 1, 0});
+ CarData msg201_3 = buildCarData(201, {3, 3, 3});
+
+ // Inserting 5 elements
+ telemetry->write({msg101_2, msg101_4, msg101_4, msg201_3});
+ telemetry->write({msg201_3});
+
+ EXPECT_EQ(buffer.size(), 3);
+ std::vector<BufferedCarData> result = {buffer.popFront(), buffer.popFront(), buffer.popFront()};
+ std::vector<BufferedCarData> expected = {buildBufferedCarData(msg101_4, getuid()),
+ buildBufferedCarData(msg201_3, getuid()),
+ buildBufferedCarData(msg201_3, getuid())};
+ EXPECT_THAT(result, ContainerEq(expected));
+ EXPECT_EQ(buffer.size(), 0);
+}
+
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
diff --git a/cpp/telemetry/tests/CarTelemetryInternalImplTest.cpp b/cpp/telemetry/tests/CarTelemetryInternalImplTest.cpp
new file mode 100644
index 0000000..22838cd
--- /dev/null
+++ b/cpp/telemetry/tests/CarTelemetryInternalImplTest.cpp
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "CarTelemetryInternalImpl.h"
+#include "RingBuffer.h"
+
+#include <aidl/android/automotive/telemetry/internal/BnCarDataListener.h>
+#include <aidl/android/automotive/telemetry/internal/CarDataInternal.h>
+#include <aidl/android/automotive/telemetry/internal/ICarTelemetryInternal.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <unistd.h>
+
+#include <memory>
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+using ::aidl::android::automotive::telemetry::internal::BnCarDataListener;
+using ::aidl::android::automotive::telemetry::internal::CarDataInternal;
+using ::aidl::android::automotive::telemetry::internal::ICarTelemetryInternal;
+using ::ndk::ScopedAStatus;
+
+const size_t kMaxBufferSize = 5;
+
+class MockCarDataListener : public BnCarDataListener {
+public:
+ MOCK_METHOD(ScopedAStatus, onCarDataReceived, (const std::vector<CarDataInternal>& dataList),
+ (override));
+};
+
+// The main test class.
+class CarTelemetryInternalImplTest : public ::testing::Test {
+protected:
+ CarTelemetryInternalImplTest() :
+ mBuffer(RingBuffer(kMaxBufferSize)),
+ mTelemetryInternal(ndk::SharedRefBase::make<CarTelemetryInternalImpl>(&mBuffer)),
+ mMockCarDataListener(ndk::SharedRefBase::make<MockCarDataListener>()) {}
+
+ RingBuffer mBuffer;
+ std::shared_ptr<ICarTelemetryInternal> mTelemetryInternal;
+ std::shared_ptr<MockCarDataListener> mMockCarDataListener;
+};
+
+TEST_F(CarTelemetryInternalImplTest, SetListenerReturnsOk) {
+ auto status = mTelemetryInternal->setListener(mMockCarDataListener);
+
+ EXPECT_TRUE(status.isOk()) << status.getMessage();
+}
+
+TEST_F(CarTelemetryInternalImplTest, SetListenerFailsWhenAlreadySubscribed) {
+ mTelemetryInternal->setListener(mMockCarDataListener);
+
+ auto status = mTelemetryInternal->setListener(ndk::SharedRefBase::make<MockCarDataListener>());
+
+ EXPECT_EQ(status.getExceptionCode(), ::EX_ILLEGAL_STATE) << status.getMessage();
+}
+
+TEST_F(CarTelemetryInternalImplTest, ClearListenerWorks) {
+ mTelemetryInternal->setListener(mMockCarDataListener);
+
+ mTelemetryInternal->clearListener();
+ auto status = mTelemetryInternal->setListener(mMockCarDataListener);
+
+ EXPECT_TRUE(status.isOk()) << status.getMessage();
+}
+
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
diff --git a/cpp/telemetry/tests/RingBufferTest.cpp b/cpp/telemetry/tests/RingBufferTest.cpp
new file mode 100644
index 0000000..c5b5bc7
--- /dev/null
+++ b/cpp/telemetry/tests/RingBufferTest.cpp
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "RingBuffer.h"
+
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <memory>
+
+// NOTE: many of RingBuffer's behaviors are tested as part of CarTelemetryImpl.
+
+namespace android {
+namespace automotive {
+namespace telemetry {
+
+using testing::ContainerEq;
+
+BufferedCarData buildBufferedCarData(int32_t id, const std::vector<uint8_t>& content) {
+ return {.mId = id, .mContent = content, .mPublisherUid = 0};
+}
+
+TEST(RingBufferTest, PopFrontReturnsCorrectResults) {
+ RingBuffer buffer(/* sizeLimit= */ 10);
+ buffer.push(buildBufferedCarData(101, {7}));
+ buffer.push(buildBufferedCarData(102, {7}));
+
+ BufferedCarData result = buffer.popFront();
+
+ EXPECT_EQ(result, buildBufferedCarData(101, {7}));
+}
+
+TEST(RingBufferTest, PopFrontRemovesFromBuffer) {
+ RingBuffer buffer(/* sizeLimit= */ 10);
+ buffer.push(buildBufferedCarData(101, {7}));
+ buffer.push(buildBufferedCarData(102, {7, 8}));
+
+ buffer.popFront();
+
+ EXPECT_EQ(buffer.size(), 1); // only ID=102 left
+}
+
+} // namespace telemetry
+} // namespace automotive
+} // namespace android
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/ICarWatchdogServiceForSystem.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/ICarWatchdogServiceForSystem.aidl
index 9aa234f..ef68dbc 100644
--- a/cpp/watchdog/aidl/android/automotive/watchdog/internal/ICarWatchdogServiceForSystem.aidl
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/ICarWatchdogServiceForSystem.aidl
@@ -52,7 +52,8 @@
* @param uids List of UIDs to resolve the package infos.
* @param vendorPackagePrefixes List of vendor package prefixes.
*/
- List<PackageInfo> getPackageInfosForUids(in int[] uids, in List<String> vendorPackagePrefixes);
+ List<PackageInfo> getPackageInfosForUids(
+ in int[] uids, in @utf8InCpp List<String> vendorPackagePrefixes);
/**
* Pushes the latest I/O overuse stats to the watchdog server.
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageIdentifier.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageIdentifier.aidl
index fa6e36b..c0dd86e 100644
--- a/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageIdentifier.aidl
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageIdentifier.aidl
@@ -23,7 +23,7 @@
/**
* Name of the package.
*/
- String name;
+ @utf8InCpp String name;
/**
* UID of the package.
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageInfo.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageInfo.aidl
index dfcfa9a..2deb5f3 100644
--- a/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageInfo.aidl
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/PackageInfo.aidl
@@ -38,7 +38,7 @@
/**
* List of packages owned by the package. This list is empty when the UID is not a shared UID.
*/
- List<String> sharedUidPackages;
+ @utf8InCpp List<String> sharedUidPackages;
/**
* Component type of the package and the owned packages.
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/PerStateIoOveruseThreshold.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/PerStateIoOveruseThreshold.aidl
index 00a3c93..65f50ce 100644
--- a/cpp/watchdog/aidl/android/automotive/watchdog/internal/PerStateIoOveruseThreshold.aidl
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/PerStateIoOveruseThreshold.aidl
@@ -29,7 +29,7 @@
* 2. package name for package specific thresholds.
* 3. string equivalent of ApplicationCategoryType enum for category specific thresholds.
*/
- String name;
+ @utf8InCpp String name;
/**
* Defines the I/O overuse thresholds for a package. The thresholds are defined in terms of
diff --git a/cpp/watchdog/aidl/android/automotive/watchdog/internal/ResourceOveruseConfiguration.aidl b/cpp/watchdog/aidl/android/automotive/watchdog/internal/ResourceOveruseConfiguration.aidl
index 6cb6f23..26ae106 100644
--- a/cpp/watchdog/aidl/android/automotive/watchdog/internal/ResourceOveruseConfiguration.aidl
+++ b/cpp/watchdog/aidl/android/automotive/watchdog/internal/ResourceOveruseConfiguration.aidl
@@ -33,14 +33,14 @@
* List of only non-critical system and vendor packages that are safe to kill on disk I/O
* overuse. All third-party packages are considered safe to kill.
*/
- List<String> safeToKillPackages;
+ @utf8InCpp List<String> safeToKillPackages;
/**
* Defines the list of vendor package prefixes. Any pre-installed package name starting with one
* of these prefixes will be identified as a vendor package in addition to packages under the
* vendor partition. This must be defined only by the vendor component.
*/
- List<String> vendorPackagePrefixes;
+ @utf8InCpp List<String> vendorPackagePrefixes;
/**
* Defines the package metadata.
diff --git a/cpp/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java b/cpp/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java
index 0f6cce1..57d18b0 100644
--- a/cpp/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java
+++ b/cpp/watchdog/car-watchdog-lib/src/android/car/watchdoglib/CarWatchdogDaemonHelper.java
@@ -24,6 +24,7 @@
import android.automotive.watchdog.internal.ICarWatchdogMonitor;
import android.automotive.watchdog.internal.ICarWatchdogServiceForSystem;
import android.automotive.watchdog.internal.PackageResourceOveruseAction;
+import android.automotive.watchdog.internal.ResourceOveruseConfiguration;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
@@ -34,6 +35,7 @@
import com.android.internal.annotations.GuardedBy;
+import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
@@ -46,9 +48,11 @@
public final class CarWatchdogDaemonHelper {
private static final String TAG = CarWatchdogDaemonHelper.class.getSimpleName();
- // Carwatchdog daemon polls for the service manager status once every 250 milliseconds.
- // CAR_WATCHDOG_DAEMON_BIND_RETRY_INTERVAL_MS value should be at least twice the poll interval
- // used by the daemon.
+ /*
+ * Car watchdog daemon polls for the service manager status once every 250 milliseconds.
+ * CAR_WATCHDOG_DAEMON_BIND_RETRY_INTERVAL_MS value should be at least twice the poll interval
+ * used by the daemon.
+ */
private static final long CAR_WATCHDOG_DAEMON_BIND_RETRY_INTERVAL_MS = 500;
private static final long CAR_WATCHDOG_DAEMON_FIND_MARGINAL_TIME_MS = 300;
private static final int CAR_WATCHDOG_DAEMON_BIND_MAX_RETRY = 3;
@@ -250,10 +254,37 @@
}
/**
+ * Sets the given resource overuse configurations.
+ *
+ * @param configurations Resource overuse configuration per component type.
+ * @throws IllegalArgumentException If the configurations are invalid.
+ * @throws RemoteException
+ */
+ public void updateResourceOveruseConfigurations(
+ List<ResourceOveruseConfiguration> configurations) throws RemoteException {
+ invokeDaemonMethod((daemon) -> daemon.updateResourceOveruseConfigurations(configurations));
+ }
+
+ /**
+ * Returns the available resource overuse configurations.
+ *
+ * @throws RemoteException
+ */
+ public List<ResourceOveruseConfiguration> getResourceOveruseConfigurations()
+ throws RemoteException {
+ List<ResourceOveruseConfiguration> configurations = new ArrayList<>();
+ invokeDaemonMethod((daemon) -> {
+ configurations.addAll(daemon.getResourceOveruseConfigurations());
+ });
+ return configurations;
+ }
+
+ /**
* Notifies car watchdog daemon with the actions taken on resource overuse.
*
* @param actions List of actions taken on resource overuse. One action taken per resource
* overusing user package.
+ * @throws RemoteException
*/
public void actionTakenOnResourceOveruse(List<PackageResourceOveruseAction> actions)
throws RemoteException {
diff --git a/cpp/watchdog/server/Android.bp b/cpp/watchdog/server/Android.bp
index 2afee42..a0d85b8 100644
--- a/cpp/watchdog/server/Android.bp
+++ b/cpp/watchdog/server/Android.bp
@@ -40,8 +40,8 @@
"liblog",
"libutils",
],
- static_libs: [
- "libgtest_prod",
+ header_libs: [
+ "libgtest_prod_headers",
],
}
diff --git a/cpp/watchdog/server/src/IoOveruseConfigs.cpp b/cpp/watchdog/server/src/IoOveruseConfigs.cpp
index e595e89..90a73df 100644
--- a/cpp/watchdog/server/src/IoOveruseConfigs.cpp
+++ b/cpp/watchdog/server/src/IoOveruseConfigs.cpp
@@ -21,7 +21,6 @@
#include "PackageInfoResolver.h"
#include <android-base/strings.h>
-#include <utils/String8.h>
#include <inttypes.h>
@@ -31,8 +30,6 @@
namespace automotive {
namespace watchdog {
-using ::android::String16;
-using ::android::String8;
using ::android::automotive::watchdog::PerStateBytes;
using ::android::automotive::watchdog::internal::ApplicationCategoryType;
using ::android::automotive::watchdog::internal::ComponentType;
@@ -73,11 +70,11 @@
PER_CATEGORY_THRESHOLDS;
const int32_t kThirdPartyComponentUpdatableConfigs = COMPONENT_SPECIFIC_GENERIC_THRESHOLDS;
-const std::vector<String16> toString16Vector(const std::unordered_set<std::string>& values) {
- std::vector<String16> output;
+const std::vector<std::string> toStringVector(const std::unordered_set<std::string>& values) {
+ std::vector<std::string> output;
for (const auto& v : values) {
if (!v.empty()) {
- output.emplace_back(String16(String8(v.c_str())));
+ output.emplace_back(v);
}
}
return output;
@@ -92,14 +89,13 @@
std::string toString(const PerStateIoOveruseThreshold& thresholds) {
return StringPrintf("name=%s, foregroundBytes=%" PRId64 ", backgroundBytes=%" PRId64
", garageModeBytes=%" PRId64,
- String8(thresholds.name).c_str(),
- thresholds.perStateWriteBytes.foregroundBytes,
+ thresholds.name.c_str(), thresholds.perStateWriteBytes.foregroundBytes,
thresholds.perStateWriteBytes.backgroundBytes,
thresholds.perStateWriteBytes.garageModeBytes);
}
Result<void> containsValidThresholds(const PerStateIoOveruseThreshold& thresholds) {
- if (thresholds.name.size() == 0) {
+ if (thresholds.name.empty()) {
return Error() << "Doesn't contain threshold name";
}
@@ -145,7 +141,7 @@
return Error() << "Invalid " << toString(componentType)
<< " component level generic thresholds: " << result.error();
}
- if (String8(ioOveruseConfig.componentLevelThresholds.name).string() != componentTypeStr) {
+ if (ioOveruseConfig.componentLevelThresholds.name != componentTypeStr) {
return Error() << "Invalid component name "
<< ioOveruseConfig.componentLevelThresholds.name
<< " in component level generic thresholds for component "
@@ -231,38 +227,36 @@
}
std::string errorMsgs;
for (const auto& packageThreshold : thresholds) {
- std::string packageName = std::string(String8(packageThreshold.name));
- if (packageName.empty()) {
+ if (packageThreshold.name.empty()) {
StringAppendF(&errorMsgs, "\tSkipping per-package threshold without package name\n");
continue;
}
- maybeAppendVendorPackagePrefixes(packageName);
+ maybeAppendVendorPackagePrefixes(packageThreshold.name);
if (auto result = containsValidThresholds(packageThreshold); !result.ok()) {
StringAppendF(&errorMsgs,
"\tSkipping invalid package specific thresholds for package %s: %s\n",
- packageName.c_str(), result.error().message().c_str());
+ packageThreshold.name.c_str(), result.error().message().c_str());
continue;
}
- if (const auto& it = mPerPackageThresholds.find(packageName);
+ if (const auto& it = mPerPackageThresholds.find(packageThreshold.name);
it != mPerPackageThresholds.end()) {
StringAppendF(&errorMsgs, "\tDuplicate threshold received for package '%s'\n",
- packageName.c_str());
+ packageThreshold.name.c_str());
}
- mPerPackageThresholds[packageName] = packageThreshold;
+ mPerPackageThresholds[packageThreshold.name] = packageThreshold;
}
return errorMsgs.empty() ? Result<void>{} : Error() << errorMsgs;
}
Result<void> ComponentSpecificConfig::updateSafeToKillPackages(
- const std::vector<String16>& packages,
+ const std::vector<std::string>& packages,
const std::function<void(const std::string&)>& maybeAppendVendorPackagePrefixes) {
mSafeToKillPackages.clear();
if (packages.empty()) {
return Error() << "\tNo safe-to-kill packages provided so clearing it\n";
}
std::string errorMsgs;
- for (const auto& packageNameStr16 : packages) {
- std::string packageName = std::string(String8(packageNameStr16));
+ for (const auto& packageName : packages) {
if (packageName.empty()) {
StringAppendF(&errorMsgs, "\tSkipping empty safe-to-kill package name");
continue;
@@ -296,15 +290,15 @@
result.error().message().c_str());
continue;
}
- std::string name = std::string(String8(categoryThreshold.name));
- if (auto category = toApplicationCategoryType(name);
+ if (auto category = toApplicationCategoryType(categoryThreshold.name);
category == ApplicationCategoryType::OTHERS) {
- StringAppendF(&errorMsgs, "\tInvalid application category %s\n", name.c_str());
+ StringAppendF(&errorMsgs, "\tInvalid application category %s\n",
+ categoryThreshold.name.c_str());
} else {
if (const auto& it = mPerCategoryThresholds.find(category);
it != mPerCategoryThresholds.end()) {
StringAppendF(&errorMsgs, "\tDuplicate threshold received for category: '%s'\n",
- name.c_str());
+ categoryThreshold.name.c_str());
}
mPerCategoryThresholds[category] = categoryThreshold;
}
@@ -393,8 +387,8 @@
std::string nonUpdatableConfigMsgs;
if (updatableConfigsFilter & OveruseConfigEnum::VENDOR_PACKAGE_PREFIXES) {
mVendorPackagePrefixes.clear();
- for (const auto& prefixStr16 : resourceOveruseConfiguration.vendorPackagePrefixes) {
- if (auto prefix = std::string(String8(prefixStr16)); !prefix.empty()) {
+ for (const auto& prefix : resourceOveruseConfiguration.vendorPackagePrefixes) {
+ if (!prefix.empty()) {
mVendorPackagePrefixes.insert(prefix);
}
}
@@ -509,7 +503,7 @@
std::optional<ResourceOveruseConfiguration> IoOveruseConfigs::get(
const ComponentSpecificConfig& componentSpecificConfig, const int32_t componentFilter) {
- if (componentSpecificConfig.mGeneric.name == String16(kDefaultThresholdName)) {
+ if (componentSpecificConfig.mGeneric.name == kDefaultThresholdName) {
return {};
}
ResourceOveruseConfiguration resourceOveruseConfiguration;
@@ -518,8 +512,7 @@
ioOveruseConfiguration.componentLevelThresholds = componentSpecificConfig.mGeneric;
}
if (componentFilter & OveruseConfigEnum::VENDOR_PACKAGE_PREFIXES) {
- resourceOveruseConfiguration.vendorPackagePrefixes =
- toString16Vector(mVendorPackagePrefixes);
+ resourceOveruseConfiguration.vendorPackagePrefixes = toStringVector(mVendorPackagePrefixes);
}
if (componentFilter & OveruseConfigEnum::PACKAGE_APP_CATEGORY_MAPPINGS) {
for (const auto& [packageName, appCategoryType] : mPackagesToAppCategories) {
@@ -536,7 +529,7 @@
}
if (componentFilter & OveruseConfigEnum::COMPONENT_SPECIFIC_SAFE_TO_KILL_PACKAGES) {
resourceOveruseConfiguration.safeToKillPackages =
- toString16Vector(componentSpecificConfig.mSafeToKillPackages);
+ toStringVector(componentSpecificConfig.mSafeToKillPackages);
}
if (componentFilter & OveruseConfigEnum::PER_CATEGORY_THRESHOLDS) {
for (const auto& [category, threshold] : mPerCategoryThresholds) {
@@ -557,10 +550,10 @@
}
PerStateBytes IoOveruseConfigs::fetchThreshold(const PackageInfo& packageInfo) const {
- const std::string packageName = std::string(String8(packageInfo.packageIdentifier.name));
switch (packageInfo.componentType) {
case ComponentType::SYSTEM:
- if (const auto it = mSystemConfig.mPerPackageThresholds.find(packageName);
+ if (const auto it = mSystemConfig.mPerPackageThresholds.find(
+ packageInfo.packageIdentifier.name);
it != mSystemConfig.mPerPackageThresholds.end()) {
return it->second.perStateWriteBytes;
}
@@ -570,7 +563,8 @@
}
return mSystemConfig.mGeneric.perStateWriteBytes;
case ComponentType::VENDOR:
- if (const auto it = mVendorConfig.mPerPackageThresholds.find(packageName);
+ if (const auto it = mVendorConfig.mPerPackageThresholds.find(
+ packageInfo.packageIdentifier.name);
it != mVendorConfig.mPerPackageThresholds.end()) {
return it->second.perStateWriteBytes;
}
@@ -597,13 +591,12 @@
// Native packages can't be disabled so don't kill them on I/O overuse.
return false;
}
- const std::string packageName = std::string(String8(packageInfo.packageIdentifier.name));
switch (packageInfo.componentType) {
case ComponentType::SYSTEM:
- return mSystemConfig.mSafeToKillPackages.find(packageName) !=
+ return mSystemConfig.mSafeToKillPackages.find(packageInfo.packageIdentifier.name) !=
mSystemConfig.mSafeToKillPackages.end();
case ComponentType::VENDOR:
- return mVendorConfig.mSafeToKillPackages.find(packageName) !=
+ return mVendorConfig.mSafeToKillPackages.find(packageInfo.packageIdentifier.name) !=
mVendorConfig.mSafeToKillPackages.end();
default:
return true;
diff --git a/cpp/watchdog/server/src/IoOveruseConfigs.h b/cpp/watchdog/server/src/IoOveruseConfigs.h
index 40a0b7b..e0a2435 100644
--- a/cpp/watchdog/server/src/IoOveruseConfigs.h
+++ b/cpp/watchdog/server/src/IoOveruseConfigs.h
@@ -26,7 +26,6 @@
#include <android/automotive/watchdog/internal/PackageInfo.h>
#include <android/automotive/watchdog/internal/PerStateIoOveruseThreshold.h>
#include <android/automotive/watchdog/internal/ResourceOveruseConfiguration.h>
-#include <utils/String16.h>
#include <optional>
#include <string>
@@ -42,10 +41,10 @@
inline const android::automotive::watchdog::internal::PerStateIoOveruseThreshold
defaultThreshold() {
android::automotive::watchdog::internal::PerStateIoOveruseThreshold threshold;
- threshold.name = android::String16(kDefaultThresholdName);
- threshold.perStateWriteBytes.foregroundBytes = std::numeric_limits<uint64_t>::max();
- threshold.perStateWriteBytes.backgroundBytes = std::numeric_limits<uint64_t>::max();
- threshold.perStateWriteBytes.garageModeBytes = std::numeric_limits<uint64_t>::max();
+ threshold.name = kDefaultThresholdName;
+ threshold.perStateWriteBytes.foregroundBytes = std::numeric_limits<int64_t>::max();
+ threshold.perStateWriteBytes.backgroundBytes = std::numeric_limits<int64_t>::max();
+ threshold.perStateWriteBytes.garageModeBytes = std::numeric_limits<int64_t>::max();
return threshold;
}
@@ -130,7 +129,7 @@
* Updates |mSafeToKillPackages|.
*/
android::base::Result<void> updateSafeToKillPackages(
- const std::vector<android::String16>& packages,
+ const std::vector<std::string>& packages,
const std::function<void(const std::string&)>& maybeAppendVendorPackagePrefixes);
/*
diff --git a/cpp/watchdog/server/src/IoOveruseMonitor.cpp b/cpp/watchdog/server/src/IoOveruseMonitor.cpp
index fd9f61f..c724d57 100644
--- a/cpp/watchdog/server/src/IoOveruseMonitor.cpp
+++ b/cpp/watchdog/server/src/IoOveruseMonitor.cpp
@@ -15,6 +15,7 @@
*/
#define LOG_TAG "carwatchdogd"
+#define DEBUG false // STOPSHIP if true.
#include "IoOveruseMonitor.h"
@@ -27,6 +28,8 @@
#include <binder/Status.h>
#include <cutils/multiuser.h>
+#include <limits>
+
namespace android {
namespace automotive {
namespace watchdog {
@@ -44,17 +47,21 @@
using ::android::base::Result;
using ::android::binder::Status;
+// Minimum written bytes to sync the stats with the Watchdog service.
+constexpr int64_t kMinSyncWrittenBytes = 100 * 1024;
+// Minimum percentage of threshold to warn killable applications.
constexpr double kDefaultIoOveruseWarnPercentage = 80;
+// Maximum numer of system-wide stats (from periodic monitoring) to cache.
constexpr size_t kMaxPeriodicMonitorBufferSize = 1000;
namespace {
std::string uniquePackageIdStr(const PackageIdentifier& id) {
- return StringPrintf("%s:%" PRId32, String8(id.name).c_str(), multiuser_get_user_id(id.uid));
+ return StringPrintf("%s:%" PRId32, id.name.c_str(), multiuser_get_user_id(id.uid));
}
PerStateBytes diff(const PerStateBytes& lhs, const PerStateBytes& rhs) {
- const auto sub = [](const uint64_t& l, const uint64_t& r) -> uint64_t {
+ const auto sub = [](const int64_t& l, const int64_t& r) -> int64_t {
return l >= r ? (l - r) : 0;
};
PerStateBytes result;
@@ -76,6 +83,15 @@
return std::make_tuple(startTime, currentEpochSeconds - startTime);
}
+int64_t totalPerStateBytes(PerStateBytes perStateBytes) {
+ const auto sum = [](const int64_t& l, const int64_t& r) -> int64_t {
+ return std::numeric_limits<int64_t>::max() - l > r ? (l + r)
+ : std::numeric_limits<int64_t>::max();
+ };
+ return sum(perStateBytes.foregroundBytes,
+ sum(perStateBytes.backgroundBytes, perStateBytes.garageModeBytes));
+}
+
} // namespace
std::tuple<int64_t, int64_t> calculateStartAndDuration(const time_t& currentTime) {
@@ -84,6 +100,19 @@
return calculateStartAndDuration(currentGmt);
}
+IoOveruseMonitor::IoOveruseMonitor(
+ const android::sp<IWatchdogServiceHelperInterface>& watchdogServiceHelper) :
+ mMinSyncWrittenBytes(kMinSyncWrittenBytes),
+ mWatchdogServiceHelper(watchdogServiceHelper),
+ mSystemWideWrittenBytes({}),
+ mPeriodicMonitorBufferSize(0),
+ mLastSystemWideIoMonitorTime(0),
+ mUserPackageDailyIoUsageById({}),
+ mIoOveruseWarnPercentage(0),
+ mLastUserPackageIoMonitorTime(0),
+ mOveruseListenersByUid({}),
+ mBinderDeathRecipient(new BinderDeathRecipient(this)) {}
+
Result<void> IoOveruseMonitor::init() {
std::unique_lock writeLock(mRwMutex);
if (isInitializedLocked()) {
@@ -100,7 +129,7 @@
mIoOveruseWarnPercentage = static_cast<double>(
sysprop::ioOveruseWarnPercentage().value_or(kDefaultIoOveruseWarnPercentage));
/*
- * TODO(b/167240592): Read the latest I/O overuse config.
+ * TODO(b/185287136): Read the latest I/O overuse config.
* The latest I/O overuse config is read in this order:
* 1. From /data partition as this contains the latest config and any updates received from OEM
* and system applications.
@@ -109,10 +138,13 @@
*/
mIoOveruseConfigs = new IoOveruseConfigs();
- // TODO(b/167240592): Read the vendor package prefixes from disk before the below call.
+ // TODO(b/185287136): Read the vendor package prefixes from disk before the below call.
mPackageInfoResolver = PackageInfoResolver::getInstance();
mPackageInfoResolver->setPackageConfigurations(mIoOveruseConfigs->vendorPackagePrefixes(),
mIoOveruseConfigs->packagesToAppCategories());
+ if (DEBUG) {
+ ALOGD("Initialized %s data processor", name().c_str());
+ }
return {};
}
@@ -129,6 +161,9 @@
}
mBinderDeathRecipient.clear();
mOveruseListenersByUid.clear();
+ if (DEBUG) {
+ ALOGD("Terminated %s data processor", name().c_str());
+ }
return;
}
@@ -148,7 +183,7 @@
/*
* Date changed so reset the daily I/O usage cache.
*
- * TODO(b/170741935): Ping CarWatchdogService on date change so it can re-enable the daily
+ * TODO(b/185287136): Ping CarWatchdogService on date change so it can re-enable the daily
* disabled packages. Also sync prev day's stats with CarWatchdogService.
*/
mUserPackageDailyIoUsageById.clear();
@@ -156,14 +191,26 @@
mLastUserPackageIoMonitorTime = time;
const auto [startTime, durationInSeconds] = calculateStartAndDuration(curGmt);
- const auto perUidIoUsage = uidIoStats.promote()->deltaStats();
+ auto perUidIoUsage = uidIoStats.promote()->deltaStats();
/*
- * TODO(b/167240592): Maybe move the packageInfo fetching logic into UidIoStats module.
+ * TODO(b/185849350): Maybe move the packageInfo fetching logic into UidIoStats module.
* This will also help avoid fetching package names in IoPerfCollection module.
*/
std::vector<uid_t> seenUids;
- for (const auto& [uid, uidIoStats] : perUidIoUsage) {
- seenUids.push_back(uid);
+ for (auto it = perUidIoUsage.begin(); it != perUidIoUsage.end();) {
+ /*
+ * UidIoStats::deltaStats returns entries with zero write bytes because other metrics
+ * in these entries are non-zero.
+ */
+ if (it->second.ios.sumWriteBytes() == 0) {
+ it = perUidIoUsage.erase(it);
+ continue;
+ }
+ seenUids.push_back(it->first);
+ ++it;
+ }
+ if (perUidIoUsage.empty()) {
+ return {};
}
const auto packageInfosByUid = mPackageInfoResolver->getPackageInfosForUids(seenUids);
std::unordered_map<uid_t, IoOveruseStats> overusingNativeStats;
@@ -173,7 +220,7 @@
continue;
}
/*
- * TODO(b/167240592): Derive the garage mode status from the collection flag, which will
+ * TODO(b/185498771): Derive the garage mode status from the collection flag, which will
* be added to the |onPeriodicCollection| API.
*/
UserPackageIoUsage curUsage(packageInfo->second, uidIoStats.ios,
@@ -204,6 +251,16 @@
mIoOveruseConfigs->isSafeToKill(dailyIoUsage->packageInfo);
const auto& remainingWriteBytes = stats.ioOveruseStats.remainingWriteBytes;
+ const auto exceedsWarnThreshold = [&](double remaining, double threshold) {
+ if (threshold == 0) {
+ return true;
+ }
+ double usedPercent = (100 - (remaining / threshold) * 100);
+ return usedPercent > mIoOveruseWarnPercentage;
+ };
+ bool shouldSyncWatchdogService =
+ (totalPerStateBytes(dailyIoUsage->writtenBytes) -
+ dailyIoUsage->lastSyncedWrittenBytes) >= mMinSyncWrittenBytes;
if (remainingWriteBytes.foregroundBytes == 0 || remainingWriteBytes.backgroundBytes == 0 ||
remainingWriteBytes.garageModeBytes == 0) {
stats.ioOveruseStats.totalOveruses = ++dailyIoUsage->totalOveruses;
@@ -221,45 +278,44 @@
if (dailyIoUsage->packageInfo.uidType == UidType::NATIVE) {
overusingNativeStats[uid] = stats.ioOveruseStats;
}
- mLatestIoOveruseStats.emplace_back(std::move(stats));
- continue;
- }
- if (dailyIoUsage->packageInfo.uidType == UidType::NATIVE ||
- !stats.ioOveruseStats.killableOnOveruse || dailyIoUsage->isPackageWarned) {
+ shouldSyncWatchdogService = true;
+ } else if (dailyIoUsage->packageInfo.uidType != UidType::NATIVE &&
+ stats.ioOveruseStats.killableOnOveruse && !dailyIoUsage->isPackageWarned &&
+ (exceedsWarnThreshold(remainingWriteBytes.foregroundBytes,
+ threshold.foregroundBytes) ||
+ exceedsWarnThreshold(remainingWriteBytes.backgroundBytes,
+ threshold.backgroundBytes) ||
+ exceedsWarnThreshold(remainingWriteBytes.garageModeBytes,
+ threshold.garageModeBytes))) {
/*
* No need to warn native services or applications that won't be killed on I/O overuse
* as they will be sent a notification when they exceed their daily threshold.
*/
- mLatestIoOveruseStats.emplace_back(std::move(stats));
- continue;
- }
- const auto exceedsWarnThreshold = [&](double remaining, double threshold) {
- if (threshold == 0) {
- return true;
- }
- double usedPercent = (100 - (remaining / threshold) * 100);
- return usedPercent > mIoOveruseWarnPercentage;
- };
- if (exceedsWarnThreshold(remainingWriteBytes.foregroundBytes, threshold.foregroundBytes) ||
- exceedsWarnThreshold(remainingWriteBytes.backgroundBytes, threshold.backgroundBytes) ||
- exceedsWarnThreshold(remainingWriteBytes.garageModeBytes, threshold.garageModeBytes)) {
stats.shouldNotify = true;
// Avoid duplicate warning before the daily threshold exceeded notification is sent.
dailyIoUsage->isPackageWarned = true;
+ shouldSyncWatchdogService = true;
}
- mLatestIoOveruseStats.emplace_back(std::move(stats));
+ if (shouldSyncWatchdogService) {
+ dailyIoUsage->lastSyncedWrittenBytes = totalPerStateBytes(dailyIoUsage->writtenBytes);
+ mLatestIoOveruseStats.emplace_back(std::move(stats));
+ }
}
-
if (!overusingNativeStats.empty()) {
notifyNativePackagesLocked(overusingNativeStats);
}
-
+ if (mLatestIoOveruseStats.empty()) {
+ return {};
+ }
if (const auto status = mWatchdogServiceHelper->latestIoOveruseStats(mLatestIoOveruseStats);
!status.isOk()) {
// Don't clear the cache as it can be pushed again on the next collection.
ALOGW("Failed to push the latest I/O overuse stats to watchdog service");
} else {
mLatestIoOveruseStats.clear();
+ if (DEBUG) {
+ ALOGD("Pushed latest I/O overuse stats to watchdog service");
+ }
}
return {};
@@ -295,7 +351,7 @@
{.pollDurationInSecs = difftime(time, mLastSystemWideIoMonitorTime),
.bytesInKib = diskStats.numKibWritten});
for (const auto& threshold : mIoOveruseConfigs->systemWideAlertThresholds()) {
- uint64_t accountedWrittenKib = 0;
+ int64_t accountedWrittenKib = 0;
double accountedDurationInSecs = 0;
size_t accountedPolls = 0;
for (auto rit = mSystemWideWrittenBytes.rbegin(); rit != mSystemWideWrittenBytes.rend();
@@ -328,12 +384,12 @@
}
Result<void> IoOveruseMonitor::onShutdownPrepareComplete() {
- // TODO(b/167240592): Flush in-memory stats to disk.
+ // TODO(b/185287136): Flush in-memory stats to disk.
return {};
}
Result<void> IoOveruseMonitor::onDump([[maybe_unused]] int fd) {
- // TODO(b/167240592): Dump the list of killed/disabled packages. Dump the list of packages that
+ // TODO(b/183436216): Dump the list of killed/disabled packages. Dump the list of packages that
// exceed xx% of their threshold.
return {};
}
@@ -351,7 +407,10 @@
stats.set<ResourceOveruseStats::ioOveruseStats>(ioOveruseStats);
listener->onOveruse(stats);
}
- // TODO(b/167240592): Upload I/O overuse metrics for native packages.
+ if (DEBUG) {
+ ALOGD("Notified native packages on I/O overuse");
+ }
+ // TODO(b/184310189): Upload I/O overuse metrics for native packages.
}
Result<void> IoOveruseMonitor::updateResourceOveruseConfigurations(
@@ -375,7 +434,10 @@
Result<void> IoOveruseMonitor::actionTakenOnIoOveruse(
[[maybe_unused]] const std::vector<PackageResourceOveruseAction>& actions) {
- // TODO(b/167240592): Upload metrics.
+ // TODO(b/184310189): Upload metrics.
+ if (DEBUG) {
+ ALOGD("Recorded action taken on I/O overuse");
+ }
return {};
}
@@ -395,6 +457,9 @@
<< "(pid " << callingPid << ", uid: " << callingUid << ") is dead";
}
mOveruseListenersByUid[callingUid] = listener;
+ if (DEBUG) {
+ ALOGD("Added I/O overuse listener for uid: %d", callingUid);
+ }
return {};
}
@@ -410,11 +475,14 @@
!findListenerAndProcessLocked(binder, processor)) {
return Error(Status::EX_ILLEGAL_ARGUMENT) << "Listener is not previously registered";
}
+ if (DEBUG) {
+ ALOGD("Removed I/O overuse listener for uid: %d", IPCThreadState::self()->getCallingUid());
+ }
return {};
}
Result<void> IoOveruseMonitor::getIoOveruseStats(IoOveruseStats* ioOveruseStats) {
- if (std::shared_lock readLock(mRwMutex); !isInitializedLocked()) {
+ if (!isInitialized()) {
return Error(Status::EX_ILLEGAL_STATE) << "I/O overuse monitor is not initialized";
}
uid_t callingUid = IPCThreadState::self()->getCallingUid();
@@ -447,6 +515,9 @@
calculateStartAndDuration(mLastUserPackageIoMonitorTime);
ioOveruseStats->startTime = startTime;
ioOveruseStats->durationInSeconds = durationInSeconds;
+ if (DEBUG) {
+ ALOGD("Returning I/O overuse listener for uid: %d", callingUid);
+ }
return {};
}
@@ -493,10 +564,9 @@
if (id() == r.id()) {
packageInfo = r.packageInfo;
}
- const auto sum = [](const uint64_t& l, const uint64_t& r) -> uint64_t {
- return (std::numeric_limits<uint64_t>::max() - l) > r
- ? (l + r)
- : std::numeric_limits<uint64_t>::max();
+ const auto sum = [](const int64_t& l, const int64_t& r) -> int64_t {
+ return (std::numeric_limits<int64_t>::max() - l) > r ? (l + r)
+ : std::numeric_limits<int64_t>::max();
};
writtenBytes.foregroundBytes =
sum(writtenBytes.foregroundBytes, r.writtenBytes.foregroundBytes);
diff --git a/cpp/watchdog/server/src/IoOveruseMonitor.h b/cpp/watchdog/server/src/IoOveruseMonitor.h
index 966e7ec..7040353 100644
--- a/cpp/watchdog/server/src/IoOveruseMonitor.h
+++ b/cpp/watchdog/server/src/IoOveruseMonitor.h
@@ -66,6 +66,9 @@
*/
class IIoOveruseMonitor : virtual public IDataProcessorInterface {
public:
+ // Returns whether or not the monitor is initialized.
+ virtual bool isInitialized() = 0;
+
// Below API is from internal/ICarWatchdog.aidl. Please refer to the AIDL for description.
virtual android::base::Result<void> updateResourceOveruseConfigurations(
const std::vector<
@@ -92,19 +95,15 @@
class IoOveruseMonitor final : public IIoOveruseMonitor {
public:
explicit IoOveruseMonitor(
- const android::sp<IWatchdogServiceHelperInterface>& watchdogServiceHelper) :
- mWatchdogServiceHelper(watchdogServiceHelper),
- mSystemWideWrittenBytes({}),
- mPeriodicMonitorBufferSize(0),
- mLastSystemWideIoMonitorTime(0),
- mUserPackageDailyIoUsageById({}),
- mIoOveruseWarnPercentage(0),
- mLastUserPackageIoMonitorTime(0),
- mOveruseListenersByUid({}),
- mBinderDeathRecipient(new BinderDeathRecipient(this)) {}
+ const android::sp<IWatchdogServiceHelperInterface>& watchdogServiceHelper);
~IoOveruseMonitor() { terminate(); }
+ bool isInitialized() {
+ std::shared_lock readLock(mRwMutex);
+ return isInitializedLocked();
+ }
+
// Below methods implement IDataProcessorInterface.
std::string name() { return "IoOveruseMonitor"; }
friend std::ostream& operator<<(std::ostream& os, const IoOveruseMonitor& monitor);
@@ -116,7 +115,7 @@
return {};
}
- // TODO(b/167240592): Forward WatchdogBinderMediator's notifySystemStateChange call to
+ // TODO(b/185498771): Forward WatchdogBinderMediator's notifySystemStateChange call to
// WatchdogPerfService. On POWER_CYCLE_SHUTDOWN_PREPARE, switch to garage mode collection
// and pass collection flag as a param in this API to indicate garage mode collection.
android::base::Result<void> onPeriodicCollection(time_t time,
@@ -133,7 +132,7 @@
time_t time, const android::wp<IProcDiskStatsInterface>& procDiskStats,
const std::function<void()>& alertHandler);
- // TODO(b/167240592): Forward WatchdogBinderMediator's notifySystemStateChange call to
+ // TODO(b/185498771): Forward WatchdogBinderMediator's notifySystemStateChange call to
// WatchdogProcessService. On POWER_CYCLE_SHUTDOWN_PREPARE_COMPLETE, call this method via
// the IDataProcessorInterface. onShutdownPrepareComplete, IoOveruseMonitor will flush
// in-memory stats to disk.
@@ -187,6 +186,7 @@
PerStateBytes forgivenWriteBytes = {};
int totalOveruses = 0;
bool isPackageWarned = false;
+ uint64_t lastSyncedWrittenBytes = 0;
UserPackageIoUsage& operator+=(const UserPackageIoUsage& r);
@@ -219,12 +219,13 @@
// Local IPackageInfoResolver instance. Useful to mock in tests.
sp<IPackageInfoResolver> mPackageInfoResolver;
+ // Minimum written bytes to sync the stats with the Watchdog service.
+ double mMinSyncWrittenBytes;
+ android::sp<IWatchdogServiceHelperInterface> mWatchdogServiceHelper;
// Makes sure only one collection is running at any given time.
mutable std::shared_mutex mRwMutex;
- android::sp<IWatchdogServiceHelperInterface> mWatchdogServiceHelper GUARDED_BY(mRwMutex);
-
// Summary of configs available for all the components and system-wide overuse alert thresholds.
sp<IIoOveruseConfigs> mIoOveruseConfigs GUARDED_BY(mRwMutex);
diff --git a/cpp/watchdog/server/src/IoPerfCollection.cpp b/cpp/watchdog/server/src/IoPerfCollection.cpp
index 059059a..08c1daa 100644
--- a/cpp/watchdog/server/src/IoPerfCollection.cpp
+++ b/cpp/watchdog/server/src/IoPerfCollection.cpp
@@ -161,7 +161,7 @@
for (const auto& stat : data.topNReads) {
StringAppendF(&buffer, "%" PRIu32 ", %s", stat.userId, stat.packageName.c_str());
for (int i = 0; i < UID_STATES; ++i) {
- StringAppendF(&buffer, ", %" PRIu64 ", %.2f%%, %" PRIu64 ", %.2f%%", stat.bytes[i],
+ StringAppendF(&buffer, ", %" PRIi64 ", %.2f%%, %" PRIi64 ", %.2f%%", stat.bytes[i],
percentage(stat.bytes[i], data.total[READ_BYTES][i]), stat.fsync[i],
percentage(stat.fsync[i], data.total[FSYNC_COUNT][i]));
}
@@ -177,7 +177,7 @@
for (const auto& stat : data.topNWrites) {
StringAppendF(&buffer, "%" PRIu32 ", %s", stat.userId, stat.packageName.c_str());
for (int i = 0; i < UID_STATES; ++i) {
- StringAppendF(&buffer, ", %" PRIu64 ", %.2f%%, %" PRIu64 ", %.2f%%", stat.bytes[i],
+ StringAppendF(&buffer, ", %" PRIi64 ", %.2f%%, %" PRIi64 ", %.2f%%", stat.bytes[i],
percentage(stat.bytes[i], data.total[WRITE_BYTES][i]), stat.fsync[i],
percentage(stat.fsync[i], data.total[FSYNC_COUNT][i]));
}
@@ -421,9 +421,6 @@
for (const auto& uIt : usages) {
const UidIoUsage& curUsage = uIt.second;
- if (curUsage.ios.isZero()) {
- continue;
- }
uids.push_back(curUsage.uid);
uidIoPerfData->total[READ_BYTES][FOREGROUND] +=
curUsage.ios.metrics[READ_BYTES][FOREGROUND];
diff --git a/cpp/watchdog/server/src/IoPerfCollection.h b/cpp/watchdog/server/src/IoPerfCollection.h
index e86320f..1c40d00 100644
--- a/cpp/watchdog/server/src/IoPerfCollection.h
+++ b/cpp/watchdog/server/src/IoPerfCollection.h
@@ -49,12 +49,12 @@
struct Stats {
userid_t userId = 0;
std::string packageName;
- uint64_t bytes[UID_STATES];
- uint64_t fsync[UID_STATES];
+ int64_t bytes[UID_STATES];
+ int64_t fsync[UID_STATES];
};
std::vector<Stats> topNReads = {};
std::vector<Stats> topNWrites = {};
- uint64_t total[METRIC_TYPES][UID_STATES] = {{0}};
+ int64_t total[METRIC_TYPES][UID_STATES] = {{0}};
};
std::string toString(const UidIoPerfData& perfData);
diff --git a/cpp/watchdog/server/src/PackageInfoResolver.cpp b/cpp/watchdog/server/src/PackageInfoResolver.cpp
index f6b1dfd..1a4346a 100644
--- a/cpp/watchdog/server/src/PackageInfoResolver.cpp
+++ b/cpp/watchdog/server/src/PackageInfoResolver.cpp
@@ -23,7 +23,6 @@
#include <android/automotive/watchdog/internal/ComponentType.h>
#include <android/automotive/watchdog/internal/UidType.h>
#include <cutils/android_filesystem_config.h>
-#include <utils/String16.h>
#include <inttypes.h>
@@ -36,7 +35,6 @@
using ::android::IBinder;
using ::android::sp;
-using ::android::String16;
using ::android::automotive::watchdog::internal::ApplicationCategoryType;
using ::android::automotive::watchdog::internal::ComponentType;
using ::android::automotive::watchdog::internal::PackageInfo;
@@ -53,7 +51,7 @@
namespace {
-constexpr const char16_t* kSharedPackagePrefix = u"shared:";
+constexpr const char* kSharedPackagePrefix = "shared:";
ComponentType getComponentTypeForNativeUid(uid_t uid, std::string_view packageName,
const std::vector<std::string>& vendorPackagePrefixes) {
@@ -83,7 +81,7 @@
return Error() << "Failed to fetch package name";
}
const char* packageName = usrpwd->pw_name;
- packageInfo.packageIdentifier.name = String16(packageName);
+ packageInfo.packageIdentifier.name = packageName;
packageInfo.packageIdentifier.uid = uid;
packageInfo.uidType = UidType::NATIVE;
packageInfo.componentType =
@@ -153,7 +151,7 @@
continue;
}
mUidToPackageInfoMapping[uid] = *result;
- if (result->packageIdentifier.name.startsWith(kSharedPackagePrefix)) {
+ if (StartsWith(result->packageIdentifier.name, kSharedPackagePrefix)) {
// When the UID is shared, poll car watchdog service to fetch the shared packages info.
missingUids.emplace_back(static_cast<int32_t>(uid));
}
@@ -178,10 +176,10 @@
}
for (auto& packageInfo : packageInfos) {
const auto& id = packageInfo.packageIdentifier;
- if (id.name.size() == 0) {
+ if (id.name.empty()) {
continue;
}
- if (const auto it = mPackagesToAppCategories.find(String8(id.name).c_str());
+ if (const auto it = mPackagesToAppCategories.find(id.name);
packageInfo.uidType == UidType::APPLICATION && it != mPackagesToAppCategories.end()) {
packageInfo.appCategoryType = it->second;
}
@@ -200,8 +198,8 @@
std::shared_lock readLock(mRWMutex);
for (const auto& uid : uids) {
if (mUidToPackageInfoMapping.find(uid) != mUidToPackageInfoMapping.end()) {
- uidToPackageNameMapping[uid] = std::string(
- String8(mUidToPackageInfoMapping.at(uid).packageIdentifier.name));
+ uidToPackageNameMapping[uid] =
+ mUidToPackageInfoMapping.at(uid).packageIdentifier.name;
}
}
}
diff --git a/cpp/watchdog/server/src/ServiceManager.cpp b/cpp/watchdog/server/src/ServiceManager.cpp
index 978e83c..6bcba40 100644
--- a/cpp/watchdog/server/src/ServiceManager.cpp
+++ b/cpp/watchdog/server/src/ServiceManager.cpp
@@ -26,7 +26,6 @@
namespace watchdog {
using ::android::sp;
-using ::android::String16;
using ::android::automotive::watchdog::WatchdogPerfService;
using ::android::automotive::watchdog::WatchdogProcessService;
using ::android::base::Error;
diff --git a/cpp/watchdog/server/src/UidIoStats.cpp b/cpp/watchdog/server/src/UidIoStats.cpp
index 31cd0d9..e88693a 100644
--- a/cpp/watchdog/server/src/UidIoStats.cpp
+++ b/cpp/watchdog/server/src/UidIoStats.cpp
@@ -36,6 +36,7 @@
namespace watchdog {
using ::android::base::Error;
+using ::android::base::ParseInt;
using ::android::base::ParseUint;
using ::android::base::ReadFileToString;
using ::android::base::Result;
@@ -47,19 +48,19 @@
bool parseUidIoStats(const std::string& data, UidIoUsage* usage) {
std::vector<std::string> fields = Split(data, " ");
if (fields.size() < 11 || !ParseUint(fields[0], &usage->uid) ||
- !ParseUint(fields[3], &usage->ios.metrics[READ_BYTES][FOREGROUND]) ||
- !ParseUint(fields[4], &usage->ios.metrics[WRITE_BYTES][FOREGROUND]) ||
- !ParseUint(fields[7], &usage->ios.metrics[READ_BYTES][BACKGROUND]) ||
- !ParseUint(fields[8], &usage->ios.metrics[WRITE_BYTES][BACKGROUND]) ||
- !ParseUint(fields[9], &usage->ios.metrics[FSYNC_COUNT][FOREGROUND]) ||
- !ParseUint(fields[10], &usage->ios.metrics[FSYNC_COUNT][BACKGROUND])) {
+ !ParseInt(fields[3], &usage->ios.metrics[READ_BYTES][FOREGROUND]) ||
+ !ParseInt(fields[4], &usage->ios.metrics[WRITE_BYTES][FOREGROUND]) ||
+ !ParseInt(fields[7], &usage->ios.metrics[READ_BYTES][BACKGROUND]) ||
+ !ParseInt(fields[8], &usage->ios.metrics[WRITE_BYTES][BACKGROUND]) ||
+ !ParseInt(fields[9], &usage->ios.metrics[FSYNC_COUNT][FOREGROUND]) ||
+ !ParseInt(fields[10], &usage->ios.metrics[FSYNC_COUNT][BACKGROUND])) {
ALOGW("Invalid uid I/O stats: \"%s\"", data.c_str());
return false;
}
return true;
}
-uint64_t maybeDiff(uint64_t lhs, uint64_t rhs) {
+int64_t maybeDiff(int64_t lhs, int64_t rhs) {
return lhs > rhs ? lhs - rhs : 0;
}
@@ -93,8 +94,8 @@
}
std::string IoUsage::toString() const {
- return StringPrintf("FgRdBytes:%" PRIu64 " BgRdBytes:%" PRIu64 " FgWrBytes:%" PRIu64
- " BgWrBytes:%" PRIu64 " FgFsync:%" PRIu64 " BgFsync:%" PRIu64,
+ return StringPrintf("FgRdBytes:%" PRIi64 " BgRdBytes:%" PRIi64 " FgWrBytes:%" PRIi64
+ " BgWrBytes:%" PRIi64 " FgFsync:%" PRIi64 " BgFsync:%" PRIi64,
metrics[READ_BYTES][FOREGROUND], metrics[READ_BYTES][BACKGROUND],
metrics[WRITE_BYTES][FOREGROUND], metrics[WRITE_BYTES][BACKGROUND],
metrics[FSYNC_COUNT][FOREGROUND], metrics[FSYNC_COUNT][BACKGROUND]);
@@ -113,11 +114,16 @@
mDeltaUidIoUsages.clear();
for (const auto& it : *uidIoUsages) {
- const UidIoUsage& curUsage = it.second;
- mDeltaUidIoUsages[it.first] = curUsage;
- if (mLatestUidIoUsages.find(it.first) != mLatestUidIoUsages.end()) {
- mDeltaUidIoUsages[it.first] -= mLatestUidIoUsages[it.first];
+ UidIoUsage curUsage = it.second;
+ if (curUsage.ios.isZero()) {
+ continue;
}
+ if (mLatestUidIoUsages.find(it.first) != mLatestUidIoUsages.end()) {
+ if (curUsage -= mLatestUidIoUsages[it.first]; curUsage.ios.isZero()) {
+ continue;
+ }
+ }
+ mDeltaUidIoUsages[it.first] = curUsage;
}
mLatestUidIoUsages = *uidIoUsages;
return {};
diff --git a/cpp/watchdog/server/src/UidIoStats.h b/cpp/watchdog/server/src/UidIoStats.h
index c6e87d1..b3257f8 100644
--- a/cpp/watchdog/server/src/UidIoStats.h
+++ b/cpp/watchdog/server/src/UidIoStats.h
@@ -18,6 +18,7 @@
#define CPP_WATCHDOG_SERVER_SRC_UIDIOSTATS_H_
#include <android-base/result.h>
+#include <android-base/stringprintf.h>
#include <utils/Mutex.h>
#include <utils/RefBase.h>
@@ -48,8 +49,8 @@
class IoUsage {
public:
IoUsage() : metrics{{0}} {};
- IoUsage(uint64_t fgRdBytes, uint64_t bgRdBytes, uint64_t fgWrBytes, uint64_t bgWrBytes,
- uint64_t fgFsync, uint64_t bgFsync) {
+ IoUsage(int64_t fgRdBytes, int64_t bgRdBytes, int64_t fgWrBytes, int64_t bgWrBytes,
+ int64_t fgFsync, int64_t bgFsync) {
metrics[READ_BYTES][FOREGROUND] = fgRdBytes;
metrics[READ_BYTES][BACKGROUND] = bgRdBytes;
metrics[WRITE_BYTES][FOREGROUND] = fgWrBytes;
@@ -61,23 +62,23 @@
bool operator==(const IoUsage& usage) const {
return memcmp(&metrics, &usage.metrics, sizeof(metrics)) == 0;
}
- uint64_t sumReadBytes() const {
+ int64_t sumReadBytes() const {
const auto& [fgBytes, bgBytes] =
std::tuple(metrics[READ_BYTES][FOREGROUND], metrics[READ_BYTES][BACKGROUND]);
- return (std::numeric_limits<uint64_t>::max() - fgBytes) > bgBytes
+ return (std::numeric_limits<int64_t>::max() - fgBytes) > bgBytes
? (fgBytes + bgBytes)
- : std::numeric_limits<uint64_t>::max();
+ : std::numeric_limits<int64_t>::max();
}
- uint64_t sumWriteBytes() const {
+ int64_t sumWriteBytes() const {
const auto& [fgBytes, bgBytes] =
std::tuple(metrics[WRITE_BYTES][FOREGROUND], metrics[WRITE_BYTES][BACKGROUND]);
- return (std::numeric_limits<uint64_t>::max() - fgBytes) > bgBytes
+ return (std::numeric_limits<int64_t>::max() - fgBytes) > bgBytes
? (fgBytes + bgBytes)
- : std::numeric_limits<uint64_t>::max();
+ : std::numeric_limits<int64_t>::max();
}
bool isZero() const;
std::string toString() const;
- uint64_t metrics[METRIC_TYPES][UID_STATES];
+ int64_t metrics[METRIC_TYPES][UID_STATES];
};
struct UidIoUsage {
@@ -87,6 +88,10 @@
ios -= rhs.ios;
return *this;
}
+ bool operator==(const UidIoUsage& rhs) const { return uid == rhs.uid && ios == rhs.ios; }
+ std::string toString() const {
+ return android::base::StringPrintf("Uid: %d, Usage: {%s}", uid, ios.toString().c_str());
+ }
};
class UidIoStats : public RefBase {
diff --git a/cpp/watchdog/server/src/WatchdogBinderMediator.cpp b/cpp/watchdog/server/src/WatchdogBinderMediator.cpp
index b27d9a9..0a4eb25 100644
--- a/cpp/watchdog/server/src/WatchdogBinderMediator.cpp
+++ b/cpp/watchdog/server/src/WatchdogBinderMediator.cpp
@@ -32,6 +32,7 @@
using ::android::defaultServiceManager;
using ::android::IBinder;
using ::android::sp;
+using ::android::String16;
using ::android::base::Error;
using ::android::base::Join;
using ::android::base::ParseUint;
@@ -170,7 +171,6 @@
ALOGW("Failed to dump I/O perf collection: %s", ret.error().message().c_str());
return ret.error().code();
}
- // TODO(b/167240592): Add a dump call to I/O overuse monitor and relevant tests.
return OK;
}
diff --git a/cpp/watchdog/server/src/WatchdogBinderMediator.h b/cpp/watchdog/server/src/WatchdogBinderMediator.h
index e470290..63b687b 100644
--- a/cpp/watchdog/server/src/WatchdogBinderMediator.h
+++ b/cpp/watchdog/server/src/WatchdogBinderMediator.h
@@ -66,7 +66,7 @@
~WatchdogBinderMediator() { terminate(); }
// Implements ICarWatchdog.aidl APIs.
- status_t dump(int fd, const Vector<String16>& args) override;
+ status_t dump(int fd, const Vector<android::String16>& args) override;
android::binder::Status registerClient(const android::sp<ICarWatchdogClient>& client,
TimeoutLength timeout) override;
android::binder::Status unregisterClient(
diff --git a/cpp/watchdog/server/src/WatchdogInternalHandler.cpp b/cpp/watchdog/server/src/WatchdogInternalHandler.cpp
index 31c40bf..556f9f1 100644
--- a/cpp/watchdog/server/src/WatchdogInternalHandler.cpp
+++ b/cpp/watchdog/server/src/WatchdogInternalHandler.cpp
@@ -37,6 +37,7 @@
using aawi::PackageResourceOveruseAction;
using aawi::ResourceOveruseConfiguration;
using ::android::sp;
+using ::android::String16;
using ::android::binder::Status;
namespace {
@@ -65,6 +66,18 @@
return mBinderMediator->dump(fd, args);
}
+void WatchdogInternalHandler::checkAndRegisterIoOveruseMonitor() {
+ if (mIoOveruseMonitor->isInitialized()) {
+ return;
+ }
+ if (const auto result = mWatchdogPerfService->registerDataProcessor(mIoOveruseMonitor);
+ !result.ok()) {
+ ALOGE("Failed to register I/O overuse monitor to watchdog performance service: %s",
+ result.error().message().c_str());
+ }
+ return;
+}
+
Status WatchdogInternalHandler::registerCarWatchdogService(
const sp<ICarWatchdogServiceForSystem>& service) {
Status status = checkSystemUser();
@@ -74,6 +87,12 @@
if (service == nullptr) {
return fromExceptionCode(Status::EX_ILLEGAL_ARGUMENT, kNullCarWatchdogServiceError);
}
+ /*
+ * I/O overuse monitor reads from system, vendor, and data partitions during initialization.
+ * When CarService is running these partitions are available to read, thus register the I/O
+ * overuse monitor on processing the request to register CarService.
+ */
+ checkAndRegisterIoOveruseMonitor();
return mWatchdogServiceHelper->registerService(service);
}
@@ -167,19 +186,6 @@
if (const auto result = mWatchdogPerfService->onBootFinished(); !result.ok()) {
return fromExceptionCode(result.error().code(), result.error().message());
}
- /*
- * I/O overuse monitor reads from data partition on init so register the I/O
- * overuse monitor only on boot-complete.
- *
- * TODO(b/167240592): Uncomment the below code block after the I/O overuse monitor
- * is completely implemented.
- * if (const auto result
- * = mWatchdogPerfService->registerDataProcessor(mIoOveruseMonitor);
- * !result.ok()) {
- * ALOGW("Failed to register I/O overuse monitor to watchdog performance "
- * "service: %s", result.error().message().c_str());
- * }
- */
}
return Status::ok();
}
@@ -194,6 +200,8 @@
if (!status.isOk()) {
return status;
}
+ // Maybe retry registring I/O overuse monitor if failed to initialize previously.
+ checkAndRegisterIoOveruseMonitor();
if (const auto result = mIoOveruseMonitor->updateResourceOveruseConfigurations(configs);
!result.ok()) {
return fromExceptionCode(result.error().code(), result.error().message());
@@ -207,6 +215,8 @@
if (!status.isOk()) {
return status;
}
+ // Maybe retry registring I/O overuse monitor if failed to initialize previously.
+ checkAndRegisterIoOveruseMonitor();
if (const auto result = mIoOveruseMonitor->getResourceOveruseConfigurations(configs);
!result.ok()) {
return fromExceptionCode(result.error().code(), result.error().message());
diff --git a/cpp/watchdog/server/src/WatchdogInternalHandler.h b/cpp/watchdog/server/src/WatchdogInternalHandler.h
index c155523..098b0dd 100644
--- a/cpp/watchdog/server/src/WatchdogInternalHandler.h
+++ b/cpp/watchdog/server/src/WatchdogInternalHandler.h
@@ -32,6 +32,7 @@
#include <binder/Status.h>
#include <gtest/gtest_prod.h>
#include <utils/Errors.h>
+#include <utils/String16.h>
#include <utils/Vector.h>
namespace android {
@@ -55,7 +56,7 @@
mIoOveruseMonitor(ioOveruseMonitor) {}
~WatchdogInternalHandler() { terminate(); }
- status_t dump(int fd, const Vector<String16>& args) override;
+ status_t dump(int fd, const Vector<android::String16>& args) override;
android::binder::Status registerCarWatchdogService(
const android::sp<
android::automotive::watchdog::internal::ICarWatchdogServiceForSystem>& service)
@@ -103,6 +104,8 @@
}
private:
+ void checkAndRegisterIoOveruseMonitor();
+
android::sp<WatchdogBinderMediator> mBinderMediator;
android::sp<IWatchdogServiceHelperInterface> mWatchdogServiceHelper;
android::sp<WatchdogProcessService> mWatchdogProcessService;
diff --git a/cpp/watchdog/server/src/WatchdogPerfService.cpp b/cpp/watchdog/server/src/WatchdogPerfService.cpp
index 79177ad..214a16c 100644
--- a/cpp/watchdog/server/src/WatchdogPerfService.cpp
+++ b/cpp/watchdog/server/src/WatchdogPerfService.cpp
@@ -15,6 +15,7 @@
*/
#define LOG_TAG "carwatchdogd"
+#define DEBUG false // STOPSHIP if true.
#include "WatchdogPerfService.h"
@@ -36,6 +37,7 @@
namespace watchdog {
using ::android::String16;
+using ::android::String8;
using ::android::base::Error;
using ::android::base::Join;
using ::android::base::ParseUint;
@@ -140,6 +142,9 @@
}
Mutex::Autolock lock(mMutex);
mDataProcessors.push_back(processor);
+ if (DEBUG) {
+ ALOGD("Successfully registered %s to %s", processor->name().c_str(), kServiceName);
+ }
return {};
}
@@ -241,6 +246,9 @@
}
if (mCollectionThread.joinable()) {
mCollectionThread.join();
+ if (DEBUG) {
+ ALOGD("%s collection thread terminated", kServiceName);
+ }
}
}
@@ -260,6 +268,9 @@
mBoottimeCollection.lastUptime = mHandlerLooper->now();
mHandlerLooper->removeMessages(this);
mHandlerLooper->sendMessage(this, SwitchMessage::END_BOOTTIME_COLLECTION);
+ if (DEBUG) {
+ ALOGD("Boot-time event finished");
+ }
return {};
}
@@ -471,6 +482,9 @@
}
}
+ if (DEBUG) {
+ ALOGD("Custom event finished");
+ }
WriteStringToFd(kDumpMajorDelimiter, fd);
return {};
}
@@ -564,6 +578,9 @@
toString(mCurrCollectionEvent));
return {};
}
+ if (DEBUG) {
+ ALOGD("Processing %s collection event", toString(metadata->eventType));
+ }
if (metadata->interval < kMinEventInterval) {
return Error()
<< "Collection interval of "
@@ -636,6 +653,9 @@
if (metadata->eventType != static_cast<int>(EventType::PERIODIC_MONITOR)) {
return Error() << "Invalid monitor event " << toString(metadata->eventType);
}
+ if (DEBUG) {
+ ALOGD("Processing %s monitor event", toString(metadata->eventType));
+ }
if (metadata->interval < kMinEventInterval) {
return Error()
<< "Monitor interval of "
diff --git a/cpp/watchdog/server/src/WatchdogPerfService.h b/cpp/watchdog/server/src/WatchdogPerfService.h
index c44659b..0551d35 100644
--- a/cpp/watchdog/server/src/WatchdogPerfService.h
+++ b/cpp/watchdog/server/src/WatchdogPerfService.h
@@ -171,7 +171,8 @@
// 1. Starts a custom collection.
// 2. Or ends the current custom collection and dumps the collected data.
// Returns any error observed during the dump generation.
- virtual android::base::Result<void> onCustomCollection(int fd, const Vector<String16>& args);
+ virtual android::base::Result<void> onCustomCollection(int fd,
+ const Vector<android::String16>& args);
// Generates a dump from the boot-time and periodic collection events.
virtual android::base::Result<void> onDump(int fd);
diff --git a/cpp/watchdog/server/src/WatchdogProcessService.cpp b/cpp/watchdog/server/src/WatchdogProcessService.cpp
index 7af7ecc..952c83e 100644
--- a/cpp/watchdog/server/src/WatchdogProcessService.cpp
+++ b/cpp/watchdog/server/src/WatchdogProcessService.cpp
@@ -48,6 +48,7 @@
using aawi::ICarWatchdogServiceForSystem;
using ::android::IBinder;
using ::android::sp;
+using ::android::String16;
using ::android::base::Error;
using ::android::base::GetProperty;
using ::android::base::ReadFileToString;
diff --git a/cpp/watchdog/server/src/WatchdogProcessService.h b/cpp/watchdog/server/src/WatchdogProcessService.h
index c4ae2cb..288f99e 100644
--- a/cpp/watchdog/server/src/WatchdogProcessService.h
+++ b/cpp/watchdog/server/src/WatchdogProcessService.h
@@ -50,7 +50,8 @@
android::base::Result<void> start();
void terminate();
- virtual android::base::Result<void> dump(int fd, const android::Vector<String16>& args);
+ virtual android::base::Result<void> dump(int fd,
+ const android::Vector<android::String16>& args);
void doHealthCheck(int what);
virtual android::base::Result<void> registerWatchdogServiceHelper(
diff --git a/cpp/watchdog/server/src/WatchdogServiceHelper.cpp b/cpp/watchdog/server/src/WatchdogServiceHelper.cpp
index f89942e..03f3305 100644
--- a/cpp/watchdog/server/src/WatchdogServiceHelper.cpp
+++ b/cpp/watchdog/server/src/WatchdogServiceHelper.cpp
@@ -34,7 +34,6 @@
using aawi::PackageIoOveruseStats;
using ::android::IBinder;
using ::android::sp;
-using ::android::String16;
using ::android::wp;
using ::android::base::Error;
using ::android::base::Result;
@@ -180,21 +179,17 @@
Status WatchdogServiceHelper::getPackageInfosForUids(
const std::vector<int32_t>& uids, const std::vector<std::string>& vendorPackagePrefixes,
std::vector<PackageInfo>* packageInfos) {
- /*
- * The expected number of vendor package prefixes is in the order of 10s. Thus the overhead of
- * forwarding these in each get call is very low.
- */
- std::vector<String16> prefixes;
- for (const auto& prefix : vendorPackagePrefixes) {
- prefixes.push_back(String16(prefix.c_str()));
- }
sp<ICarWatchdogServiceForSystem> service;
if (std::shared_lock readLock(mRWMutex); mService == nullptr) {
return fromExceptionCode(Status::EX_ILLEGAL_STATE, "Watchdog service is not initialized");
} else {
service = mService;
}
- return service->getPackageInfosForUids(uids, prefixes, packageInfos);
+ /*
+ * The expected number of vendor package prefixes is in the order of 10s. Thus the overhead of
+ * forwarding these in each get call is very low.
+ */
+ return service->getPackageInfosForUids(uids, vendorPackagePrefixes, packageInfos);
}
Status WatchdogServiceHelper::latestIoOveruseStats(
diff --git a/cpp/watchdog/server/tests/IoOveruseConfigsTest.cpp b/cpp/watchdog/server/tests/IoOveruseConfigsTest.cpp
index f803566..433f027 100644
--- a/cpp/watchdog/server/tests/IoOveruseConfigsTest.cpp
+++ b/cpp/watchdog/server/tests/IoOveruseConfigsTest.cpp
@@ -18,7 +18,6 @@
#include <android-base/strings.h>
#include <gmock/gmock.h>
-#include <utils/String8.h>
#include <inttypes.h>
@@ -27,8 +26,6 @@
namespace watchdog {
using ::android::sp;
-using ::android::String16;
-using ::android::String8;
using ::android::automotive::watchdog::internal::ApplicationCategoryType;
using ::android::automotive::watchdog::internal::ComponentType;
using ::android::automotive::watchdog::internal::IoOveruseAlertThreshold;
@@ -86,7 +83,7 @@
PerStateIoOveruseThreshold toPerStateIoOveruseThreshold(const std::string& name,
const PerStateBytes& perStateBytes) {
PerStateIoOveruseThreshold threshold;
- threshold.name = String16(String8(name.c_str()));
+ threshold.name = name;
threshold.perStateWriteBytes = perStateBytes;
return threshold;
}
@@ -101,7 +98,7 @@
const int64_t bgBytes,
const int64_t garageModeBytes) {
PerStateIoOveruseThreshold threshold;
- threshold.name = String16(String8(name.c_str()));
+ threshold.name = name;
threshold.perStateWriteBytes = toPerStateBytes(fgBytes, bgBytes, garageModeBytes);
return threshold;
}
@@ -133,23 +130,13 @@
const char* packageName, const ComponentType componentType,
const ApplicationCategoryType appCategoryType = ApplicationCategoryType::OTHERS) {
PackageInfo packageInfo;
- packageInfo.packageIdentifier.name = String16(packageName);
+ packageInfo.packageIdentifier.name = packageName;
packageInfo.uidType = UidType::APPLICATION;
packageInfo.componentType = componentType;
packageInfo.appCategoryType = appCategoryType;
return packageInfo;
}
-std::vector<String16> toString16Vector(const std::vector<std::string>& values) {
- std::vector<String16> output;
- for (const auto& v : values) {
- if (!v.empty()) {
- output.emplace_back(String16(String8(v.c_str())));
- }
- }
- return output;
-}
-
ResourceOveruseConfiguration constructResourceOveruseConfig(
const ComponentType type, const std::vector<std::string>&& safeToKill,
const std::vector<std::string>&& vendorPrefixes,
@@ -157,8 +144,8 @@
const IoOveruseConfiguration& ioOveruseConfiguration) {
ResourceOveruseConfiguration resourceOveruseConfig;
resourceOveruseConfig.componentType = type;
- resourceOveruseConfig.safeToKillPackages = toString16Vector(safeToKill);
- resourceOveruseConfig.vendorPackagePrefixes = toString16Vector(vendorPrefixes);
+ resourceOveruseConfig.safeToKillPackages = safeToKill;
+ resourceOveruseConfig.vendorPackagePrefixes = vendorPrefixes;
resourceOveruseConfig.packageMetadata = packageMetadata;
ResourceSpecificConfiguration config;
config.set<ResourceSpecificConfiguration::ioOveruseConfiguration>(ioOveruseConfiguration);
@@ -318,7 +305,7 @@
ASSERT_RESULT_OK(ioOveruseConfigs.update(
{systemResourceConfig, vendorResourceConfig, thirdPartyResourceConfig}));
- vendorResourceConfig.vendorPackagePrefixes.push_back(String16(String8("vendorPkgB")));
+ vendorResourceConfig.vendorPackagePrefixes.push_back("vendorPkgB");
std::vector<ResourceOveruseConfiguration> expected = {systemResourceConfig,
vendorResourceConfig,
thirdPartyResourceConfig};
@@ -755,7 +742,7 @@
const auto ioOveruseConfigs = sampleIoOveruseConfigs();
PackageInfo packageInfo;
- packageInfo.packageIdentifier.name = String16("native package");
+ packageInfo.packageIdentifier.name = "native package";
packageInfo.uidType = UidType::NATIVE;
packageInfo.componentType = ComponentType::SYSTEM;
diff --git a/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp b/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp
index 38a0040..89dd263 100644
--- a/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp
+++ b/cpp/watchdog/server/tests/IoOveruseMonitorTest.cpp
@@ -31,12 +31,12 @@
namespace watchdog {
constexpr size_t kTestMonitorBufferSize = 3;
+constexpr int64_t KTestMinSyncWrittenBytes = 5'000;
constexpr double kTestIoOveruseWarnPercentage = 80;
constexpr std::chrono::seconds kTestMonitorInterval = 5s;
using ::android::IPCThreadState;
using ::android::RefBase;
-using ::android::String16;
using ::android::automotive::watchdog::internal::ApplicationCategoryType;
using ::android::automotive::watchdog::internal::ComponentType;
using ::android::automotive::watchdog::internal::IoOveruseAlertThreshold;
@@ -69,7 +69,7 @@
PackageIdentifier constructPackageIdentifier(const char* packageName, const int32_t uid) {
PackageIdentifier packageIdentifier;
- packageIdentifier.name = String16(String8(packageName));
+ packageIdentifier.name = packageName;
packageIdentifier.uid = uid;
return packageIdentifier;
}
@@ -175,6 +175,7 @@
if (const auto result = mIoOveruseMonitor->init(); !result.ok()) {
return result;
}
+ mIoOveruseMonitor->mMinSyncWrittenBytes = KTestMinSyncWrittenBytes;
mIoOveruseMonitor->mPeriodicMonitorBufferSize = kTestMonitorBufferSize;
mIoOveruseMonitor->mIoOveruseWarnPercentage = kTestIoOveruseWarnPercentage;
mIoOveruseMonitor->mIoOveruseConfigs = ioOveruseConfigs;
@@ -220,23 +221,22 @@
};
TEST_F(IoOveruseMonitorTest, TestOnPeriodicCollection) {
- std::unordered_map<uid_t, PackageInfo> packageInfoMapping = {
- {1001000,
- constructPackageInfo(
- /*packageName=*/"system.daemon", /*uid=*/1001000, UidType::NATIVE)},
- {1112345,
- constructPackageInfo(
- /*packageName=*/"com.android.google.package", /*uid=*/1112345,
- UidType::APPLICATION)},
- {1212345,
- constructPackageInfo(
- /*packageName=*/"com.android.google.package", /*uid=*/1212345,
- UidType::APPLICATION)},
- {1113999,
- constructPackageInfo(
- /*packageName=*/"com.android.google.package", /*uid=*/1113999,
- UidType::APPLICATION)},
- };
+ std::unordered_map<uid_t, PackageInfo> packageInfoMapping =
+ {{1001000,
+ constructPackageInfo(
+ /*packageName=*/"system.daemon", /*uid=*/1001000, UidType::NATIVE)},
+ {1112345,
+ constructPackageInfo(
+ /*packageName=*/"com.android.google.package", /*uid=*/1112345,
+ UidType::APPLICATION)},
+ {1212345,
+ constructPackageInfo(
+ /*packageName=*/"com.android.google.package", /*uid=*/1212345,
+ UidType::APPLICATION)},
+ {1113999,
+ constructPackageInfo(
+ /*packageName=*/"com.android.google.package", /*uid=*/1113999,
+ UidType::APPLICATION)}};
ON_CALL(*mMockPackageInfoResolver, getPackageInfosForUids(_))
.WillByDefault(Return(packageInfoMapping));
mMockIoOveruseConfigs->injectPackageConfigs({
@@ -272,24 +272,25 @@
ASSERT_RESULT_OK(
mIoOveruseMonitor->onPeriodicCollection(currentTime, mockUidIoStats, nullptr, nullptr));
- std::vector<PackageIoOveruseStats> expectedIoOveruseStats = {
- constructPackageIoOveruseStats(/*uid*=*/1001000, /*shouldNotify=*/false,
- /*isKillable=*/false, /*remaining=*/
- constructPerStateBytes(10'000, 20'000, 100'000),
- /*written=*/constructPerStateBytes(70'000, 20'000, 0),
- /*totalOveruses=*/0, startTime, durationInSeconds),
- constructPackageIoOveruseStats(/*uid*=*/1112345, /*shouldNotify=*/false,
- /*isKillable=*/true, /*remaining=*/
- constructPerStateBytes(35'000, 15'000, 100'000),
- /*written=*/constructPerStateBytes(35'000, 15'000, 0),
- /*totalOveruses=*/0, startTime, durationInSeconds),
- // Exceeds threshold.
- constructPackageIoOveruseStats(/*uid*=*/1212345, /*shouldNotify=*/true,
- /*isKillable=*/true,
- /*remaining=*/constructPerStateBytes(0, 10'000, 100'000),
- /*written=*/constructPerStateBytes(70'000, 20'000, 0),
- /*totalOveruses=*/1, startTime, durationInSeconds),
- };
+
+ std::vector<PackageIoOveruseStats> expectedIoOveruseStats =
+ {constructPackageIoOveruseStats(/*uid*=*/1001000, /*shouldNotify=*/false,
+ /*isKillable=*/false, /*remaining=*/
+ constructPerStateBytes(10'000, 20'000, 100'000),
+ /*written=*/constructPerStateBytes(70'000, 20'000, 0),
+ /*totalOveruses=*/0, startTime, durationInSeconds),
+ constructPackageIoOveruseStats(/*uid*=*/1112345, /*shouldNotify=*/false,
+ /*isKillable=*/true, /*remaining=*/
+ constructPerStateBytes(35'000, 15'000, 100'000),
+ /*written=*/constructPerStateBytes(35'000, 15'000, 0),
+ /*totalOveruses=*/0, startTime, durationInSeconds),
+ // Exceeds threshold.
+ constructPackageIoOveruseStats(/*uid*=*/1212345, /*shouldNotify=*/true,
+ /*isKillable=*/true,
+ /*remaining=*/
+ constructPerStateBytes(0, 10'000, 100'000),
+ /*written=*/constructPerStateBytes(70'000, 20'000, 0),
+ /*totalOveruses=*/1, startTime, durationInSeconds)};
EXPECT_THAT(actualIoOveruseStats, UnorderedElementsAreArray(expectedIoOveruseStats))
<< "Expected: " << toString(expectedIoOveruseStats)
<< "\nActual: " << toString(actualIoOveruseStats);
@@ -384,15 +385,171 @@
<< "\nActual: " << toString(actualIoOveruseStats);
}
+TEST_F(IoOveruseMonitorTest, TestOnPeriodicCollectionWithZeroWriteBytes) {
+ sp<MockUidIoStats> mockUidIoStats = new MockUidIoStats();
+ mockUidIoStats->expectDeltaStats(
+ {{1001000, IoUsage(10, 0, /*fgWrBytes=*/0, /*bgWrBytes=*/0, 1, 0)},
+ {1112345, IoUsage(0, 20, /*fgWrBytes=*/0, /*bgWrBytes=*/0, 0, 0)},
+ {1212345, IoUsage(0, 00, /*fgWrBytes=*/0, /*bgWrBytes=*/0, 0, 1)}});
+
+ EXPECT_CALL(*mMockPackageInfoResolver, getPackageInfosForUids(_)).Times(0);
+ EXPECT_CALL(*mMockIoOveruseConfigs, fetchThreshold(_)).Times(0);
+ EXPECT_CALL(*mMockIoOveruseConfigs, isSafeToKill(_)).Times(0);
+ EXPECT_CALL(*mMockWatchdogServiceHelper, latestIoOveruseStats(_)).Times(0);
+
+ ASSERT_RESULT_OK(
+ mIoOveruseMonitor->onPeriodicCollection(std::chrono::system_clock::to_time_t(
+ std::chrono::system_clock::now()),
+ mockUidIoStats, nullptr, nullptr));
+}
+
+TEST_F(IoOveruseMonitorTest, TestOnPeriodicCollectionWithSmallWrittenBytes) {
+ std::unordered_map<uid_t, PackageInfo> packageInfoMapping =
+ {{1001000,
+ constructPackageInfo(
+ /*packageName=*/"system.daemon", /*uid=*/1001000, UidType::NATIVE)},
+ {1112345,
+ constructPackageInfo(
+ /*packageName=*/"com.android.google.package", /*uid=*/1112345,
+ UidType::APPLICATION)},
+ {1212345,
+ constructPackageInfo(
+ /*packageName=*/"com.android.google.package", /*uid=*/1212345,
+ UidType::APPLICATION)},
+ {1312345,
+ constructPackageInfo(
+ /*packageName=*/"com.android.google.package", /*uid=*/1312345,
+ UidType::APPLICATION)}};
+ EXPECT_CALL(*mMockPackageInfoResolver, getPackageInfosForUids(_))
+ .WillRepeatedly(Return(packageInfoMapping));
+ mMockIoOveruseConfigs->injectPackageConfigs(
+ {{"system.daemon",
+ {constructPerStateBytes(/*fgBytes=*/80'000, /*bgBytes=*/40'000, /*gmBytes=*/100'000),
+ /*isSafeToKill=*/false}},
+ {"com.android.google.package",
+ {constructPerStateBytes(/*fgBytes=*/70'000, /*bgBytes=*/30'000, /*gmBytes=*/100'000),
+ /*isSafeToKill=*/true}}});
+
+ sp<MockUidIoStats> mockUidIoStats = new MockUidIoStats();
+ /*
+ * UID 1212345 current written bytes < |KTestMinSyncWrittenBytes| so the UID's stats are not
+ * synced.
+ */
+ mockUidIoStats->expectDeltaStats(
+ {{1001000, IoUsage(10, 0, /*fgWrBytes=*/59'200, /*bgWrBytes=*/0, 1, 0)},
+ {1112345, IoUsage(0, 20, /*fgWrBytes=*/0, /*bgWrBytes=*/25'200, 0, 0)},
+ {1212345, IoUsage(0, 00, /*fgWrBytes=*/300, /*bgWrBytes=*/600, 0, 1)},
+ {1312345, IoUsage(0, 00, /*fgWrBytes=*/51'200, /*bgWrBytes=*/0, 0, 1)}});
+
+ std::vector<PackageIoOveruseStats> actualIoOveruseStats;
+ EXPECT_CALL(*mMockWatchdogServiceHelper, latestIoOveruseStats(_))
+ .WillOnce(DoAll(SaveArg<0>(&actualIoOveruseStats), Return(Status::ok())));
+
+ time_t currentTime = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
+ const auto [startTime, durationInSeconds] = calculateStartAndDuration(currentTime);
+
+ ASSERT_RESULT_OK(
+ mIoOveruseMonitor->onPeriodicCollection(currentTime, mockUidIoStats, nullptr, nullptr));
+
+ std::vector<PackageIoOveruseStats> expectedIoOveruseStats =
+ {constructPackageIoOveruseStats(/*uid*=*/1001000, /*shouldNotify=*/false,
+ /*isKillable=*/false, /*remaining=*/
+ constructPerStateBytes(20'800, 40'000, 100'000),
+ /*written=*/
+ constructPerStateBytes(59'200, 0, 0),
+ /*totalOveruses=*/0, startTime, durationInSeconds),
+ constructPackageIoOveruseStats(/*uid*=*/1112345, /*shouldNotify=*/true,
+ /*isKillable=*/true, /*remaining=*/
+ constructPerStateBytes(70'000, 4'800, 100'000),
+ /*written=*/constructPerStateBytes(0, 25'200, 0),
+ /*totalOveruses=*/0, startTime, durationInSeconds),
+ constructPackageIoOveruseStats(/*uid*=*/1312345, /*shouldNotify=*/false,
+ /*isKillable=*/true, /*remaining=*/
+ constructPerStateBytes(18'800, 30'000, 100'000),
+ /*written=*/constructPerStateBytes(51'200, 0, 0),
+ /*totalOveruses=*/0, startTime, durationInSeconds)};
+
+ EXPECT_THAT(actualIoOveruseStats, UnorderedElementsAreArray(expectedIoOveruseStats))
+ << "Expected: " << toString(expectedIoOveruseStats)
+ << "\nActual: " << toString(actualIoOveruseStats);
+
+ actualIoOveruseStats.clear();
+ EXPECT_CALL(*mMockWatchdogServiceHelper, latestIoOveruseStats(_))
+ .WillOnce(DoAll(SaveArg<0>(&actualIoOveruseStats), Return(Status::ok())));
+
+ /*
+ * UID 1001000 current written bytes is < |kTestMinSyncWrittenBytes| but exceeds warn threshold
+ * but not killable so the UID's stats are not synced.
+ * UID 1112345 current written bytes is < |kTestMinSyncWrittenBytes| but exceeds threshold so
+ * the UID's stats are synced.
+ * UID 1212345 current written bytes is < |kTestMinSyncWrittenBytes| but total written bytes
+ * since last synced > |kTestMinSyncWrittenBytes| so the UID's stats are synced.
+ * UID 1312345 current written bytes is < |kTestMinSyncWrittenBytes| but exceeds warn threshold
+ * and killable so the UID's stat are synced.
+ */
+ mockUidIoStats->expectDeltaStats(
+ {{1001000,
+ IoUsage(10, 0, /*fgWrBytes=*/KTestMinSyncWrittenBytes - 100, /*bgWrBytes=*/0, 1, 0)},
+ {1112345,
+ IoUsage(0, 20, /*fgWrBytes=*/0, /*bgWrBytes=*/KTestMinSyncWrittenBytes - 100, 0, 0)},
+ {1212345,
+ IoUsage(0, 00, /*fgWrBytes=*/KTestMinSyncWrittenBytes - 300, /*bgWrBytes=*/0, 0, 1)},
+ {1312345,
+ IoUsage(0, 00, /*fgWrBytes=*/KTestMinSyncWrittenBytes - 100, /*bgWrBytes=*/0, 0,
+ 1)}});
+
+ ASSERT_RESULT_OK(
+ mIoOveruseMonitor->onPeriodicCollection(currentTime, mockUidIoStats, nullptr, nullptr));
+
+ expectedIoOveruseStats =
+ {constructPackageIoOveruseStats(/*uid*=*/1112345, /*shouldNotify=*/true,
+ /*isKillable=*/true, /*remaining=*/
+ constructPerStateBytes(70'000, 0, 100'000),
+ /*written=*/constructPerStateBytes(0, 30'100, 0),
+ /*totalOveruses=*/1, startTime, durationInSeconds),
+ constructPackageIoOveruseStats(/*uid*=*/1212345, /*shouldNotify=*/false,
+ /*isKillable=*/true, /*remaining=*/
+ constructPerStateBytes(65'000, 29'400, 100'000),
+ /*written=*/constructPerStateBytes(5'000, 600, 0),
+ /*totalOveruses=*/0, startTime, durationInSeconds),
+ constructPackageIoOveruseStats(/*uid*=*/1312345, /*shouldNotify=*/true,
+ /*isKillable=*/true, /*remaining=*/
+ constructPerStateBytes(13'900, 30'000, 100'000),
+ /*written=*/constructPerStateBytes(56'100, 0, 0),
+ /*totalOveruses=*/0, startTime, durationInSeconds)};
+ EXPECT_THAT(actualIoOveruseStats, UnorderedElementsAreArray(expectedIoOveruseStats))
+ << "Expected: " << toString(expectedIoOveruseStats)
+ << "\nActual: " << toString(actualIoOveruseStats);
+}
+
+TEST_F(IoOveruseMonitorTest, TestOnPeriodicCollectionWithNoPackageInfo) {
+ sp<MockUidIoStats> mockUidIoStats = new MockUidIoStats();
+ mockUidIoStats->expectDeltaStats(
+ {{1001000, IoUsage(0, 0, /*fgWrBytes=*/70'000, /*bgWrBytes=*/20'000, 0, 0)},
+ {1112345, IoUsage(0, 0, /*fgWrBytes=*/35'000, /*bgWrBytes=*/15'000, 0, 0)},
+ {1212345, IoUsage(0, 0, /*fgWrBytes=*/70'000, /*bgWrBytes=*/20'000, 0, 0)}});
+
+ ON_CALL(*mMockPackageInfoResolver, getPackageInfosForUids(_))
+ .WillByDefault(Return(std::unordered_map<uid_t, PackageInfo>{}));
+
+ EXPECT_CALL(*mMockIoOveruseConfigs, fetchThreshold(_)).Times(0);
+ EXPECT_CALL(*mMockIoOveruseConfigs, isSafeToKill(_)).Times(0);
+ EXPECT_CALL(*mMockWatchdogServiceHelper, latestIoOveruseStats(_)).Times(0);
+
+ ASSERT_RESULT_OK(
+ mIoOveruseMonitor->onPeriodicCollection(std::chrono::system_clock::to_time_t(
+ std::chrono::system_clock::now()),
+ mockUidIoStats, nullptr, nullptr));
+}
+
TEST_F(IoOveruseMonitorTest, TestOnPeriodicMonitor) {
- IIoOveruseConfigs::IoOveruseAlertThresholdSet alertThresholds = {
- toIoOveruseAlertThreshold(
- /*durationInSeconds=*/10, /*writtenBytesPerSecond=*/15'360),
- toIoOveruseAlertThreshold(
- /*durationInSeconds=*/17, /*writtenBytesPerSecond=*/10'240),
- toIoOveruseAlertThreshold(
- /*durationInSeconds=*/23, /*writtenBytesPerSecond=*/7'168),
- };
+ IIoOveruseConfigs::IoOveruseAlertThresholdSet alertThresholds =
+ {toIoOveruseAlertThreshold(
+ /*durationInSeconds=*/10, /*writtenBytesPerSecond=*/15'360),
+ toIoOveruseAlertThreshold(
+ /*durationInSeconds=*/17, /*writtenBytesPerSecond=*/10'240),
+ toIoOveruseAlertThreshold(
+ /*durationInSeconds=*/23, /*writtenBytesPerSecond=*/7'168)};
ON_CALL(*mMockIoOveruseConfigs, systemWideAlertThresholds())
.WillByDefault(ReturnRef(alertThresholds));
diff --git a/cpp/watchdog/server/tests/MockCarWatchdogServiceForSystem.h b/cpp/watchdog/server/tests/MockCarWatchdogServiceForSystem.h
index 8ea4ba3..c8f1614 100644
--- a/cpp/watchdog/server/tests/MockCarWatchdogServiceForSystem.h
+++ b/cpp/watchdog/server/tests/MockCarWatchdogServiceForSystem.h
@@ -24,7 +24,6 @@
#include <binder/Status.h>
#include <gmock/gmock.h>
#include <utils/RefBase.h>
-#include <utils/String16.h>
#include <utils/StrongPointer.h>
namespace android {
@@ -45,7 +44,7 @@
(int32_t, android::automotive::watchdog::internal::TimeoutLength), (override));
MOCK_METHOD(android::binder::Status, prepareProcessTermination, (), (override));
MOCK_METHOD(android::binder::Status, getPackageInfosForUids,
- (const std::vector<int32_t>&, const std::vector<android::String16>&,
+ (const std::vector<int32_t>&, const std::vector<std::string>&,
std::vector<android::automotive::watchdog::internal::PackageInfo>*),
(override));
MOCK_METHOD(
diff --git a/cpp/watchdog/server/tests/MockDataProcessor.h b/cpp/watchdog/server/tests/MockDataProcessor.h
index e120ff7..34a572e 100644
--- a/cpp/watchdog/server/tests/MockDataProcessor.h
+++ b/cpp/watchdog/server/tests/MockDataProcessor.h
@@ -28,7 +28,7 @@
class MockDataProcessor : virtual public IDataProcessorInterface {
public:
MockDataProcessor() {
- ON_CALL(*this, name()).WillByDefault(::testing::Return("MockedDataProcessor"));
+ EXPECT_CALL(*this, name()).WillRepeatedly(::testing::Return("MockedDataProcessor"));
}
MOCK_METHOD(std::string, name, (), (override));
MOCK_METHOD(android::base::Result<void>, init, (), (override));
diff --git a/cpp/watchdog/server/tests/MockIoOveruseConfigs.h b/cpp/watchdog/server/tests/MockIoOveruseConfigs.h
index 67c1edf..cc88c1d 100644
--- a/cpp/watchdog/server/tests/MockIoOveruseConfigs.h
+++ b/cpp/watchdog/server/tests/MockIoOveruseConfigs.h
@@ -71,8 +71,7 @@
.WillByDefault([perPackageConfig = perPackageConfig](
const android::automotive::watchdog::internal::PackageInfo&
packageInfo) {
- const std::string packageName =
- std::string(String8(packageInfo.packageIdentifier.name));
+ const std::string packageName = packageInfo.packageIdentifier.name;
if (const auto it = perPackageConfig.find(packageName);
it != perPackageConfig.end()) {
return it->second.threshold;
@@ -83,8 +82,7 @@
.WillByDefault([perPackageConfig = perPackageConfig](
const android::automotive::watchdog::internal::PackageInfo&
packageInfo) {
- const std::string packageName =
- std::string(String8(packageInfo.packageIdentifier.name));
+ const std::string packageName = packageInfo.packageIdentifier.name;
if (const auto it = perPackageConfig.find(packageName);
it != perPackageConfig.end()) {
return it->second.isSafeToKill;
diff --git a/cpp/watchdog/server/tests/MockIoOveruseMonitor.h b/cpp/watchdog/server/tests/MockIoOveruseMonitor.h
index 221b1a1..cb7d0c5 100644
--- a/cpp/watchdog/server/tests/MockIoOveruseMonitor.h
+++ b/cpp/watchdog/server/tests/MockIoOveruseMonitor.h
@@ -35,6 +35,7 @@
ON_CALL(*this, name()).WillByDefault(::testing::Return("MockIoOveruseMonitor"));
}
~MockIoOveruseMonitor() {}
+ MOCK_METHOD(bool, isInitialized, (), (override));
MOCK_METHOD(android::base::Result<void>, updateResourceOveruseConfigurations,
(const std::vector<
android::automotive::watchdog::internal::ResourceOveruseConfiguration>&),
diff --git a/cpp/watchdog/server/tests/MockWatchdogPerfService.h b/cpp/watchdog/server/tests/MockWatchdogPerfService.h
index 5d14837..60d165b 100644
--- a/cpp/watchdog/server/tests/MockWatchdogPerfService.h
+++ b/cpp/watchdog/server/tests/MockWatchdogPerfService.h
@@ -36,7 +36,7 @@
MOCK_METHOD(void, terminate, (), (override));
MOCK_METHOD(android::base::Result<void>, onBootFinished, (), (override));
MOCK_METHOD(android::base::Result<void>, onCustomCollection,
- (int fd, const Vector<String16>& args), (override));
+ (int fd, const Vector<android::String16>& args), (override));
MOCK_METHOD(android::base::Result<void>, onDump, (int fd), (override));
};
diff --git a/cpp/watchdog/server/tests/MockWatchdogProcessService.h b/cpp/watchdog/server/tests/MockWatchdogProcessService.h
index 8c35f10..b720d91 100644
--- a/cpp/watchdog/server/tests/MockWatchdogProcessService.h
+++ b/cpp/watchdog/server/tests/MockWatchdogProcessService.h
@@ -40,7 +40,7 @@
class MockWatchdogProcessService : public WatchdogProcessService {
public:
MockWatchdogProcessService() : WatchdogProcessService(nullptr) {}
- MOCK_METHOD(android::base::Result<void>, dump, (int fd, const Vector<String16>& args),
+ MOCK_METHOD(android::base::Result<void>, dump, (int fd, const Vector<android::String16>& args),
(override));
MOCK_METHOD(android::base::Result<void>, registerWatchdogServiceHelper,
(const android::sp<IWatchdogServiceHelperInterface>& helper), (override));
diff --git a/cpp/watchdog/server/tests/PackageInfoResolverTest.cpp b/cpp/watchdog/server/tests/PackageInfoResolverTest.cpp
index 92fc5b0..f2fe3a6 100644
--- a/cpp/watchdog/server/tests/PackageInfoResolverTest.cpp
+++ b/cpp/watchdog/server/tests/PackageInfoResolverTest.cpp
@@ -23,13 +23,11 @@
#include <android/automotive/watchdog/internal/UidType.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
-#include <utils/String16.h>
namespace android {
namespace automotive {
namespace watchdog {
-using ::android::String16;
using ::android::automotive::watchdog::internal::ApplicationCategoryType;
using ::android::automotive::watchdog::internal::ComponentType;
using ::android::automotive::watchdog::internal::PackageInfo;
@@ -53,9 +51,9 @@
PackageInfo constructPackageInfo(const char* packageName, int32_t uid, UidType uidType,
ComponentType componentType,
ApplicationCategoryType appCategoryType,
- std::vector<String16> sharedUidPackages = {}) {
+ std::vector<std::string> sharedUidPackages = {}) {
PackageInfo packageInfo;
- packageInfo.packageIdentifier.name = String16(packageName);
+ packageInfo.packageIdentifier.name = packageName;
packageInfo.packageIdentifier.uid = uid;
packageInfo.uidType = uidType;
packageInfo.componentType = componentType;
@@ -209,7 +207,7 @@
{6100,
constructPackageInfo("shared:system.package.A", 6100, UidType::NATIVE,
ComponentType::SYSTEM, ApplicationCategoryType::OTHERS,
- {String16("system.pkg.1"), String16("system.pkg.2")})},
+ {"system.pkg.1", "system.pkg.2"})},
{7700,
constructPackageInfo("system.package.B", 7700, UidType::NATIVE, ComponentType::SYSTEM,
ApplicationCategoryType::OTHERS)},
diff --git a/cpp/watchdog/server/tests/UidIoStatsTest.cpp b/cpp/watchdog/server/tests/UidIoStatsTest.cpp
index 2326381..6e88ed5 100644
--- a/cpp/watchdog/server/tests/UidIoStatsTest.cpp
+++ b/cpp/watchdog/server/tests/UidIoStatsTest.cpp
@@ -17,6 +17,7 @@
#include "UidIoStats.h"
#include <android-base/file.h>
+#include <android-base/stringprintf.h>
#include <gmock/gmock.h>
#include <unordered_map>
@@ -25,25 +26,37 @@
namespace automotive {
namespace watchdog {
+using ::android::base::StringAppendF;
using ::android::base::WriteStringToFile;
+using ::testing::UnorderedElementsAreArray;
+
+namespace {
+
+std::string toString(std::unordered_map<uid_t, UidIoUsage> usages) {
+ std::string buffer;
+ for (const auto& [uid, usage] : usages) {
+ StringAppendF(&buffer, "{%s}\n", usage.toString().c_str());
+ }
+ return buffer;
+}
+
+} // namespace
TEST(UidIoStatsTest, TestValidStatFile) {
// Format: uid fgRdChar fgWrChar fgRdBytes fgWrBytes bgRdChar bgWrChar bgRdBytes bgWrBytes
// fgFsync bgFsync
- constexpr char firstSnapshot[] =
- "1001234 5000 1000 3000 500 0 0 0 0 20 0\n"
- "1005678 500 100 30 50 300 400 100 200 45 60\n"
- "1009 0 0 0 0 40000 50000 20000 30000 0 300\n"
- "1001000 4000 3000 2000 1000 400 300 200 100 50 10\n";
- std::unordered_map<uid_t, UidIoUsage> expectedFirstUsage = {
- {1001234,
- {.uid = 1001234,
- .ios = {/*fgRdBytes=*/3000, /*bgRdBytes=*/0, /*fgWrBytes=*/500,
- /*bgWrBytes=*/0, /*fgFsync=*/20, /*bgFsync=*/0}}},
- {1005678, {.uid = 1005678, .ios = {30, 100, 50, 200, 45, 60}}},
- {1009, {.uid = 1009, .ios = {0, 20000, 0, 30000, 0, 300}}},
- {1001000, {.uid = 1001000, .ios = {2000, 200, 1000, 100, 50, 10}}},
- };
+ constexpr char firstSnapshot[] = "1001234 5000 1000 3000 500 0 0 0 0 20 0\n"
+ "1005678 500 100 30 50 300 400 100 200 45 60\n"
+ "1009 0 0 0 0 40000 50000 20000 30000 0 300\n"
+ "1001000 4000 3000 2000 1000 400 300 200 100 50 10\n";
+ std::unordered_map<uid_t, UidIoUsage> expectedFirstUsage =
+ {{1001234,
+ {.uid = 1001234,
+ .ios = {/*fgRdBytes=*/3000, /*bgRdBytes=*/0, /*fgWrBytes=*/500,
+ /*bgWrBytes=*/0, /*fgFsync=*/20, /*bgFsync=*/0}}},
+ {1005678, {.uid = 1005678, .ios = {30, 100, 50, 200, 45, 60}}},
+ {1009, {.uid = 1009, .ios = {0, 20000, 0, 30000, 0, 300}}},
+ {1001000, {.uid = 1001000, .ios = {2000, 200, 1000, 100, 50, 10}}}};
TemporaryFile tf;
ASSERT_NE(tf.fd, -1);
ASSERT_TRUE(WriteStringToFile(firstSnapshot, tf.path));
@@ -53,62 +66,38 @@
ASSERT_RESULT_OK(uidIoStats.collect());
const auto& actualFirstUsage = uidIoStats.deltaStats();
- EXPECT_EQ(expectedFirstUsage.size(), actualFirstUsage.size());
+ EXPECT_THAT(actualFirstUsage, UnorderedElementsAreArray(expectedFirstUsage))
+ << "Expected: " << toString(expectedFirstUsage)
+ << "Actual: " << toString(actualFirstUsage);
- for (const auto& it : expectedFirstUsage) {
- if (actualFirstUsage.find(it.first) == actualFirstUsage.end()) {
- ADD_FAILURE() << "Expected uid " << it.first << " not found in the first snapshot";
- }
- const UidIoUsage& expected = it.second;
- const UidIoUsage& actual = actualFirstUsage.at(it.first);
- EXPECT_EQ(expected.uid, actual.uid);
- EXPECT_EQ(expected.ios, actual.ios)
- << "Unexpected I/O usage for uid " << it.first << " in first snapshot.\nExpected:\n"
- << expected.ios.toString() << "\nActual:\n"<< actual.ios.toString();
- }
-
- constexpr char secondSnapshot[] =
- "1001234 10000 2000 7000 950 0 0 0 0 45 0\n"
- "1005678 600 100 40 50 1000 1000 1000 600 50 70\n"
- "1003456 300 500 200 300 0 0 0 0 50 0\n"
- "1001000 400 300 200 100 40 30 20 10 5 1\n";
- std::unordered_map<uid_t, UidIoUsage> expectedSecondUsage = {
- {1001234,
- {.uid = 1001234,
- .ios = {/*fgRdBytes=*/4000, /*bgRdBytes=*/0,
- /*fgWrBytes=*/450, /*bgWrBytes=*/0, /*fgFsync=*/25,
- /*bgFsync=*/0}}},
- {1005678, {.uid = 1005678, .ios = {10, 900, 0, 400, 5, 10}}},
- {1003456, {.uid = 1003456, .ios = {200, 0, 300, 0, 50, 0}}},
- {1001000, {.uid = 1001000, .ios = {0, 0, 0, 0, 0, 0}}},
- };
+ constexpr char secondSnapshot[] = "1001234 10000 2000 7000 950 0 0 0 0 45 0\n"
+ "1005678 600 100 40 50 1000 1000 1000 600 50 70\n"
+ "1003456 300 500 200 300 0 0 0 0 50 0\n"
+ "1001000 400 300 200 100 40 30 20 10 5 1\n";
+ std::unordered_map<uid_t, UidIoUsage> expectedSecondUsage =
+ {{1001234,
+ {.uid = 1001234,
+ .ios = {/*fgRdBytes=*/4000, /*bgRdBytes=*/0,
+ /*fgWrBytes=*/450, /*bgWrBytes=*/0, /*fgFsync=*/25,
+ /*bgFsync=*/0}}},
+ {1005678, {.uid = 1005678, .ios = {10, 900, 0, 400, 5, 10}}},
+ {1003456, {.uid = 1003456, .ios = {200, 0, 300, 0, 50, 0}}}};
ASSERT_TRUE(WriteStringToFile(secondSnapshot, tf.path));
ASSERT_RESULT_OK(uidIoStats.collect());
const auto& actualSecondUsage = uidIoStats.deltaStats();
- EXPECT_EQ(expectedSecondUsage.size(), actualSecondUsage.size());
-
- for (const auto& it : expectedSecondUsage) {
- if (actualSecondUsage.find(it.first) == actualSecondUsage.end()) {
- ADD_FAILURE() << "Expected uid " << it.first << " not found in the second snapshot";
- }
- const UidIoUsage& expected = it.second;
- const UidIoUsage& actual = actualSecondUsage.at(it.first);
- EXPECT_EQ(expected.uid, actual.uid);
- EXPECT_EQ(expected.ios, actual.ios)
- << "Unexpected I/O usage for uid " << it.first << " in second snapshot:.\nExpected:\n"
- << expected.ios.toString() << "\nActual:\n"<< actual.ios.toString();
- }
+ EXPECT_THAT(actualSecondUsage, UnorderedElementsAreArray(expectedSecondUsage))
+ << "Expected: " << toString(expectedSecondUsage)
+ << "Actual: " << toString(actualSecondUsage);
}
TEST(UidIoStatsTest, TestErrorOnInvalidStatFile) {
// Format: uid fgRdChar fgWrChar fgRdBytes fgWrBytes bgRdChar bgWrChar bgRdBytes bgWrBytes
// fgFsync bgFsync
- constexpr char contents[] =
- "1001234 5000 1000 3000 500 0 0 0 0 20 0\n"
- "1005678 500 100 30 50 300 400 100 200 45 60\n"
- "1009012 0 0 0 0 40000 50000 20000 30000 0 300\n"
- "1001000 4000 3000 2000 1000 CORRUPTED DATA\n";
+ constexpr char contents[] = "1001234 5000 1000 3000 500 0 0 0 0 20 0\n"
+ "1005678 500 100 30 50 300 400 100 200 45 60\n"
+ "1009012 0 0 0 0 40000 50000 20000 30000 0 300\n"
+ "1001000 4000 3000 2000 1000 CORRUPTED DATA\n";
TemporaryFile tf;
ASSERT_NE(tf.fd, -1);
ASSERT_TRUE(WriteStringToFile(contents, tf.path));
diff --git a/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp b/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp
index 3db7e29..f5dd7dc 100644
--- a/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp
+++ b/cpp/watchdog/server/tests/WatchdogInternalHandlerTest.cpp
@@ -48,6 +48,7 @@
using aawi::PackageResourceOveruseAction;
using aawi::ResourceOveruseConfiguration;
using ::android::sp;
+using ::android::String16;
using ::android::base::Result;
using ::android::binder::Status;
using ::testing::_;
@@ -67,7 +68,7 @@
-> Result<void> { return Result<void>{}; }) {}
~MockWatchdogBinderMediator() {}
- MOCK_METHOD(status_t, dump, (int fd, const Vector<String16>& args), (override));
+ MOCK_METHOD(status_t, dump, (int fd, const Vector<android::String16>& args), (override));
};
class ScopedChangeCallingUid : public RefBase {
diff --git a/cpp/watchdog/server/tests/WatchdogPerfServiceTest.cpp b/cpp/watchdog/server/tests/WatchdogPerfServiceTest.cpp
index c7de12b..3873195 100644
--- a/cpp/watchdog/server/tests/WatchdogPerfServiceTest.cpp
+++ b/cpp/watchdog/server/tests/WatchdogPerfServiceTest.cpp
@@ -39,6 +39,7 @@
namespace automotive {
namespace watchdog {
+using ::android::String16;
using ::android::wp;
using ::android::automotive::watchdog::testing::LooperStub;
using ::android::base::Error;
diff --git a/cpp/watchdog/server/tests/WatchdogServiceHelperTest.cpp b/cpp/watchdog/server/tests/WatchdogServiceHelperTest.cpp
index 681d900..17edadf 100644
--- a/cpp/watchdog/server/tests/WatchdogServiceHelperTest.cpp
+++ b/cpp/watchdog/server/tests/WatchdogServiceHelperTest.cpp
@@ -22,7 +22,6 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <utils/RefBase.h>
-#include <utils/String16.h>
namespace android {
namespace automotive {
@@ -41,7 +40,6 @@
using ::android::IBinder;
using ::android::RefBase;
using ::android::sp;
-using ::android::String16;
using ::android::base::Error;
using ::android::base::Result;
using ::android::binder::Status;
@@ -80,7 +78,7 @@
ComponentType componentType,
ApplicationCategoryType appCategoryType) {
PackageInfo packageInfo;
- packageInfo.packageIdentifier.name = String16(packageName);
+ packageInfo.packageIdentifier.name = packageName;
packageInfo.packageIdentifier.uid = uid;
packageInfo.uidType = uidType;
packageInfo.componentType = componentType;
@@ -329,7 +327,6 @@
TEST_F(WatchdogServiceHelperTest, TestGetPackageInfosForUids) {
std::vector<int32_t> uids = {1000};
std::vector<std::string> prefixesStr = {"vendor.package"};
- std::vector<String16> prefixesStr16 = {String16("vendor.package")};
std::vector<PackageInfo> expectedPackageInfo{
constructPackageInfo("vendor.package.A", 120000, UidType::NATIVE, ComponentType::VENDOR,
ApplicationCategoryType::OTHERS),
@@ -340,7 +337,7 @@
registerCarWatchdogService();
- EXPECT_CALL(*mMockCarWatchdogServiceForSystem, getPackageInfosForUids(uids, prefixesStr16, _))
+ EXPECT_CALL(*mMockCarWatchdogServiceForSystem, getPackageInfosForUids(uids, prefixesStr, _))
.WillOnce(DoAll(SetArgPointee<2>(expectedPackageInfo), Return(Status::ok())));
Status status =
diff --git a/packages/CarShell/AndroidManifest.xml b/packages/CarShell/AndroidManifest.xml
index a32307b..2f39dfd 100644
--- a/packages/CarShell/AndroidManifest.xml
+++ b/packages/CarShell/AndroidManifest.xml
@@ -37,6 +37,8 @@
<uses-permission android:name="android.car.permission.CAR_DIAGNOSTICS" />
<!-- Permission required for 'adb shell cmd car_service silent-mode' -->
<uses-permission android:name="android.car.permission.CAR_POWER" />
+ <!-- Permission required for ATS tests - AtsCarTests#CarPowerManagerTest-->
+ <uses-permission android:name="android.car.permission.CONTROL_CAR_POWER_POLICY" />
<!-- Permission required for 'adb shell cmd car_service day-night-mode [day|night]' -->
<uses-permission android:name="android.car.permission.MODIFY_DAY_NIGHT_MODE" />
<!-- Permission required for ATS tests - AtsDeviceInfo -->
diff --git a/service/Android.bp b/service/Android.bp
index ae41129..700d4e3 100644
--- a/service/Android.bp
+++ b/service/Android.bp
@@ -55,6 +55,7 @@
// to CarSettings
"car-admin-ui-lib",
"Slogf",
+ "cartelemetry-protos",
]
android_app {
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
index c7e719d..af0fb97 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -370,6 +370,14 @@
android:label="@string/car_permission_label_driving_state"
android:description="@string/car_permission_desc_driving_state"/>
+ <!-- Allows an application to use car's telemetry service.
+ <p>Protection level: signature|privileged
+ -->
+ <permission android:name="android.car.permission.USE_CAR_TELEMETRY_SERVICE"
+ android:protectionLevel="signature|privileged"
+ android:label="@string/car_permission_label_use_telemetry_service"
+ android:description="@string/car_permission_desc_use_telemetry_service"/>
+
<!-- Allows an application to request to launch the EVS previewing activity.
<p>Protection level: signature|privileged
-->
diff --git a/service/jni/Android.bp b/service/jni/Android.bp
index 6048d06..86f3455 100644
--- a/service/jni/Android.bp
+++ b/service/jni/Android.bp
@@ -14,6 +14,10 @@
//
//
//#################################
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_library_shared {
name: "libcarservicejni",
srcs: [
diff --git a/service/res/values-bs/strings.xml b/service/res/values-bs/strings.xml
index 7158d2c..1fd16cc 100644
--- a/service/res/values-bs/strings.xml
+++ b/service/res/values-bs/strings.xml
@@ -21,17 +21,17 @@
<string name="car_permission_label_camera" msgid="3725702064841827180">"pristupiti kameri automobila"</string>
<string name="car_permission_desc_camera" msgid="917024932164501426">"Pristupiti kameri(ama) automobila."</string>
<string name="car_permission_label_energy" msgid="7409144323527821558">"pristupiti informacijama o energiji automobila"</string>
- <string name="car_permission_desc_energy" msgid="3392963810053235407">"Pristupiti informacijama o energiji automobila."</string>
- <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"podesi preostali domet automobila"</string>
- <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"Podesi vrijednost preostalog dometa automobila."</string>
+ <string name="car_permission_desc_energy" msgid="3392963810053235407">"pristupiti informacijama o energiji automobila"</string>
+ <string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"podesiti preostali domet automobila"</string>
+ <string name="car_permission_desc_adjust_range_remaining" msgid="2369321650437370673">"podesiti vrijednost preostalog dometa automobila"</string>
<string name="car_permission_label_hvac" msgid="1499454192558727843">"pristupiti grijanju, ventilaciji i klimatizaciji automobila"</string>
<string name="car_permission_desc_hvac" msgid="3754229695589774195">"Pristupiti grijanju, ventilaciji i klimatizaciji automobila."</string>
<string name="car_permission_label_mileage" msgid="4661317074631150551">"pristupiti informacijama o pređenim kilometrima automobila"</string>
- <string name="car_permission_desc_mileage" msgid="7179735693278681090">"Pristupiti informacijama o pređenim kilometrima."</string>
+ <string name="car_permission_desc_mileage" msgid="7179735693278681090">"pristupiti informacijama o pređenim kilometrima"</string>
<string name="car_permission_label_speed" msgid="1149027717860529745">"očitati brzinu automobila"</string>
- <string name="car_permission_desc_speed" msgid="2047965198165448241">"Pristupiti informacijama o brzini automobila."</string>
+ <string name="car_permission_desc_speed" msgid="2047965198165448241">"pristupiti informacijama o brzini automobila"</string>
<string name="car_permission_label_vehicle_dynamics_state" msgid="313779267420048367">"pristupiti stanju dinamike automobila"</string>
- <string name="car_permission_desc_vehicle_dynamics_state" msgid="8891506193446375660">"Pristupiti stanju dinamike automobila."</string>
+ <string name="car_permission_desc_vehicle_dynamics_state" msgid="8891506193446375660">"pristupiti stanju dinamike automobila"</string>
<string name="car_permission_label_vendor_extension" msgid="7141601811734127361">"pristupiti kanalu trgovca automobilima"</string>
<string name="car_permission_desc_vendor_extension" msgid="2970718502334714035">"Pristupiti kanalu trgovca automobilima radi razmjene posebnih informacija o automobilu."</string>
<string name="car_permission_label_radio" msgid="6009465291685935112">"upravljati radiom automobila"</string>
@@ -47,11 +47,11 @@
<string name="car_permission_label_mock_vehicle_hal" msgid="7198852512207405935">"emulirati HAL vozila"</string>
<string name="car_permission_label_receive_ducking" msgid="4884538660766756573">"prijem događaja sa smanjivanjem jačine zvuka tokom govora"</string>
<string name="car_permission_desc_receive_ducking" msgid="776376388266656512">"Dozvoljava aplikaciji da primi obavještenje kada se glasnoća smanji uslijed reproduciranja drugog zvučnog zapisa u automobilu."</string>
- <string name="car_permission_desc_mock_vehicle_hal" msgid="5235596491098649155">"Emulirati HAL vozila u svrhu internog testiranja."</string>
- <string name="car_permission_desc_audio_volume" msgid="536626185654307889">"Kontrolirati jačinu zvuka u automobilu."</string>
+ <string name="car_permission_desc_mock_vehicle_hal" msgid="5235596491098649155">"emulirati HAL vozila u svrhu internog testiranja"</string>
+ <string name="car_permission_desc_audio_volume" msgid="536626185654307889">"kontrolirati jačinu zvuka u automobilu"</string>
<string name="car_permission_desc_audio_settings" msgid="7192007170677915937">"Kontrolirajte postavke zvuka automobila."</string>
<string name="car_permission_label_control_app_blocking" msgid="9112678596919993386">"Blokiranje aplikacija"</string>
- <string name="car_permission_desc_control_app_blocking" msgid="7539378161760696190">"Kontrolirati blokiranje aplikacija tokom vožnje."</string>
+ <string name="car_permission_desc_control_app_blocking" msgid="7539378161760696190">"kontrolirati blokiranje aplikacija tokom vožnje"</string>
<string name="car_permission_car_navigation_manager" msgid="5895461364007854077">"Upravitelj navigacije"</string>
<string name="car_permission_desc_car_navigation_manager" msgid="6188751054665471537">"Dostaviti podatke o navigaciji kontrolnoj tabli"</string>
<string name="car_permission_car_display_in_cluster" msgid="4005987646292458684">"Direktno iscrtavanje na kontrolnoj ploči"</string>
diff --git a/service/res/values-gu/strings.xml b/service/res/values-gu/strings.xml
index 9ad2f2f..931b0e7 100644
--- a/service/res/values-gu/strings.xml
+++ b/service/res/values-gu/strings.xml
@@ -96,22 +96,14 @@
<string name="car_permission_desc_driving_state" msgid="2684025262811635737">"ડ્રાઇવ કરવાની સ્થિતિના ફેરફારો વિશે સાંભળો."</string>
<string name="car_permission_label_use_evs_service" msgid="1729276125209310607">"કાર EVS સર્વિસનો ઉપયોગ કરો"</string>
<string name="car_permission_desc_use_evs_service" msgid="2374737642186632816">"EVS વીડિયો સ્ટ્રીમને સબ્સ્ક્રાઇબ કરો"</string>
- <!-- no translation found for car_permission_label_request_evs_activity (3906551972883482883) -->
- <skip />
- <!-- no translation found for car_permission_desc_request_evs_activity (4582768053649138488) -->
- <skip />
- <!-- no translation found for car_permission_label_control_evs_activity (2030069860204405679) -->
- <skip />
- <!-- no translation found for car_permission_desc_control_evs_activity (691646545916976346) -->
- <skip />
- <!-- no translation found for car_permission_label_use_evs_camera (3607720208623955067) -->
- <skip />
- <!-- no translation found for car_permission_desc_use_evs_camera (1625845902221003985) -->
- <skip />
- <!-- no translation found for car_permission_label_monitor_evs_status (2091521314159379622) -->
- <skip />
- <!-- no translation found for car_permission_desc_monitor_evs_status (2764278897143573535) -->
- <skip />
+ <string name="car_permission_label_request_evs_activity" msgid="3906551972883482883">"EVS પ્રીવ્યૂ પ્રવૃત્તિ માટે વિનંતી કરો"</string>
+ <string name="car_permission_desc_request_evs_activity" msgid="4582768053649138488">"EVS પ્રીવ્યૂ પ્રવૃત્તિ લૉન્ચ કરવા માટે, સિસ્ટમને વિનંતી કરો"</string>
+ <string name="car_permission_label_control_evs_activity" msgid="2030069860204405679">"EVS પ્રીવ્યૂ પ્રવૃત્તિ નિયંત્રિત કરો"</string>
+ <string name="car_permission_desc_control_evs_activity" msgid="691646545916976346">"સિસ્ટમની EVS પ્રીવ્યૂ પ્રવૃત્તિ નિયંત્રિત કરો"</string>
+ <string name="car_permission_label_use_evs_camera" msgid="3607720208623955067">"EVS કૅમેરાનો ઉપયોગ કરો"</string>
+ <string name="car_permission_desc_use_evs_camera" msgid="1625845902221003985">"EVS કૅમેરા સ્ટ્રીમને સબ્સ્ક્રાઇબ કરો"</string>
+ <string name="car_permission_label_monitor_evs_status" msgid="2091521314159379622">"EVS સેવાનું સ્ટેટસ મૉનિટર કરો"</string>
+ <string name="car_permission_desc_monitor_evs_status" msgid="2764278897143573535">"EVS સેવાના સ્ટેટસમાં થનારા ફેરફારો સાંભળો"</string>
<string name="car_permission_label_car_engine_detailed" msgid="8911992719173587337">"કારના એન્જિનની વિગતવાર માહિતીને ઍક્સેસ કરો"</string>
<string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"તમારી કારના એન્જિનની વિગતવાર માહિતીને ઍક્સેસ કરો."</string>
<string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"કારના ઇંધણના દરવાજા અને ચાર્જ પોર્ટને ઍક્સેસ કરો"</string>
diff --git a/service/res/values-hi/strings.xml b/service/res/values-hi/strings.xml
index 28a9e89..6ff2cbd 100644
--- a/service/res/values-hi/strings.xml
+++ b/service/res/values-hi/strings.xml
@@ -102,7 +102,7 @@
<string name="car_permission_label_use_evs_camera" msgid="3607720208623955067">"ईवीएस कैमरा इस्तेमाल करें"</string>
<string name="car_permission_desc_use_evs_camera" msgid="1625845902221003985">"ईवीएस कैमरा स्ट्रीम की सदस्यता लें"</string>
<string name="car_permission_label_monitor_evs_status" msgid="2091521314159379622">"ईवीएस सेवा के स्टेटस पर नज़र रखें"</string>
- <string name="car_permission_desc_monitor_evs_status" msgid="2764278897143573535">"ईवीएस सेवा के स्टेटस में हुए बदलावों को सुनें"</string>
+ <string name="car_permission_desc_monitor_evs_status" msgid="2764278897143573535">"ईवीएस सेवा के स्टेटस में हुए बदलावों पर नज़र रखें"</string>
<string name="car_permission_label_car_engine_detailed" msgid="8911992719173587337">"कार के इंजन की जानकारी ऐक्सेस कर सकता है"</string>
<string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"आपकी कार के इंजन की पूरी जानकारी ऐक्सेस कर सकता है."</string>
<string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"कार की ईंधन टंकी का ढक्कन और चार्जिंग पोर्ट ऐक्सेस कर सकता है"</string>
diff --git a/service/res/values-ne/strings.xml b/service/res/values-ne/strings.xml
index 826af97..adb1532 100644
--- a/service/res/values-ne/strings.xml
+++ b/service/res/values-ne/strings.xml
@@ -95,22 +95,14 @@
<string name="car_permission_desc_driving_state" msgid="2684025262811635737">"ड्राइभिङको स्थितिमा हुने परिवर्तनहरू सुन्ने।"</string>
<string name="car_permission_label_use_evs_service" msgid="1729276125209310607">"कार EVS सेवा प्रयोग गर्नुहोस्"</string>
<string name="car_permission_desc_use_evs_service" msgid="2374737642186632816">"EVS भिडियो स्ट्रिमको सदस्यता लिनुहोस्"</string>
- <!-- no translation found for car_permission_label_request_evs_activity (3906551972883482883) -->
- <skip />
- <!-- no translation found for car_permission_desc_request_evs_activity (4582768053649138488) -->
- <skip />
- <!-- no translation found for car_permission_label_control_evs_activity (2030069860204405679) -->
- <skip />
- <!-- no translation found for car_permission_desc_control_evs_activity (691646545916976346) -->
- <skip />
- <!-- no translation found for car_permission_label_use_evs_camera (3607720208623955067) -->
- <skip />
- <!-- no translation found for car_permission_desc_use_evs_camera (1625845902221003985) -->
- <skip />
- <!-- no translation found for car_permission_label_monitor_evs_status (2091521314159379622) -->
- <skip />
- <!-- no translation found for car_permission_desc_monitor_evs_status (2764278897143573535) -->
- <skip />
+ <string name="car_permission_label_request_evs_activity" msgid="3906551972883482883">"EVS प्रिभ्यू गतिविधि अनुरोध गर्ने"</string>
+ <string name="car_permission_desc_request_evs_activity" msgid="4582768053649138488">"प्रणालीलाई EVS प्रिभ्यू गतिविधि सुरु गर्न अनुरोध गर्ने"</string>
+ <string name="car_permission_label_control_evs_activity" msgid="2030069860204405679">"EVS प्रिभ्यू गतिविधि नियन्त्रण गर्ने"</string>
+ <string name="car_permission_desc_control_evs_activity" msgid="691646545916976346">"प्रणालीको EVS प्रिभ्यू गतिविधि नियन्त्रण गर्ने"</string>
+ <string name="car_permission_label_use_evs_camera" msgid="3607720208623955067">"EVS क्यामेरा प्रयोग गर्ने"</string>
+ <string name="car_permission_desc_use_evs_camera" msgid="1625845902221003985">"EVS क्यामेरा स्ट्रिमको सदस्यता लिने"</string>
+ <string name="car_permission_label_monitor_evs_status" msgid="2091521314159379622">"EVS सेवाको स्थिति अनुगमन गर्ने"</string>
+ <string name="car_permission_desc_monitor_evs_status" msgid="2764278897143573535">"EVS सेवाको स्थितिमा भएका परिवर्तनबारे सूचना प्राप्त गर्ने"</string>
<string name="car_permission_label_car_engine_detailed" msgid="8911992719173587337">"कारको इन्जिनको विस्तृत जानकारीमाथि पहुँच राख्ने"</string>
<string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"तपाईंको कारको इन्जिनको विस्तृत जानकारीमाथि पहुँच राख्ने।"</string>
<string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"कारको इन्धन हाल्ने ट्याङ्कीको बिर्को तथा चार्ज गर्ने पोर्टमाथि पहुँच राख्ने"</string>
diff --git a/service/res/values-ur/strings.xml b/service/res/values-ur/strings.xml
index 60b2f7d..3c2ac8e 100644
--- a/service/res/values-ur/strings.xml
+++ b/service/res/values-ur/strings.xml
@@ -95,22 +95,14 @@
<string name="car_permission_desc_driving_state" msgid="2684025262811635737">"کار چلانے کے دوران کی تبدیلیوں کے بارے میں سنیں۔"</string>
<string name="car_permission_label_use_evs_service" msgid="1729276125209310607">"کار کی EVS سروس استعمال کریں"</string>
<string name="car_permission_desc_use_evs_service" msgid="2374737642186632816">"EVS ویڈیو اسٹریمز کو سبسکرائب کریں"</string>
- <!-- no translation found for car_permission_label_request_evs_activity (3906551972883482883) -->
- <skip />
- <!-- no translation found for car_permission_desc_request_evs_activity (4582768053649138488) -->
- <skip />
- <!-- no translation found for car_permission_label_control_evs_activity (2030069860204405679) -->
- <skip />
- <!-- no translation found for car_permission_desc_control_evs_activity (691646545916976346) -->
- <skip />
- <!-- no translation found for car_permission_label_use_evs_camera (3607720208623955067) -->
- <skip />
- <!-- no translation found for car_permission_desc_use_evs_camera (1625845902221003985) -->
- <skip />
- <!-- no translation found for car_permission_label_monitor_evs_status (2091521314159379622) -->
- <skip />
- <!-- no translation found for car_permission_desc_monitor_evs_status (2764278897143573535) -->
- <skip />
+ <string name="car_permission_label_request_evs_activity" msgid="3906551972883482883">"EVS پیش منظر کی سرگرمی کی درخواست کریں"</string>
+ <string name="car_permission_desc_request_evs_activity" msgid="4582768053649138488">"EVS پیش منظر کی سرگرمی کو شروع کرنے کیلئے سسٹم سے درخواست کریں"</string>
+ <string name="car_permission_label_control_evs_activity" msgid="2030069860204405679">"EVS پیش منظر کی سرگرمی کو کنٹرول کریں"</string>
+ <string name="car_permission_desc_control_evs_activity" msgid="691646545916976346">"سسٹم کی EVS پیش منظر کی سرگرمی کنٹرول کریں"</string>
+ <string name="car_permission_label_use_evs_camera" msgid="3607720208623955067">"EVS کیمرا استعمال کریں"</string>
+ <string name="car_permission_desc_use_evs_camera" msgid="1625845902221003985">"EVS کیمرے کی سلسلہ بندیوں کو سبسکرائب کریں"</string>
+ <string name="car_permission_label_monitor_evs_status" msgid="2091521314159379622">"EVS سروس کے اسٹیٹس کو مانیٹر کریں"</string>
+ <string name="car_permission_desc_monitor_evs_status" msgid="2764278897143573535">"EVS سروس کے اسٹیٹس کی تبدیلیوں کو سنیں"</string>
<string name="car_permission_label_car_engine_detailed" msgid="8911992719173587337">"کار کے انجن کی تفصیلات تک رسائی حاصل کریں"</string>
<string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"اپنی کار کے انجن کی تفصیلی معلومات تک رسائی حاصل کریں۔"</string>
<string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"کار کے ایندھن کے دروازے اور چارج پورٹ تک رسائی حاصل کریں"</string>
diff --git a/service/res/values-vi/strings.xml b/service/res/values-vi/strings.xml
index e1a8da9..0851457 100644
--- a/service/res/values-vi/strings.xml
+++ b/service/res/values-vi/strings.xml
@@ -95,22 +95,14 @@
<string name="car_permission_desc_driving_state" msgid="2684025262811635737">"Nghe những thay đổi về trạng thái Lái xe."</string>
<string name="car_permission_label_use_evs_service" msgid="1729276125209310607">"Sử dụng dịch vụ CarEvsService"</string>
<string name="car_permission_desc_use_evs_service" msgid="2374737642186632816">"Theo dõi luồng video của EVS"</string>
- <!-- no translation found for car_permission_label_request_evs_activity (3906551972883482883) -->
- <skip />
- <!-- no translation found for car_permission_desc_request_evs_activity (4582768053649138488) -->
- <skip />
- <!-- no translation found for car_permission_label_control_evs_activity (2030069860204405679) -->
- <skip />
- <!-- no translation found for car_permission_desc_control_evs_activity (691646545916976346) -->
- <skip />
- <!-- no translation found for car_permission_label_use_evs_camera (3607720208623955067) -->
- <skip />
- <!-- no translation found for car_permission_desc_use_evs_camera (1625845902221003985) -->
- <skip />
- <!-- no translation found for car_permission_label_monitor_evs_status (2091521314159379622) -->
- <skip />
- <!-- no translation found for car_permission_desc_monitor_evs_status (2764278897143573535) -->
- <skip />
+ <string name="car_permission_label_request_evs_activity" msgid="3906551972883482883">"Yêu cầu hoạt động xem trước qua EVS"</string>
+ <string name="car_permission_desc_request_evs_activity" msgid="4582768053649138488">"Yêu cầu hệ thống triển khai hoạt động xem trước qua EVS"</string>
+ <string name="car_permission_label_control_evs_activity" msgid="2030069860204405679">"Kiểm soát hoạt động xem trước qua EVS"</string>
+ <string name="car_permission_desc_control_evs_activity" msgid="691646545916976346">"Kiểm soát hoạt động xem trước của hệ thống qua EVS"</string>
+ <string name="car_permission_label_use_evs_camera" msgid="3607720208623955067">"Sử dụng camera qua EVS"</string>
+ <string name="car_permission_desc_use_evs_camera" msgid="1625845902221003985">"Đăng ký xem video của camera phát trực tuyến qua EVS"</string>
+ <string name="car_permission_label_monitor_evs_status" msgid="2091521314159379622">"Theo dõi sự thay đổi trạng thái của dịch vụ EVS"</string>
+ <string name="car_permission_desc_monitor_evs_status" msgid="2764278897143573535">"Theo dõi sự thay đổi trạng thái của dịch vụ EVS"</string>
<string name="car_permission_label_car_engine_detailed" msgid="8911992719173587337">"truy cập vào thông tin chi tiết về động cơ ô tô"</string>
<string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"Truy cập vào thông tin chi tiết về động cơ trên ô tô của bạn."</string>
<string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"truy cập vào cổng sạc và cổng nhiên liệu của ô tô"</string>
diff --git a/service/res/values-zh-rHK/strings.xml b/service/res/values-zh-rHK/strings.xml
index e6144d8..70727f7 100644
--- a/service/res/values-zh-rHK/strings.xml
+++ b/service/res/values-zh-rHK/strings.xml
@@ -95,14 +95,14 @@
<string name="car_permission_desc_driving_state" msgid="2684025262811635737">"聽取駕駛狀態變動。"</string>
<string name="car_permission_label_use_evs_service" msgid="1729276125209310607">"使用 Car EVS Service"</string>
<string name="car_permission_desc_use_evs_service" msgid="2374737642186632816">"訂閱電動車影片串流"</string>
- <string name="car_permission_label_request_evs_activity" msgid="3906551972883482883">"要求電動車預覽活動"</string>
- <string name="car_permission_desc_request_evs_activity" msgid="4582768053649138488">"要求系統啟動電動車預覽活動"</string>
- <string name="car_permission_label_control_evs_activity" msgid="2030069860204405679">"控制電動車預覽活動"</string>
- <string name="car_permission_desc_control_evs_activity" msgid="691646545916976346">"控制系統的電動車預覽活動"</string>
- <string name="car_permission_label_use_evs_camera" msgid="3607720208623955067">"使用電動車攝影機"</string>
- <string name="car_permission_desc_use_evs_camera" msgid="1625845902221003985">"訂閱電動車攝影機串流"</string>
- <string name="car_permission_label_monitor_evs_status" msgid="2091521314159379622">"監察電動車服務的狀態"</string>
- <string name="car_permission_desc_monitor_evs_status" msgid="2764278897143573535">"聽取電動車服務的狀態變更"</string>
+ <string name="car_permission_label_request_evs_activity" msgid="3906551972883482883">"要求 EVS 預覽活動"</string>
+ <string name="car_permission_desc_request_evs_activity" msgid="4582768053649138488">"要求系統啟動 EVS 預覽活動"</string>
+ <string name="car_permission_label_control_evs_activity" msgid="2030069860204405679">"控制 EVS 預覽活動"</string>
+ <string name="car_permission_desc_control_evs_activity" msgid="691646545916976346">"控制系統的 EVS 預覽活動"</string>
+ <string name="car_permission_label_use_evs_camera" msgid="3607720208623955067">"使用 EVS 攝影機"</string>
+ <string name="car_permission_desc_use_evs_camera" msgid="1625845902221003985">"訂閱 EVS 攝影機串流"</string>
+ <string name="car_permission_label_monitor_evs_status" msgid="2091521314159379622">"監察 EVS 服務的狀態"</string>
+ <string name="car_permission_desc_monitor_evs_status" msgid="2764278897143573535">"留意 EVS 服務狀態變更"</string>
<string name="car_permission_label_car_engine_detailed" msgid="8911992719173587337">"存取汽車引擎詳情"</string>
<string name="car_permission_desc_car_engine_detailed" msgid="1746863362811347700">"存取汽車引擎詳情。"</string>
<string name="car_permission_label_car_energy_ports" msgid="8548990315169219454">"存取汽車油箱蓋及充電埠"</string>
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index 4a92f48..fb893c5 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -368,6 +368,7 @@
<item>storage_monitoring</item>
<item>vehicle_map_service</item>
<item>car_evs_service</item>
+ <item>car_telemetry_service</item>
</string-array>
<!-- Configuration to enable passenger support.
diff --git a/service/res/values/strings.xml b/service/res/values/strings.xml
index 1ab9d21..671140e 100644
--- a/service/res/values/strings.xml
+++ b/service/res/values/strings.xml
@@ -175,6 +175,11 @@
<!-- Permission text: apps can listen to driving state changes [CHAR LIMIT=NONE] -->
<string name="car_permission_desc_driving_state">Listen to Driving state changes.</string>
+ <!-- Permission text: apps can collect metrics. [CHAR LIMIT=NONE] -->
+ <string name="car_permission_label_use_telemetry_service">Use Car Telemetry Service</string>
+ <!-- Permission text: apps can collect metrics. [CHAR LIMIT=NONE] -->
+ <string name="car_permission_desc_use_telemetry_service">Collect car system health data.</string>
+
<!-- Permission text: apps can control EVS stream state. [CHAR LIMIT=NONE] -->
<string name="car_permission_label_use_evs_service">Use Car EVS Service</string>
<!-- Permission text: apps can control EVS stream state. [CHAR LIMIT=NONE] -->
diff --git a/service/src/com/android/car/AppFocusService.java b/service/src/com/android/car/AppFocusService.java
index 674d7ff..477529d 100644
--- a/service/src/com/android/car/AppFocusService.java
+++ b/service/src/com/android/car/AppFocusService.java
@@ -208,7 +208,9 @@
// ignore as listener doesn't own focus.
return;
}
- if (mFocusOwners.contains(appType)) {
+ // Because this code will run as part of unit tests on older platform, we can't use
+ // APIs such as {@link SparseArray#contains} that are added with API 30.
+ if (mFocusOwners.indexOfKey(appType) >= 0) {
mFocusOwners.remove(appType);
mActiveAppTypes.remove(appType);
info.removeOwnedAppType(appType);
@@ -278,7 +280,9 @@
*/
public boolean isFocusOwner(int uid, int pid, int appType) {
synchronized (mLock) {
- if (mFocusOwners.contains(appType)) {
+ // Because this code will run as part of unit tests on older platform, we can't use
+ // APIs such as {@link SparseArray#contains} that are added with API 30.
+ if (mFocusOwners.indexOfKey(appType) >= 0) {
OwnershipClientInfo clientInfo = mFocusOwners.get(appType);
return clientInfo.getUid() == uid && clientInfo.getPid() == pid;
}
diff --git a/service/src/com/android/car/CarFeatureController.java b/service/src/com/android/car/CarFeatureController.java
index 38ed695..ef67bb2 100644
--- a/service/src/com/android/car/CarFeatureController.java
+++ b/service/src/com/android/car/CarFeatureController.java
@@ -94,6 +94,7 @@
Car.STORAGE_MONITORING_SERVICE,
Car.VEHICLE_MAP_SERVICE,
Car.CAR_EVS_SERVICE,
+ Car.CAR_TELEMETRY_SERVICE,
// All items below here are deprecated, but still could be supported
Car.CAR_INSTRUMENT_CLUSTER_SERVICE
));
diff --git a/service/src/com/android/car/CarShellCommand.java b/service/src/com/android/car/CarShellCommand.java
index ffa071e..6867ca5 100644
--- a/service/src/com/android/car/CarShellCommand.java
+++ b/service/src/com/android/car/CarShellCommand.java
@@ -236,6 +236,8 @@
PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_SET_GROUP_VOLUME,
PERMISSION_CAR_CONTROL_AUDIO_VOLUME);
+ USER_BUILD_COMMAND_TO_PERMISSION_MAP.put(COMMAND_INJECT_KEY,
+ android.Manifest.permission.INJECT_EVENTS);
}
private static final String PARAM_DAY_MODE = "day";
@@ -1030,9 +1032,14 @@
private void injectKeyEvent(int action, int keyCode, int display) {
long currentTime = SystemClock.uptimeMillis();
if (action == KeyEvent.ACTION_DOWN) mKeyDownTime = currentTime;
- mCarInputService.onKeyEvent(
- new KeyEvent(/* downTime= */ mKeyDownTime, /* eventTime= */ currentTime,
- action, keyCode, /* repeat= */ 0), display);
+ long token = Binder.clearCallingIdentity();
+ try {
+ mCarInputService.injectKeyEvent(
+ new KeyEvent(/* downTime= */ mKeyDownTime, /* eventTime= */ currentTime,
+ action, keyCode, /* repeat= */ 0), display);
+ } finally {
+ Binder.restoreCallingIdentity(token);
+ }
}
private void injectRotary(String[] args, IndentingPrintWriter writer) {
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 4335b3c..52d03fc 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -67,6 +67,7 @@
import com.android.car.power.CarPowerManagementService;
import com.android.car.stats.CarStatsService;
import com.android.car.systeminterface.SystemInterface;
+import com.android.car.telemetry.CarTelemetryService;
import com.android.car.user.CarUserNoticeService;
import com.android.car.user.CarUserService;
import com.android.car.util.LimitedTimingsTraceLog;
@@ -131,6 +132,7 @@
private final CarDevicePolicyService mCarDevicePolicyService;
private final ClusterHomeService mClusterHomeService;
private final CarEvsService mCarEvsService;
+ private final CarTelemetryService mCarTelemetryService;
private final CarServiceBase[] mAllServices;
@@ -348,6 +350,12 @@
mCarEvsService = null;
}
+ if (mFeatureController.isFeatureEnabled(Car.CAR_TELEMETRY_SERVICE)) {
+ mCarTelemetryService = new CarTelemetryService(serviceContext);
+ } else {
+ mCarTelemetryService = null;
+ }
+
// Be careful with order. Service depending on other service should be inited later.
List<CarServiceBase> allServices = new ArrayList<>();
allServices.add(mFeatureController);
@@ -382,6 +390,7 @@
allServices.add(mCarDevicePolicyService);
addServiceIfNonNull(allServices, mClusterHomeService);
addServiceIfNonNull(allServices, mCarEvsService);
+ addServiceIfNonNull(allServices, mCarTelemetryService);
// Always put mCarExperimentalFeatureServiceController in last.
addServiceIfNonNull(allServices, mCarExperimentalFeatureServiceController);
@@ -617,6 +626,8 @@
return mClusterHomeService;
case Car.CAR_EVS_SERVICE:
return mCarEvsService;
+ case Car.CAR_TELEMETRY_SERVICE:
+ return mCarTelemetryService;
default:
IBinder service = null;
if (mCarExperimentalFeatureServiceController != null) {
diff --git a/service/src/com/android/car/admin/CarDevicePolicyService.java b/service/src/com/android/car/admin/CarDevicePolicyService.java
index db7fe83..4425936 100644
--- a/service/src/com/android/car/admin/CarDevicePolicyService.java
+++ b/service/src/com/android/car/admin/CarDevicePolicyService.java
@@ -25,6 +25,8 @@
import android.car.admin.ICarDevicePolicyService;
import android.car.user.UserCreationResult;
import android.car.user.UserRemovalResult;
+import android.car.user.UserStartResult;
+import android.car.user.UserStopResult;
import android.content.pm.UserInfo;
import android.os.UserManager;
import android.sysprop.CarProperties;
@@ -102,6 +104,17 @@
}
@Override
+ public void startUserInBackground(@UserIdInt int userId,
+ AndroidFuture<UserStartResult> receiver) {
+ mCarUserService.startUserInBackground(userId, receiver);
+ }
+
+ @Override
+ public void stopUser(@UserIdInt int userId, AndroidFuture<UserStopResult> receiver) {
+ mCarUserService.stopUser(userId, receiver);
+ }
+
+ @Override
public void dump(@NonNull IndentingPrintWriter writer) {
checkHasDumpPermissionGranted("dump()");
diff --git a/service/src/com/android/car/audio/CarAudioContext.java b/service/src/com/android/car/audio/CarAudioContext.java
index 5c76e17..c93eca3 100644
--- a/service/src/com/android/car/audio/CarAudioContext.java
+++ b/service/src/com/android/car/audio/CarAudioContext.java
@@ -254,6 +254,10 @@
return uniqueContexts;
}
+ static boolean isCriticalAudioContext(@CarAudioContext.AudioContext int audioContext) {
+ return CarAudioContext.EMERGENCY == audioContext || CarAudioContext.SAFETY == audioContext;
+ }
+
static String toString(@AudioContext int audioContext) {
String name = CONTEXT_NAMES.get(audioContext);
if (name != null) {
diff --git a/service/src/com/android/car/audio/CarAudioFocus.java b/service/src/com/android/car/audio/CarAudioFocus.java
index 5b5ac10..0b10e2b 100644
--- a/service/src/com/android/car/audio/CarAudioFocus.java
+++ b/service/src/com/android/car/audio/CarAudioFocus.java
@@ -15,6 +15,8 @@
*/
package com.android.car.audio;
+import static com.android.car.audio.CarAudioContext.isCriticalAudioContext;
+
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioFocusInfo;
@@ -26,7 +28,6 @@
import android.util.Slog;
import com.android.car.CarLog;
-import com.android.car.audio.CarAudioContext.AudioContext;
import com.android.internal.annotations.GuardedBy;
import java.util.ArrayList;
@@ -131,10 +132,6 @@
}
}
- private boolean isCriticalAudioContext(@AudioContext int audioContext) {
- return CarAudioContext.EMERGENCY == audioContext || CarAudioContext.SAFETY == audioContext;
- }
-
// This sends a focus loss message to the targeted requester.
private void sendFocusLossLocked(AudioFocusInfo loser, int lossType) {
int result = mAudioManager.dispatchAudioFocusChange(loser, lossType,
diff --git a/service/src/com/android/car/audio/CarAudioPowerListener.java b/service/src/com/android/car/audio/CarAudioPowerListener.java
index eed53cb..0ee885c 100644
--- a/service/src/com/android/car/audio/CarAudioPowerListener.java
+++ b/service/src/com/android/car/audio/CarAudioPowerListener.java
@@ -78,7 +78,7 @@
void startListeningForPolicyChanges() {
if (mCarPowerManagementService == null) {
Slog.w(TAG, "Cannot find CarPowerManagementService");
- mCarAudioService.enableAudio();
+ mCarAudioService.setAudioEnabled(/* isAudioEnabled= */ true);
return;
}
@@ -100,7 +100,7 @@
if (policy == null) {
Slog.w(TAG, "Policy is null. Defaulting to enabled");
- mCarAudioService.enableAudio();
+ mCarAudioService.setAudioEnabled(/* isAudioEnabled= */ true);
return;
}
@@ -112,11 +112,6 @@
@GuardedBy("mLock")
private void updateAudioPowerStateLocked(CarPowerPolicy policy) {
mIsAudioEnabled = policy.isComponentEnabled(AUDIO);
-
- if (mIsAudioEnabled) {
- mCarAudioService.enableAudio();
- } else {
- mCarAudioService.disableAudio();
- }
+ mCarAudioService.setAudioEnabled(mIsAudioEnabled);
}
}
diff --git a/service/src/com/android/car/audio/CarAudioService.java b/service/src/com/android/car/audio/CarAudioService.java
index 08e648d..1e81c94 100644
--- a/service/src/com/android/car/audio/CarAudioService.java
+++ b/service/src/com/android/car/audio/CarAudioService.java
@@ -1243,22 +1243,16 @@
return getCarAudioZone(zoneId).getInputAudioDevices();
}
- void disableAudio() {
- // TODO(b/176258537) mute everything
+ void setAudioEnabled(boolean isAudioEnabled) {
if (Slogf.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
- Slogf.d(CarLog.TAG_AUDIO, "Disabling audio");
+ Slogf.d(CarLog.TAG_AUDIO, "Setting isAudioEnabled to %b", isAudioEnabled);
}
- mFocusHandler.setRestrictFocus(/* isFocusRestricted= */ true);
- }
-
- void enableAudio() {
- // TODO(b/176258537) unmute appropriate things
- if (Slogf.isLoggable(CarLog.TAG_AUDIO, Log.DEBUG)) {
- Slogf.d(CarLog.TAG_AUDIO, "Enabling audio");
+ mFocusHandler.setRestrictFocus(/* isFocusRestricted= */ !isAudioEnabled);
+ if (mUseCarVolumeGroupMuting) {
+ mCarVolumeGroupMuting.setRestrictMuting(/* isMutingRestricted= */ !isAudioEnabled);
}
-
- mFocusHandler.setRestrictFocus(/* isFocusRestricted= */ false);
+ // TODO(b/176258537) if not using group volume, then set master mute accordingly
}
private void enforcePermission(String permissionName) {
diff --git a/service/src/com/android/car/audio/CarAudioZone.java b/service/src/com/android/car/audio/CarAudioZone.java
index 33d5d40..de540d2 100644
--- a/service/src/com/android/car/audio/CarAudioZone.java
+++ b/service/src/com/android/car/audio/CarAudioZone.java
@@ -115,7 +115,8 @@
*
* Note that it is fine that there are devices which do not appear in any group. Those devices
* may be reserved for other purposes.
- * Step value validation is done in {@link CarVolumeGroup#bind(int, CarAudioDeviceInfo)}
+ * Step value validation is done in
+ * {@link CarVolumeGroup.Builder#setDeviceInfoForContext(int, CarAudioDeviceInfo)}
*/
boolean validateVolumeGroups() {
Set<Integer> contexts = new HashSet<>();
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelper.java b/service/src/com/android/car/audio/CarAudioZonesHelper.java
index cf85356..7956ea2 100644
--- a/service/src/com/android/car/audio/CarAudioZonesHelper.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelper.java
@@ -119,9 +119,10 @@
}
}
- static void bindNonLegacyContexts(CarVolumeGroup group, CarAudioDeviceInfo info) {
+ static void setNonLegacyContexts(CarVolumeGroup.Builder groupBuilder,
+ CarAudioDeviceInfo info) {
for (@AudioContext int audioContext : NON_LEGACY_CONTEXTS) {
- group.bind(audioContext, info);
+ groupBuilder.setDeviceInfoForContext(audioContext, info);
}
}
@@ -401,19 +402,20 @@
private CarVolumeGroup parseVolumeGroup(XmlPullParser parser, int zoneId, int groupId)
throws XmlPullParserException, IOException {
- CarVolumeGroup group =
- new CarVolumeGroup(zoneId, groupId, mCarAudioSettings, mUseCarVolumeGroupMute);
+ CarVolumeGroup.Builder groupBuilder =
+ new CarVolumeGroup.Builder(zoneId, groupId, mCarAudioSettings,
+ mUseCarVolumeGroupMute);
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
if (TAG_AUDIO_DEVICE.equals(parser.getName())) {
String address = parser.getAttributeValue(NAMESPACE, ATTR_DEVICE_ADDRESS);
validateOutputDeviceExist(address);
- parseVolumeGroupContexts(parser, group, address);
+ parseVolumeGroupContexts(parser, groupBuilder, address);
} else {
skip(parser);
}
}
- return group;
+ return groupBuilder.build();
}
private void validateOutputDeviceExist(String address) {
@@ -425,7 +427,7 @@
}
private void parseVolumeGroupContexts(
- XmlPullParser parser, CarVolumeGroup group, String address)
+ XmlPullParser parser, CarVolumeGroup.Builder groupBuilder, String address)
throws XmlPullParserException, IOException {
while (parser.next() != XmlPullParser.END_TAG) {
if (parser.getEventType() != XmlPullParser.START_TAG) continue;
@@ -434,11 +436,11 @@
parser.getAttributeValue(NAMESPACE, ATTR_CONTEXT_NAME));
validateCarAudioContextSupport(carAudioContext);
CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.get(address);
- group.bind(carAudioContext, info);
+ groupBuilder.setDeviceInfoForContext(carAudioContext, info);
// If V1, default new contexts to same device as DEFAULT_AUDIO_USAGE
if (isVersionOne() && carAudioContext == CarAudioService.DEFAULT_AUDIO_CONTEXT) {
- bindNonLegacyContexts(group, info);
+ setNonLegacyContexts(groupBuilder, info);
}
}
// Always skip to upper level since we're at the lowest.
diff --git a/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java b/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
index 42fccf2..173252a 100644
--- a/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
+++ b/service/src/com/android/car/audio/CarAudioZonesHelperLegacy.java
@@ -127,29 +127,15 @@
}
SparseArray<CarAudioZone> loadAudioZones() {
- final CarAudioZone zone = new CarAudioZone(PRIMARY_AUDIO_ZONE,
- "Primary zone");
- for (CarVolumeGroup group : loadVolumeGroups()) {
- zone.addVolumeGroup(group);
- bindContextsForVolumeGroup(group);
+ CarAudioZone zone = new CarAudioZone(PRIMARY_AUDIO_ZONE, "Primary zone");
+ for (CarVolumeGroup volumeGroup : loadVolumeGroups()) {
+ zone.addVolumeGroup(volumeGroup);
}
SparseArray<CarAudioZone> carAudioZones = new SparseArray<>();
carAudioZones.put(PRIMARY_AUDIO_ZONE, zone);
return carAudioZones;
}
- private void bindContextsForVolumeGroup(CarVolumeGroup group) {
- for (int legacyAudioContext : group.getContexts()) {
- int busNumber = mLegacyAudioContextToBus.get(legacyAudioContext);
- CarAudioDeviceInfo info = mBusToCarAudioDeviceInfo.get(busNumber);
- group.bind(legacyAudioContext, info);
-
- if (legacyAudioContext == CarAudioService.DEFAULT_AUDIO_CONTEXT) {
- CarAudioZonesHelper.bindNonLegacyContexts(group, info);
- }
- }
- }
-
/**
* @return all {@link CarVolumeGroup} read from configuration.
*/
@@ -186,8 +172,33 @@
return carVolumeGroups;
}
- private CarVolumeGroup parseVolumeGroup(int id, AttributeSet attrs, XmlResourceParser parser)
- throws XmlPullParserException, IOException {
+ private CarVolumeGroup parseVolumeGroup(int id, AttributeSet attrs,
+ XmlResourceParser parser) throws XmlPullParserException, IOException {
+ CarVolumeGroup.Builder builder = new CarVolumeGroup.Builder(PRIMARY_AUDIO_ZONE, id,
+ mCarAudioSettings, /* useCarVolumeGroupMute= */ false);
+
+ List<Integer> audioContexts = parseAudioContexts(parser, attrs);
+
+ for (int i = 0; i < audioContexts.size(); i++) {
+ bindContextToBuilder(builder, audioContexts.get(i));
+ }
+
+ return builder.build();
+ }
+
+
+ private void bindContextToBuilder(CarVolumeGroup.Builder groupBuilder, int legacyAudioContext) {
+ int busNumber = mLegacyAudioContextToBus.get(legacyAudioContext);
+ CarAudioDeviceInfo info = mBusToCarAudioDeviceInfo.get(busNumber);
+ groupBuilder.setDeviceInfoForContext(legacyAudioContext, info);
+
+ if (legacyAudioContext == CarAudioService.DEFAULT_AUDIO_CONTEXT) {
+ CarAudioZonesHelper.setNonLegacyContexts(groupBuilder, info);
+ }
+ }
+
+ private List<Integer> parseAudioContexts(XmlResourceParser parser, AttributeSet attrs)
+ throws IOException, XmlPullParserException {
List<Integer> contexts = new ArrayList<>();
int type;
int innerDepth = parser.getDepth();
@@ -204,8 +215,7 @@
}
}
- return new CarVolumeGroup(mCarAudioSettings, PRIMARY_AUDIO_ZONE, id,
- contexts.stream().mapToInt(i -> i).filter(i -> i >= 0).toArray());
+ return contexts;
}
/**
diff --git a/service/src/com/android/car/audio/CarVolumeGroup.java b/service/src/com/android/car/audio/CarVolumeGroup.java
index cabce82..4394be0 100644
--- a/service/src/com/android/car/audio/CarVolumeGroup.java
+++ b/service/src/com/android/car/audio/CarVolumeGroup.java
@@ -28,6 +28,7 @@
import com.android.car.CarLog;
import com.android.car.audio.CarAudioContext.AudioContext;
import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
import java.util.ArrayList;
@@ -46,42 +47,50 @@
/* package */ final class CarVolumeGroup {
private final boolean mUseCarVolumeGroupMute;
+ private final boolean mHasCriticalAudioContexts;
private final CarAudioSettings mSettingsManager;
- private final int mZoneId;
+ private final int mDefaultGain;
private final int mId;
- private final SparseArray<String> mContextToAddress = new SparseArray<>();
- private final Map<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo = new HashMap<>();
+ private final int mMaxGain;
+ private final int mMinGain;
+ private final int mStepSize;
+ private final int mZoneId;
+ private final SparseArray<String> mContextToAddress;
+ private final Map<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo;
private final Object mLock = new Object();
- private int mDefaultGain = Integer.MIN_VALUE;
- private int mMaxGain = Integer.MIN_VALUE;
- private int mMinGain = Integer.MAX_VALUE;
- private int mStepSize = 0;
+ @GuardedBy("mLock")
private int mStoredGainIndex;
+ @GuardedBy("mLock")
private int mCurrentGainIndex = -1;
+ @GuardedBy("mLock")
private boolean mIsMuted;
+ @GuardedBy("mLock")
private @UserIdInt int mUserId = UserHandle.USER_CURRENT;
- CarVolumeGroup(int zoneId, int id, CarAudioSettings settings, boolean useCarVolumeGroupMute) {
- mSettingsManager = settings;
+ private CarVolumeGroup(int zoneId, int id, CarAudioSettings settingsManager, int stepSize,
+ int defaultGain, int minGain, int maxGain, SparseArray<String> contextToAddress,
+ Map<String, CarAudioDeviceInfo> addressToCarAudioDeviceInfo,
+ boolean useCarVolumeGroupMute) {
+
+ mSettingsManager = settingsManager;
mZoneId = zoneId;
mId = id;
- mStoredGainIndex = mSettingsManager.getStoredVolumeGainIndexForUser(mUserId, mZoneId, mId);
+ mStepSize = stepSize;
+ mDefaultGain = defaultGain;
+ mMinGain = minGain;
+ mMaxGain = maxGain;
+ mContextToAddress = contextToAddress;
+ mAddressToCarAudioDeviceInfo = addressToCarAudioDeviceInfo;
mUseCarVolumeGroupMute = useCarVolumeGroupMute;
+
+ mHasCriticalAudioContexts = containsCriticalAudioContext(contextToAddress);
}
- /**
- * @deprecated In favor of {@link #CarVolumeGroup(int, int, CarAudioSettings, boolean)}
- * Only used for legacy configuration via IAudioControl@1.0
- */
- @Deprecated
- CarVolumeGroup(CarAudioSettings settings, int zoneId, int id, @NonNull int[] contexts) {
- this(zoneId, id, settings, false);
- // Deal with the pre-populated car audio contexts
- for (int audioContext : contexts) {
- mContextToAddress.put(audioContext, null);
- }
+ void init() {
+ mStoredGainIndex = mSettingsManager.getStoredVolumeGainIndexForUser(mUserId, mZoneId, mId);
+ updateCurrentGainIndexLocked();
}
@Nullable
@@ -89,7 +98,8 @@
return mAddressToCarAudioDeviceInfo.get(address);
}
- @AudioContext int[] getContexts() {
+ @AudioContext
+ int[] getContexts() {
final int[] carAudioContexts = new int[mContextToAddress.size()];
for (int i = 0; i < carAudioContexts.length; i++) {
carAudioContexts[i] = mContextToAddress.keyAt(i);
@@ -106,7 +116,8 @@
return mContextToAddress.get(audioContext);
}
- @AudioContext List<Integer> getContextsForAddress(@NonNull String address) {
+ @AudioContext
+ List<Integer> getContextsForAddress(@NonNull String address) {
List<Integer> carAudioContexts = new ArrayList<>();
for (int i = 0; i < mContextToAddress.size(); i++) {
String value = mContextToAddress.valueAt(i);
@@ -121,57 +132,15 @@
return new ArrayList<>(mAddressToCarAudioDeviceInfo.keySet());
}
- /**
- * Binds the context number to physical address and audio device port information.
- * Because this may change the groups min/max values, thus invalidating an index computed from
- * a gain before this call, all calls to this function must happen at startup before any
- * set/getGainIndex calls.
- *
- * @param carAudioContext Context to bind audio to {@link CarAudioContext}
- * @param info {@link CarAudioDeviceInfo} instance relates to the physical address
- */
- void bind(int carAudioContext, CarAudioDeviceInfo info) {
- Preconditions.checkArgument(mContextToAddress.get(carAudioContext) == null,
- String.format("Context %s has already been bound to %s",
- CarAudioContext.toString(carAudioContext),
- mContextToAddress.get(carAudioContext)));
-
- synchronized (mLock) {
- if (mAddressToCarAudioDeviceInfo.size() == 0) {
- mStepSize = info.getStepValue();
- } else {
- Preconditions.checkArgument(
- info.getStepValue() == mStepSize,
- "Gain controls within one group must have same step value");
- }
-
- mAddressToCarAudioDeviceInfo.put(info.getAddress(), info);
- mContextToAddress.put(carAudioContext, info.getAddress());
-
- if (info.getDefaultGain() > mDefaultGain) {
- // We're arbitrarily selecting the highest
- // device default gain as the group's default.
- mDefaultGain = info.getDefaultGain();
- }
- if (info.getMaxGain() > mMaxGain) {
- mMaxGain = info.getMaxGain();
- }
- if (info.getMinGain() < mMinGain) {
- mMinGain = info.getMinGain();
- }
- updateCurrentGainIndexLocked();
- }
- }
-
int getMaxGainIndex() {
synchronized (mLock) {
- return getIndexForGainLocked(mMaxGain);
+ return getIndexForGain(mMaxGain);
}
}
int getMinGainIndex() {
synchronized (mLock) {
- return getIndexForGainLocked(mMinGain);
+ return getIndexForGain(mMinGain);
}
}
@@ -183,19 +152,13 @@
/**
* Sets the gain on this group, gain will be set on all devices within volume group.
- * @param gainIndex The gain index
*/
void setCurrentGainIndex(int gainIndex) {
+ int gainInMillibels = getGainForIndex(gainIndex);
+ Preconditions.checkArgument(isValidGainIndex(gainIndex),
+ "Gain out of range (%d:%d) %d index %d", mMinGain, mMaxGain,
+ gainInMillibels, gainIndex);
synchronized (mLock) {
- int gainInMillibels = getGainForIndexLocked(gainIndex);
- Preconditions.checkArgument(
- gainInMillibels >= mMinGain && gainInMillibels <= mMaxGain,
- "Gain out of range ("
- + mMinGain + ":"
- + mMaxGain + ") "
- + gainInMillibels + "index "
- + gainIndex);
-
for (String address : mAddressToCarAudioDeviceInfo.keySet()) {
CarAudioDeviceInfo info = mAddressToCarAudioDeviceInfo.get(address);
info.setCurrentGain(gainInMillibels);
@@ -217,6 +180,10 @@
return mAddressToCarAudioDeviceInfo.get(address).getAudioDevicePort();
}
+ boolean hasCriticalAudioContexts() {
+ return mHasCriticalAudioContexts;
+ }
+
@Override
public String toString() {
return "CarVolumeGroup id: " + mId
@@ -229,12 +196,14 @@
synchronized (mLock) {
writer.printf("CarVolumeGroup(%d)\n", mId);
writer.increaseIndent();
+ writer.printf("Zone Id(%b)\n", mZoneId);
writer.printf("Is Muted(%b)\n", mIsMuted);
writer.printf("UserId(%d)\n", mUserId);
writer.printf("Persist Volume Group Mute(%b)\n",
mSettingsManager.isPersistVolumeGroupMuteEnabled(mUserId));
+ writer.printf("Step size: %d\n", mStepSize);
writer.printf("Gain values (min / max / default/ current): %d %d %d %d\n", mMinGain,
- mMaxGain, mDefaultGain, getGainForIndexLocked(mCurrentGainIndex));
+ mMaxGain, mDefaultGain, getGainForIndex(mCurrentGainIndex));
writer.printf("Gain indexes (min / max / default / current): %d %d %d %d\n",
getMinGainIndex(), getMaxGainIndex(), getDefaultGainIndex(), mCurrentGainIndex);
for (int i = 0; i < mContextToAddress.size(); i++) {
@@ -279,6 +248,16 @@
}
}
+ private static boolean containsCriticalAudioContext(SparseArray<String> contextToAddress) {
+ for (int i = 0; i < contextToAddress.size(); i++) {
+ int audioContext = contextToAddress.keyAt(i);
+ if (CarAudioContext.isCriticalAudioContext(audioContext)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
@GuardedBy("mLock")
private void updateUserIdLocked(@UserIdInt int userId) {
mUserId = userId;
@@ -299,21 +278,21 @@
*/
@GuardedBy("mLock")
private void updateCurrentGainIndexLocked() {
- if (isValidGainLocked(mStoredGainIndex)) {
+ if (isValidGainIndex(mStoredGainIndex)) {
mCurrentGainIndex = mStoredGainIndex;
} else {
- mCurrentGainIndex = getIndexForGainLocked(mDefaultGain);
+ mCurrentGainIndex = getIndexForGain(mDefaultGain);
}
}
- @GuardedBy("mLock")
- private boolean isValidGainLocked(int gain) {
- return gain >= getIndexForGainLocked(mMinGain) && gain <= getIndexForGainLocked(mMaxGain);
+ private boolean isValidGainIndex(int gainIndex) {
+ return gainIndex >= getIndexForGain(mMinGain)
+ && gainIndex <= getIndexForGain(mMaxGain);
}
private int getDefaultGainIndex() {
synchronized (mLock) {
- return getIndexForGainLocked(mDefaultGain);
+ return getIndexForGain(mDefaultGain);
}
}
@@ -323,12 +302,11 @@
mZoneId, mId, gainIndex);
}
- private int getGainForIndexLocked(int gainIndex) {
+ private int getGainForIndex(int gainIndex) {
return mMinGain + gainIndex * mStepSize;
}
- @GuardedBy("mLock")
- private int getIndexForGainLocked(int gainInMillibel) {
+ private int getIndexForGain(int gainInMillibel) {
return (gainInMillibel - mMinGain) / mStepSize;
}
@@ -343,4 +321,75 @@
}
mIsMuted = mSettingsManager.getVolumeGroupMuteForUser(mUserId, mZoneId, mId);
}
+
+ static final class Builder {
+ private static final int UNSET_STEP_SIZE = -1;
+
+ private final int mId;
+ private final int mZoneId;
+ private final boolean mUseCarVolumeGroupMute;
+ private final CarAudioSettings mCarAudioSettings;
+ private final SparseArray<String> mContextToAddress = new SparseArray<>();
+ private final Map<String, CarAudioDeviceInfo> mAddressToCarAudioDeviceInfo =
+ new HashMap<>();
+
+ @VisibleForTesting
+ int mStepSize = UNSET_STEP_SIZE;
+ @VisibleForTesting
+ int mDefaultGain = Integer.MIN_VALUE;
+ @VisibleForTesting
+ int mMaxGain = Integer.MIN_VALUE;
+ @VisibleForTesting
+ int mMinGain = Integer.MAX_VALUE;
+
+ Builder(int zoneId, int id, CarAudioSettings carAudioSettings,
+ boolean useCarVolumeGroupMute) {
+ mZoneId = zoneId;
+ mId = id;
+ mCarAudioSettings = carAudioSettings;
+ mUseCarVolumeGroupMute = useCarVolumeGroupMute;
+ }
+
+ Builder setDeviceInfoForContext(int carAudioContext, CarAudioDeviceInfo info) {
+ Preconditions.checkArgument(mContextToAddress.get(carAudioContext) == null,
+ "Context %s has already been set to %s",
+ CarAudioContext.toString(carAudioContext),
+ mContextToAddress.get(carAudioContext));
+
+ if (mAddressToCarAudioDeviceInfo.isEmpty()) {
+ mStepSize = info.getStepValue();
+ } else {
+ Preconditions.checkArgument(
+ info.getStepValue() == mStepSize,
+ "Gain controls within one group must have same step value");
+ }
+
+ mAddressToCarAudioDeviceInfo.put(info.getAddress(), info);
+ mContextToAddress.put(carAudioContext, info.getAddress());
+
+ if (info.getDefaultGain() > mDefaultGain) {
+ // We're arbitrarily selecting the highest
+ // device default gain as the group's default.
+ mDefaultGain = info.getDefaultGain();
+ }
+ if (info.getMaxGain() > mMaxGain) {
+ mMaxGain = info.getMaxGain();
+ }
+ if (info.getMinGain() < mMinGain) {
+ mMinGain = info.getMinGain();
+ }
+
+ return this;
+ }
+
+ CarVolumeGroup build() {
+ Preconditions.checkArgument(mStepSize != UNSET_STEP_SIZE,
+ "setDeviceInfoForContext has to be called at least once before building");
+ CarVolumeGroup group = new CarVolumeGroup(mZoneId, mId, mCarAudioSettings, mStepSize,
+ mDefaultGain, mMinGain, mMaxGain, mContextToAddress,
+ mAddressToCarAudioDeviceInfo, mUseCarVolumeGroupMute);
+ group.init();
+ return group;
+ }
+ }
}
diff --git a/service/src/com/android/car/audio/CarVolumeGroupMuting.java b/service/src/com/android/car/audio/CarVolumeGroupMuting.java
index f6d2ce9..7101127 100644
--- a/service/src/com/android/car/audio/CarVolumeGroupMuting.java
+++ b/service/src/com/android/car/audio/CarVolumeGroupMuting.java
@@ -44,6 +44,8 @@
private final Object mLock = new Object();
@GuardedBy("mLock")
private List<MutingInfo> mLastMutingInformation;
+ @GuardedBy("mLock")
+ private boolean mIsMutingRestricted;
CarVolumeGroupMuting(@NonNull SparseArray<CarAudioZone> carAudioZones,
@NonNull AudioControlWrapper audioControlWrapper) {
@@ -72,11 +74,26 @@
if (Log.isLoggable(TAG, Log.DEBUG)) {
Slog.d(TAG, "carMuteChanged");
}
+
List<MutingInfo> mutingInfo = generateMutingInfo();
setLastMutingInfo(mutingInfo);
mAudioControlWrapper.onDevicesToMuteChange(mutingInfo);
}
+ public void setRestrictMuting(boolean isMutingRestricted) {
+ synchronized (mLock) {
+ mIsMutingRestricted = isMutingRestricted;
+ }
+
+ carMuteChanged();
+ }
+
+ private boolean isMutingRestricted() {
+ synchronized (mLock) {
+ return mIsMutingRestricted;
+ }
+ }
+
private void setLastMutingInfo(List<MutingInfo> mutingInfo) {
synchronized (mLock) {
mLastMutingInformation = mutingInfo;
@@ -92,8 +109,11 @@
private List<MutingInfo> generateMutingInfo() {
List<MutingInfo> mutingInformation = new ArrayList<>(mCarAudioZones.size());
+
+ boolean isMutingRestricted = isMutingRestricted();
for (int index = 0; index < mCarAudioZones.size(); index++) {
- mutingInformation.add(generateMutingInfoFromZone(mCarAudioZones.valueAt(index)));
+ mutingInformation.add(generateMutingInfoFromZone(mCarAudioZones.valueAt(index),
+ isMutingRestricted));
}
return mutingInformation;
@@ -106,6 +126,7 @@
writer.println(TAG);
writer.increaseIndent();
synchronized (mLock) {
+ writer.printf("Is muting restricted? %b\n", mIsMutingRestricted);
for (int index = 0; index < mLastMutingInformation.size(); index++) {
dumpCarMutingInfo(writer, mLastMutingInformation.get(index));
}
@@ -134,7 +155,8 @@
}
@VisibleForTesting
- static MutingInfo generateMutingInfoFromZone(CarAudioZone audioZone) {
+ static MutingInfo generateMutingInfoFromZone(CarAudioZone audioZone,
+ boolean isMutingRestricted) {
MutingInfo mutingInfo = new MutingInfo();
mutingInfo.zoneId = audioZone.getId();
@@ -144,11 +166,12 @@
for (int groupIndex = 0; groupIndex < groups.length; groupIndex++) {
CarVolumeGroup group = groups[groupIndex];
- if (group.isMuted()) {
+
+ if (group.isMuted() || (isMutingRestricted && !group.hasCriticalAudioContexts())) {
mutedDevices.addAll(group.getAddresses());
- continue;
+ } else {
+ unMutedDevices.addAll(group.getAddresses());
}
- unMutedDevices.addAll(group.getAddresses());
}
mutingInfo.deviceAddressesToMute = mutedDevices.toArray(new String[mutedDevices.size()]);
diff --git a/service/src/com/android/car/power/CarPowerManagementService.java b/service/src/com/android/car/power/CarPowerManagementService.java
index 773a626..cdb2670 100644
--- a/service/src/com/android/car/power/CarPowerManagementService.java
+++ b/service/src/com/android/car/power/CarPowerManagementService.java
@@ -54,7 +54,6 @@
import android.os.UserManager;
import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
-import android.util.Slog;
import android.util.SparseArray;
import com.android.car.CarLocalServices;
@@ -75,6 +74,7 @@
import com.android.internal.os.IResultReceiver;
import com.android.internal.util.Preconditions;
import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.server.utils.Slogf;
import java.io.BufferedReader;
import java.io.BufferedWriter;
@@ -217,6 +217,8 @@
private String mCurrentPowerPolicyGroupId;
@GuardedBy("mLock")
private boolean mIsPowerPolicyLocked;
+ @GuardedBy("mLock")
+ private boolean mHasControlOverDaemon;
@GuardedBy("mLock")
@Nullable
@@ -248,7 +250,7 @@
*/
@Override
public void onCallbackDied(T listener) {
- Slog.i(TAG, "binderDied " + listener.asBinder());
+ Slogf.i(TAG, "binderDied %s", listener.asBinder());
mActionOnDeath.take(listener);
}
}
@@ -278,14 +280,18 @@
mSwitchGuestUserBeforeSleep = resources.getBoolean(
R.bool.config_switchGuestUserBeforeGoingSleep);
if (mShutdownPrepareTimeMs < MIN_MAX_GARAGE_MODE_DURATION_MS) {
- Slog.w(TAG,
- "maxGarageModeRunningDurationInSecs smaller than minimum required, resource:"
- + mShutdownPrepareTimeMs + "(ms) while should exceed:"
- + MIN_MAX_GARAGE_MODE_DURATION_MS + "(ms), Ignore resource.");
+ Slogf.w(TAG,
+ "maxGarageModeRunningDurationInSecs smaller than minimum required, "
+ + "resource:%d(ms) while should exceed:%d(ms), Ignore resource.",
+ mShutdownPrepareTimeMs, MIN_MAX_GARAGE_MODE_DURATION_MS);
mShutdownPrepareTimeMs = MIN_MAX_GARAGE_MODE_DURATION_MS;
}
mUserService = carUserService;
mCarPowerPolicyDaemon = powerPolicyDaemon;
+ if (powerPolicyDaemon != null) {
+ // For testing purpose
+ mHasControlOverDaemon = true;
+ }
mWifiManager = context.getSystemService(WifiManager.class);
mWifiStateFile = new AtomicFile(
new File(mSystemInterface.getSystemCarDir(), WIFI_STATE_FILENAME));
@@ -325,7 +331,7 @@
// Initialize CPMS in WAIT_FOR_VHAL state
onApPowerStateChange(CpmsState.WAIT_FOR_VHAL, CarPowerStateListener.WAIT_FOR_VHAL);
} else {
- Slog.w(TAG, "Vehicle hal does not support power state yet.");
+ Slogf.w(TAG, "Vehicle hal does not support power state yet.");
onApPowerStateChange(CpmsState.ON, CarPowerStateListener.ON);
}
mSystemInterface.startDisplayStateMonitoring(this);
@@ -397,7 +403,7 @@
}
handleWaitForVhal(new CpmsState(CpmsState.WAIT_FOR_VHAL,
CarPowerStateListener.WAIT_FOR_VHAL));
- Slog.d(TAG, "setStateForTesting(): mIsBooting is set to false and power state is switched "
+ Slogf.d(TAG, "setStateForTesting(): mIsBooting is set to false and power state is switched "
+ "to Wait For Vhal");
}
@@ -429,10 +435,10 @@
state = mPendingPowerStates.peekFirst();
mPendingPowerStates.clear();
if (state == null) {
- Slog.e(TAG, "Null power state was requested");
+ Slogf.e(TAG, "Null power state was requested");
return;
}
- Slog.i(TAG, "doHandlePowerStateChange: newState=" + state.name());
+ Slogf.i(TAG, "doHandlePowerStateChange: newState=%s", state.name());
if (!needPowerStateChangeLocked(state)) {
return;
}
@@ -441,7 +447,7 @@
mCurrentState = state;
}
mHandler.cancelProcessingComplete();
- Slog.i(TAG, "setCurrentState " + state.toString());
+ Slogf.i(TAG, "setCurrentState %s", state);
CarStatsLogHelper.logPowerState(state.mState);
switch (state.mState) {
case CpmsState.WAIT_FOR_VHAL:
@@ -500,7 +506,8 @@
CarLocalServices.getService(CarUserNoticeService.class);
if (currentUserInfo != null && currentUserInfo.isGuest()
&& carUserNoticeService != null) {
- Slog.i(TAG, "Car user notice service will ignore all messages before user switch.");
+ Slogf.i(TAG, "Car user notice service will ignore all messages before user "
+ + "switch.");
Intent intent = new Intent();
intent.setComponent(new ComponentName(mContext.getPackageName(),
ContinuousBlankActivity.class.getName()));
@@ -509,7 +516,7 @@
carUserNoticeService.ignoreUserNotice(currentUserId);
}
} catch (Exception e) {
- Slog.w(TAG, "Cannot ignore user notice for current user", e);
+ Slogf.w(TAG, e, "Cannot ignore user notice for current user");
}
}
@@ -540,7 +547,7 @@
synchronized (mLock) {
if (mIsBooting) {
- Slog.d(TAG, "handleOn(): called on boot");
+ Slogf.d(TAG, "handleOn(): called on boot");
mIsBooting = false;
return;
}
@@ -549,7 +556,7 @@
try {
mUserService.onResume();
} catch (Exception e) {
- Slog.e(TAG, "Could not switch user on resume", e);
+ Slogf.e(TAG, e, "Could not switch user on resume");
}
}
@@ -561,11 +568,11 @@
}
try {
- Slog.i(TAG, "Factory resetting as it was delayed by user");
+ Slogf.i(TAG, "Factory resetting as it was delayed by user");
callback.send(/* resultCode= */ 0, /* resultData= */ null);
return true;
} catch (Exception e) {
- Slog.wtf(TAG, "Should have factory reset, but failed", e);
+ Slogf.wtf(TAG, e, "Should have factory reset, but failed");
return false;
}
}
@@ -577,8 +584,8 @@
.getDefaultPowerPolicyForState(mCurrentPowerPolicyGroupId, state);
}
if (policy == null && fallbackPolicyId == null) {
- Slog.w(TAG, "No default power policy for " + PolicyReader.powerStateToString(state)
- + " is found");
+ Slogf.w(TAG, "No default power policy for %s is found",
+ PolicyReader.powerStateToString(state));
return;
}
String policyId = policy == null ? fallbackPolicyId : policy.getPolicyId();
@@ -615,7 +622,7 @@
|| !newState.mCanSleep;
mGarageModeShouldExitImmediately = !newState.mCanPostpone;
}
- Slog.i(TAG,
+ Slogf.i(TAG,
(newState.mCanPostpone
? "starting shutdown prepare with Garage Mode"
: "starting shutdown prepare without Garage Mode"));
@@ -627,7 +634,7 @@
// Simulate system shutdown to Deep Sleep
private void simulateShutdownPrepare() {
- Slog.i(TAG, "starting shutdown prepare");
+ Slogf.i(TAG, "starting shutdown prepare");
sendPowerManagerEvent(CarPowerStateListener.SHUTDOWN_PREPARE);
mHal.sendShutdownPrepare();
doHandlePreprocessing();
@@ -666,9 +673,9 @@
if (forceReboot) {
PowerManager powerManager = mContext.getSystemService(PowerManager.class);
if (powerManager == null) {
- Slog.wtf(TAG, "No PowerManager. Cannot reboot.");
+ Slogf.wtf(TAG, "No PowerManager. Cannot reboot.");
} else {
- Slog.i(TAG, "GarageMode has completed. Forcing reboot.");
+ Slogf.i(TAG, "GarageMode has completed. Forcing reboot.");
powerManager.reboot("GarageModeReboot");
throw new AssertionError("Should not return from PowerManager.reboot()");
}
@@ -689,7 +696,7 @@
boolean needToRestore = readWifiModifiedState();
if (needToRestore) {
if (!mWifiManager.isWifiEnabled()) {
- Slog.i(TAG, "Wifi has been enabled to restore the last setting");
+ Slogf.i(TAG, "Wifi has been enabled to restore the last setting");
mWifiManager.setWifiEnabled(true);
}
// Update the persistent data as wifi is not modified by car framework.
@@ -706,7 +713,7 @@
if (!wifiEnabled) return;
mWifiManager.setWifiEnabled(false);
- Slog.i(TAG, "Wifi has been disabled and the last setting was saved");
+ Slogf.i(TAG, "Wifi has been disabled and the last setting was saved");
}
private void saveWifiModifiedState(boolean forciblyDisabled) {
@@ -714,7 +721,7 @@
try {
fos = mWifiStateFile.startWrite();
} catch (IOException e) {
- Slog.e(TAG, "Cannot create " + WIFI_STATE_FILENAME, e);
+ Slogf.e(TAG, e, "Cannot create %s", WIFI_STATE_FILENAME);
return;
}
@@ -726,7 +733,7 @@
mWifiStateFile.finishWrite(fos);
} catch (IOException e) {
mWifiStateFile.failWrite(fos);
- Slog.e(TAG, "Writing " + WIFI_STATE_FILENAME + " failed", e);
+ Slogf.e(TAG, e, "Writing %s failed", WIFI_STATE_FILENAME);
}
}
@@ -747,7 +754,7 @@
}
} catch (IOException e) {
// If a file named wifi_state doesn't exist, we will not modify Wifi at system start.
- Slog.w(TAG, "Failed to read " + WIFI_STATE_FILENAME + ": " + e);
+ Slogf.w(TAG, "Failed to read %s: %s", WIFI_STATE_FILENAME, e);
return false;
}
if (invalidState) {
@@ -780,21 +787,30 @@
pollingCount =
(shutdownPrepareTimeOverrideInSecs * 1000 / intervalMs)
+ 1;
- Slog.i(TAG, "Garage mode duration overridden secs:"
- + shutdownPrepareTimeOverrideInSecs);
+ Slogf.i(TAG, "Garage mode duration overridden secs: %d",
+ shutdownPrepareTimeOverrideInSecs);
}
}
- Slog.i(TAG, "processing before shutdown expected for: "
- + mShutdownPrepareTimeMs + " ms, adding polling:" + pollingCount);
+ Slogf.i(TAG, "processing before shutdown expected for: %dms, adding polling:%d",
+ mShutdownPrepareTimeMs, pollingCount);
+ boolean allAreComplete;
synchronized (mLock) {
mProcessingStartTime = SystemClock.elapsedRealtime();
releaseTimerLocked();
- mTimer = new Timer();
- mTimerActive = true;
- mTimer.scheduleAtFixedRate(
- new ShutdownProcessingTimerTask(pollingCount),
- 0 /*delay*/,
- intervalMs);
+ allAreComplete = mListenersWeAreWaitingFor.isEmpty();
+ if (allAreComplete) {
+ Slogf.i(TAG, "Listener queue is empty, don't start polling");
+ } else {
+ mTimer = new Timer();
+ mTimerActive = true;
+ mTimer.scheduleAtFixedRate(
+ new ShutdownProcessingTimerTask(pollingCount),
+ /* delay= */ 0,
+ intervalMs);
+ }
+ }
+ if (allAreComplete) {
+ signalComplete();
}
// allowUserSwitch value doesn't matter for onSuspend = true
mUserService.onSuspend();
@@ -847,7 +863,7 @@
listener.onStateChanged(newState);
} catch (RemoteException e) {
// It's likely the connection snapped. Let binder death handle the situation.
- Slog.e(TAG, "onStateChanged() call failed", e);
+ Slogf.e(TAG, e, "onStateChanged() call failed");
}
}
listenerList.finishBroadcast();
@@ -879,7 +895,7 @@
// Any wakeup time from before is no longer valid.
mNextWakeupSec = 0;
}
- Slog.i(TAG, "Resuming after suspending");
+ Slogf.i(TAG, "Resuming after suspending");
mSystemInterface.refreshDisplayBrightness();
onApPowerStateChange(CpmsState.WAIT_FOR_VHAL, nextListenerState);
}
@@ -888,7 +904,7 @@
if (mCurrentState == null) {
return true;
} else if (mCurrentState.equals(newState)) {
- Slog.d(TAG, "Requested state is already in effect: " + newState.name());
+ Slogf.d(TAG, "Requested state is already in effect: %s", newState.name());
return false;
}
@@ -922,13 +938,13 @@
|| newState.mState == CpmsState.WAIT_FOR_VHAL);
break;
default:
- Slog.e(TAG, "Unexpected current state: currentState="
- + mCurrentState.name() + ", newState=" + newState.name());
+ Slogf.e(TAG, "Unexpected current state: currentState=%s, newState=%s",
+ mCurrentState.name(), newState.name());
transitionAllowed = true;
}
if (!transitionAllowed) {
- Slog.e(TAG, "Requested power transition is not allowed: "
- + mCurrentState.name() + " --> " + newState.name());
+ Slogf.e(TAG, "Requested power transition is not allowed: %s --> %s",
+ mCurrentState.name(), newState.name());
}
return transitionAllowed;
}
@@ -939,7 +955,7 @@
releaseTimerLocked();
if (!mShutdownOnFinish && mLastSleepEntryTime > mProcessingStartTime) {
// entered sleep after processing start. So this could be duplicate request.
- Slog.w(TAG, "Duplicate sleep entry request, ignore");
+ Slogf.w(TAG, "Duplicate sleep entry request, ignore");
return;
}
listenerState = mShutdownOnFinish
@@ -959,7 +975,7 @@
}
private void doHandleMainDisplayStateChange(boolean on) {
- Slog.w(TAG, "Unimplemented: doHandleMainDisplayStateChange() - on = " + on);
+ Slogf.w(TAG, "Unimplemented: doHandleMainDisplayStateChange() - on = %b", on);
}
/**
@@ -1030,13 +1046,13 @@
public void scheduleNextWakeupTime(int seconds) {
ICarImpl.assertPermission(mContext, Car.PERMISSION_CAR_POWER);
if (seconds < 0) {
- Slog.w(TAG, "Next wake up time is negative. Ignoring!");
+ Slogf.w(TAG, "Next wake up time is negative. Ignoring!");
return;
}
boolean timedWakeupAllowed = mHal.isTimedWakeupAllowed();
synchronized (mLock) {
if (!timedWakeupAllowed) {
- Slog.w(TAG, "Setting timed wakeups are disabled in HAL. Skipping");
+ Slogf.w(TAG, "Setting timed wakeups are disabled in HAL. Skipping");
mNextWakeupSec = 0;
return;
}
@@ -1044,7 +1060,7 @@
// The new value is sooner than the old value. Take the new value.
mNextWakeupSec = seconds;
} else {
- Slog.d(TAG, "Tried to schedule next wake up, but already had shorter "
+ Slogf.d(TAG, "Tried to schedule next wake up, but already had shorter "
+ "scheduled time");
}
}
@@ -1116,7 +1132,7 @@
}
void notifySilentModeChange(boolean silent) {
- Slog.i(TAG, "Silent mode is set to " + silent);
+ Slogf.i(TAG, "Silent mode is set to %b", silent);
mSilentModeHandler.updateKernelSilentMode(silent);
if (silent) {
applyPreemptivePowerPolicy(PolicyReader.POWER_POLICY_ID_NO_USER_INTERACTION);
@@ -1154,48 +1170,59 @@
if (!mShutdownOnFinish) {
if (mLastSleepEntryTime > mProcessingStartTime
&& mLastSleepEntryTime < SystemClock.elapsedRealtime()) {
- Slog.i(TAG, "signalComplete: Already slept!");
+ Slogf.i(TAG, "signalComplete: Already slept!");
return;
}
}
powerHandler = mHandler;
}
- Slog.i(TAG, "Apps are finished, call handleProcessingComplete()");
+ Slogf.i(TAG, "Apps are finished, call handleProcessingComplete()");
powerHandler.handleProcessingComplete();
}
}
private void initializePowerPolicy() {
- Slog.i(TAG, "CPMS is taking control from carpowerpolicyd");
+ Slogf.i(TAG, "CPMS is taking control from carpowerpolicyd");
ICarPowerPolicySystemNotification daemon;
- String currentPowerPolicyId;
synchronized (mLock) {
daemon = mCarPowerPolicyDaemon;
- currentPowerPolicyId = mCurrentPowerPolicyId;
}
+ PolicyState state;
if (daemon != null) {
- PolicyState state;
try {
state = daemon.notifyCarServiceReady();
- String errMsg = setCurrentPowerPolicyGroup(state.policyGroupId);
- if (errMsg != null) {
- Slog.w(TAG, errMsg);
- }
} catch (RemoteException e) {
- Slog.e(TAG, "Failed to tell car power policy daemon that CarService is ready", e);
+ Slogf.e(TAG, e, "Failed to tell car power policy daemon that CarService is ready");
return;
}
- if (currentPowerPolicyId == null || currentPowerPolicyId.isEmpty()) {
- String errorMsg = applyPowerPolicy(state.policyId, false);
- if (errorMsg != null) {
- Slog.w(TAG, "Cannot apply power policy: " + errorMsg);
- }
- } else if (state.policyId.equals(currentPowerPolicyId)) {
- notifyPowerPolicyChangeToDaemon(currentPowerPolicyId);
- }
} else {
- Slog.w(TAG, "Failed to notify car service is ready. car power policy daemon is not "
+ Slogf.w(TAG, "Failed to notify car service is ready. car power policy daemon is not "
+ "available");
+ return;
+ }
+
+ String currentPowerPolicyId;
+ String currentPolicyGroupId;
+ synchronized (mLock) {
+ mHasControlOverDaemon = true;
+ currentPowerPolicyId = mCurrentPowerPolicyId;
+ currentPolicyGroupId = mCurrentPowerPolicyGroupId;
+ }
+ // In case where CPMS received power state change before taking control from the daemon.
+ notifyPowerPolicyChangeToDaemon(currentPowerPolicyId);
+ // If the current power policy or the policy group has been modified by CPMS, we ignore
+ // the power policy or the policy group passed from car power policy daemon.
+ if (currentPowerPolicyId == null || currentPowerPolicyId.isEmpty()) {
+ String errorMsg = applyPowerPolicy(state.policyId, false);
+ if (errorMsg != null) {
+ Slogf.w(TAG, "Cannot apply power policy: %s", errorMsg);
+ }
+ }
+ if (currentPolicyGroupId == null || currentPolicyGroupId.isEmpty()) {
+ String errMsg = setCurrentPowerPolicyGroup(state.policyGroupId);
+ if (errMsg != null) {
+ Slogf.w(TAG, "Cannot set power policy group: %s", errMsg);
+ }
}
mSilentModeHandler.init();
}
@@ -1218,8 +1245,8 @@
}
synchronized (mLock) {
if (mIsPowerPolicyLocked) {
- Slog.i(TAG, "Power policy is locked. The request policy(" + policyId
- + ") will be applied when power policy becomes unlocked");
+ Slogf.i(TAG, "Power policy is locked. The request policy(%s) will be applied when "
+ + "power policy becomes unlocked", policyId);
mPendingPowerPolicyId = policyId;
return null;
}
@@ -1227,7 +1254,7 @@
}
mPowerComponentHandler.applyPowerPolicy(policy);
notifyPowerPolicyChange(policyId, upToDaemon);
- Slog.i(TAG, "The current power policy is " + policyId);
+ Slogf.i(TAG, "The current power policy is %s", policyId);
return null;
}
@@ -1245,7 +1272,7 @@
}
mPowerComponentHandler.applyPowerPolicy(policy);
notifyPowerPolicyChange(policyId, true);
- Slog.i(TAG, "The current power policy is " + policyId);
+ Slogf.i(TAG, "The current power policy is %s", policyId);
return null;
}
@@ -1253,7 +1280,7 @@
String policyId;
synchronized (mLock) {
if (!mIsPowerPolicyLocked) {
- Slog.w(TAG, "Failed to cancel system power policy: the current policy is not the "
+ Slogf.w(TAG, "Failed to cancel system power policy: the current policy is not the "
+ "system power policy");
return;
}
@@ -1262,24 +1289,29 @@
}
String errMsg = applyPowerPolicy(policyId, true);
if (errMsg != null) {
- Slog.w(TAG, "Failed to cancel system power policy: " + errMsg);
+ Slogf.w(TAG, "Failed to cancel system power policy: %s", errMsg);
}
}
private void notifyPowerPolicyChangeToDaemon(String policyId) {
ICarPowerPolicySystemNotification daemon;
+ boolean hadPendingPolicyNotification;
synchronized (mLock) {
daemon = mCarPowerPolicyDaemon;
- }
- if (daemon == null) {
- Slog.e(TAG, "Failed to notify car power policy daemon: the daemon is not ready");
- return;
+ if (daemon == null) {
+ Slogf.e(TAG, "Failed to notify car power policy daemon: the daemon is not ready");
+ return;
+ }
+ if (!mHasControlOverDaemon) {
+ Slogf.w(TAG, "Notifying policy change is deferred: CPMS has not yet taken control");
+ return;
+ }
}
try {
daemon.notifyPowerPolicyChange(policyId);
- } catch (RemoteException e) {
- Slog.e(TAG, "Failed to notify car power policy daemon of a new power policy("
- + policyId + ")", e);
+ } catch (RemoteException | IllegalStateException e) {
+ Slogf.e(TAG, e, "Failed to notify car power policy daemon of a new power policy(%s)",
+ policyId);
}
}
@@ -1295,7 +1327,7 @@
? mPolicyReader.getPreemptivePowerPolicy(policyId)
: mPolicyReader.getPowerPolicy(policyId);
if (appliedPolicy == null) {
- Slog.wtf(TAG, "The new power policy(" + policyId + ") should exist");
+ Slogf.wtf(TAG, "The new power policy(%s) should exist", policyId);
}
int idx = mPowerPolicyListeners.beginBroadcast();
while (idx-- > 0) {
@@ -1309,7 +1341,7 @@
listener.onPolicyChanged(appliedPolicy, accumulatedPolicy);
} catch (RemoteException e) {
// It's likely the connection snapped. Let binder death handle the situation.
- Slog.e(TAG, "onPolicyChanged() call failed: policyId = " + policyId, e);
+ Slogf.e(TAG, e, "onPolicyChanged() call failed: policyId = %s", policyId);
}
}
mPowerPolicyListeners.finishBroadcast();
@@ -1320,7 +1352,7 @@
String errMsg = applyPreemptivePowerPolicy(
PolicyReader.POWER_POLICY_ID_NO_USER_INTERACTION);
if (errMsg != null) {
- Slog.w(TAG, errMsg);
+ Slogf.w(TAG, errMsg);
}
}
@@ -1339,12 +1371,12 @@
synchronized (mLock) {
mConnectionInProgress = false;
}
- Slog.e(TAG, "Cannot reconnect to car power policyd daemon after retrying "
- + CAR_POWER_POLICY_DAEMON_BIND_MAX_RETRY + " times");
+ Slogf.e(TAG, "Cannot reconnect to car power policyd daemon after retrying %d times",
+ CAR_POWER_POLICY_DAEMON_BIND_MAX_RETRY);
return;
}
if (makeBinderConnection()) {
- Slog.i(TAG, "Connected to car power policy daemon");
+ Slogf.i(TAG, "Connected to car power policy daemon");
initializePowerPolicy();
return;
}
@@ -1358,20 +1390,20 @@
long currentTimeMs = SystemClock.uptimeMillis();
IBinder binder = ServiceManager.getService(CAR_POWER_POLICY_DAEMON_INTERFACE);
if (binder == null) {
- Slog.w(TAG, "Finding car power policy daemon failed. Power policy management is not "
+ Slogf.w(TAG, "Finding car power policy daemon failed. Power policy management is not "
+ "supported");
return false;
}
long elapsedTimeMs = SystemClock.uptimeMillis() - currentTimeMs;
if (elapsedTimeMs > CAR_POWER_POLICY_DAEMON_FIND_MARGINAL_TIME_MS) {
- Slog.wtf(TAG, "Finding car power policy daemon took too long(" + elapsedTimeMs + "ms)");
+ Slogf.wtf(TAG, "Finding car power policy daemon took too long(%dms)", elapsedTimeMs);
}
ICarPowerPolicySystemNotification daemon =
ICarPowerPolicySystemNotification.Stub.asInterface(binder);
if (daemon == null) {
- Slog.w(TAG, "Getting car power policy daemon interface failed. Power policy management "
- + "is not supported");
+ Slogf.w(TAG, "Getting car power policy daemon interface failed. Power policy management"
+ + " is not supported");
return false;
}
synchronized (mLock) {
@@ -1392,11 +1424,12 @@
@Override
public void binderDied() {
- Slog.w(TAG, "Car power policy daemon died: reconnecting");
+ Slogf.w(TAG, "Car power policy daemon died: reconnecting");
unlinkToDeath();
mDaemon = null;
synchronized (mLock) {
mCarPowerPolicyDaemon = null;
+ mHasControlOverDaemon = false;
}
mHandler.sendMessageDelayed(PooledLambda.obtainMessage(
CarPowerManagementService::connectToDaemonHelper,
@@ -1410,14 +1443,14 @@
}
IBinder binder = mDaemon.asBinder();
if (binder == null) {
- Slog.w(TAG, "Linking to binder death recipient skipped");
+ Slogf.w(TAG, "Linking to binder death recipient skipped");
return;
}
try {
binder.linkToDeath(this, 0);
} catch (RemoteException e) {
mDaemon = null;
- Slog.w(TAG, "Linking to binder death recipient failed: " + e);
+ Slogf.w(TAG, e, "Linking to binder death recipient failed: %s");
}
}
@@ -1427,7 +1460,7 @@
}
IBinder binder = mDaemon.asBinder();
if (binder == null) {
- Slog.w(TAG, "Unlinking from binder death recipient skipped");
+ Slogf.w(TAG, "Unlinking from binder death recipient skipped");
return;
}
binder.unlinkToDeath(this, 0);
@@ -1489,7 +1522,7 @@
public void handleMessage(Message msg) {
CarPowerManagementService service = mService.get();
if (service == null) {
- Slog.i(TAG, "handleMessage null service");
+ Slogf.i(TAG, "handleMessage null service");
return;
}
switch (msg.what) {
@@ -1548,7 +1581,7 @@
long totalWaitDurationMs = 0;
while (true) {
- Slog.i(TAG, "Entering Suspend to RAM");
+ Slogf.i(TAG, "Entering Suspend to RAM");
boolean suspendSucceeded = mSystemInterface.enterDeepSleep();
if (suspendSucceeded) {
return true;
@@ -1559,7 +1592,7 @@
// We failed to suspend. Block the thread briefly and try again.
synchronized (mLock) {
if (mPendingPowerStates.isEmpty()) {
- Slog.w(TAG, "Failed to Suspend; will retry after " + retryIntervalMs + "ms.");
+ Slogf.w(TAG, "Failed to Suspend; will retry after %dms", retryIntervalMs);
try {
mLock.wait(retryIntervalMs);
} catch (InterruptedException ignored) {
@@ -1570,14 +1603,14 @@
}
// Check for a new power state now, before going around the loop again
if (!mPendingPowerStates.isEmpty()) {
- Slog.i(TAG, "Terminating the attempt to Suspend to RAM");
+ Slogf.i(TAG, "Terminating the attempt to Suspend to RAM");
return false;
}
}
}
// Too many failures trying to suspend. Shut down.
- Slog.w(TAG, "Could not Suspend to RAM after " + totalWaitDurationMs
- + "ms long trial. Shutting down.");
+ Slogf.w(TAG, "Could not Suspend to RAM after %dms long trial. Shutting down.",
+ totalWaitDurationMs);
mSystemInterface.shutdown();
return false;
}
@@ -1951,7 +1984,7 @@
// We simulate this behavior by calling wait().
// We continue from wait() when forceSimulatedResume() is called.
private void simulateSleepByWaiting() {
- Slog.i(TAG, "Starting to simulate Deep Sleep by waiting");
+ Slogf.i(TAG, "Starting to simulate Deep Sleep by waiting");
synchronized (mSimulationWaitObject) {
while (!mWakeFromSimulatedSleep) {
try {
@@ -1962,7 +1995,7 @@
}
mInSimulatedDeepSleepMode = false;
}
- Slog.i(TAG, "Exit Deep Sleep simulation");
+ Slogf.i(TAG, "Exit Deep Sleep simulation");
}
private int getMaxSuspendWaitDurationConfig() {
diff --git a/service/src/com/android/car/power/PolicyReader.java b/service/src/com/android/car/power/PolicyReader.java
index 61e63d5..ca36b1d 100644
--- a/service/src/com/android/car/power/PolicyReader.java
+++ b/service/src/com/android/car/power/PolicyReader.java
@@ -34,7 +34,6 @@
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
-import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.Xml;
@@ -42,6 +41,7 @@
import com.android.car.CarLog;
import com.android.car.CarServiceUtils;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.utils.Slogf;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
@@ -307,8 +307,8 @@
try (InputStream inputStream = new FileInputStream(VENDOR_POLICY_PATH)) {
readPowerPolicyFromXml(inputStream);
} catch (IOException | XmlPullParserException | PolicyXmlException e) {
- Slog.w(TAG, "Proceed without registered policies: failed to parse "
- + VENDOR_POLICY_PATH + ": " + e);
+ Slogf.w(TAG, "Proceed without registered policies: failed to parse %s: %s",
+ VENDOR_POLICY_PATH, e);
}
}
diff --git a/service/src/com/android/car/power/PowerComponentHandler.java b/service/src/com/android/car/power/PowerComponentHandler.java
index c73696e..573d62e 100644
--- a/service/src/com/android/car/power/PowerComponentHandler.java
+++ b/service/src/com/android/car/power/PowerComponentHandler.java
@@ -45,8 +45,6 @@
import android.os.ServiceManager;
import android.util.AtomicFile;
import android.util.IndentingPrintWriter;
-import android.util.Log;
-import android.util.Slog;
import android.util.SparseArray;
import android.util.SparseBooleanArray;
@@ -55,6 +53,7 @@
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IVoiceInteractionManagerService;
+import com.android.server.utils.Slogf;
import java.io.BufferedReader;
import java.io.BufferedWriter;
@@ -122,12 +121,12 @@
PowerComponentMediator mediator = factory.createPowerComponent(component);
String componentName = powerComponentToString(component);
if (mediator == null) {
- Slog.w(TAG, "Power component(" + componentName + ") is not valid or doesn't "
- + "need a mediator");
+ Slogf.w(TAG, "Power component(%s) is not valid or doesn't need a mediator",
+ componentName);
continue;
}
if (!mediator.isComponentAvailable()) {
- Slog.w(TAG, "Power component(" + componentName + ") is not available");
+ Slogf.w(TAG, "Power component(%s) is not available", componentName);
continue;
}
mediator.init();
@@ -258,7 +257,7 @@
}
PowerComponentMediator mediator = mPowerComponentMediators.get(component);
if (mediator == null) {
- Slog.w(TAG, powerComponentToString(component) + " doesn't have a mediator");
+ Slogf.w(TAG, "%s doesn't have a mediator", powerComponentToString(component));
return modified;
}
if (modified && mediator.isControlledBySystem()) {
@@ -286,7 +285,7 @@
// Behave as if there are no forced-off components.
return;
} catch (IOException e) {
- Slog.w(TAG, "Failed to read " + FORCED_OFF_COMPONENTS_FILENAME + ": " + e);
+ Slogf.w(TAG, "Failed to read %s: %s", FORCED_OFF_COMPONENTS_FILENAME, e);
return;
}
if (invalid) {
@@ -299,7 +298,7 @@
try {
fos = mOffComponentsByUserFile.startWrite();
} catch (IOException e) {
- Slog.e(TAG, "Cannot create " + FORCED_OFF_COMPONENTS_FILENAME, e);
+ Slogf.e(TAG, e, "Cannot create %s", FORCED_OFF_COMPONENTS_FILENAME);
return;
}
@@ -316,7 +315,7 @@
mOffComponentsByUserFile.finishWrite(fos);
} catch (IOException e) {
mOffComponentsByUserFile.failWrite(fos);
- Slog.e(TAG, "Writing " + FORCED_OFF_COMPONENTS_FILENAME + " failed", e);
+ Slogf.e(TAG, e, "Writing %s failed", FORCED_OFF_COMPONENTS_FILENAME);
}
}
@@ -340,14 +339,6 @@
}
}
- private void logd(String messageFormat, Object... args) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- // TODO(b/182476140): Replace with formatted Slog.
- String message = String.format(messageFormat, args);
- Slog.d(TAG, message);
- }
- }
-
abstract static class PowerComponentMediator {
protected int mComponentId;
@@ -390,7 +381,7 @@
@Override
public void setEnabled(boolean enabled) {
mSystemInterface.setDisplayState(enabled);
- logd("Display power component is %s", enabled ? "on" : "off");
+ Slogf.d(TAG, "Display power component is %s", enabled ? "on" : "off");
}
}
@@ -442,7 +433,7 @@
@Override
public void setEnabled(boolean enabled) {
mWifiManager.setWifiEnabled(enabled);
- logd("Wifi power component is %s", enabled ? "on" : "off");
+ Slogf.d(TAG, "Wifi power component is %s", enabled ? "on" : "off");
}
}
@@ -475,10 +466,9 @@
try {
mVoiceInteractionManagerService.setDisabled(!enabled);
mIsEnabled = enabled;
- logd("Voice Interaction power component is %s", enabled ? "on" : "off");
+ Slogf.d(TAG, "Voice Interaction power component is %s", enabled ? "on" : "off");
} catch (RemoteException e) {
- Slog.w(TAG, "IVoiceInteractionManagerService.setDisabled(" + !enabled + ") failed",
- e);
+ Slogf.w(TAG, e, "IVoiceInteractionManagerService.setDisabled(%b) failed", !enabled);
}
}
}
@@ -531,7 +521,7 @@
@Override
public void setEnabled(boolean enabled) {
// No op
- Slog.w(TAG, "Bluetooth power is controlled by "
+ Slogf.w(TAG, "Bluetooth power is controlled by "
+ "com.android.car.BluetoothDeviceConnectionPolicy");
}
}
@@ -584,7 +574,7 @@
@Override
public void setEnabled(boolean enabled) {
// No op
- Slog.w(TAG, "NFC power isn't controlled by CPMS");
+ Slogf.w(TAG, "NFC power isn't controlled by CPMS");
}
}
@@ -636,7 +626,7 @@
@Override
public void setEnabled(boolean enabled) {
// No op
- Slog.w(TAG, "Location power isn controlled by GNSS HAL");
+ Slogf.w(TAG, "Location power isn controlled by GNSS HAL");
}
}
@@ -689,7 +679,7 @@
case PowerComponent.CPU:
return null;
default:
- Slog.w(TAG, "Unknown component(" + component + ")");
+ Slogf.w(TAG, "Unknown component(%d)", component);
return null;
}
}
diff --git a/service/src/com/android/car/power/SilentModeHandler.java b/service/src/com/android/car/power/SilentModeHandler.java
index e12978a..2c557f8 100644
--- a/service/src/com/android/car/power/SilentModeHandler.java
+++ b/service/src/com/android/car/power/SilentModeHandler.java
@@ -21,11 +21,11 @@
import android.os.FileObserver;
import android.os.SystemProperties;
import android.util.IndentingPrintWriter;
-import android.util.Slog;
import com.android.car.CarLog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.utils.Slogf;
import libcore.io.IoUtils;
@@ -89,12 +89,12 @@
}
switch (bootReason) {
case FORCED_SILENT:
- Slog.i(TAG, "Starting in forced silent mode");
+ Slogf.i(TAG, "Starting in forced silent mode");
mForcedMode = true;
mSilentModeByHwState = true;
break;
case FORCED_NON_SILENT:
- Slog.i(TAG, "Starting in forced non-silent mode");
+ Slogf.i(TAG, "Starting in forced non-silent mode");
mForcedMode = true;
mSilentModeByHwState = false;
break;
@@ -110,8 +110,8 @@
}
if (forcedMode) {
updateKernelSilentMode(mSilentModeByHwState);
- Slog.i(TAG, "Now in forced mode: monitoring " + mHwStateMonitoringFileName
- + " is disabled");
+ Slogf.i(TAG, "Now in forced mode: monitoring %s is disabled",
+ mHwStateMonitoringFileName);
} else {
startMonitoringSilentModeHwState();
}
@@ -153,10 +153,10 @@
String value = silent ? VALUE_SILENT_MODE : VALUE_NON_SILENT_MODE;
writer.write(value);
writer.flush();
- Slog.i(TAG, mKernelSilentModeFileName + " is updated to " + value);
+ Slogf.i(TAG, "%s is updated to %s", mKernelSilentModeFileName, value);
} catch (IOException e) {
- Slog.w(TAG, "Failed to update " + mKernelSilentModeFileName + " to "
- + (silent ? VALUE_SILENT_MODE : VALUE_NON_SILENT_MODE));
+ Slogf.w(TAG, "Failed to update %s to %s", mKernelSilentModeFileName,
+ silent ? VALUE_SILENT_MODE : VALUE_NON_SILENT_MODE);
}
}
@@ -172,7 +172,7 @@
switchToNonForcedMode();
break;
default:
- Slog.w(TAG, "Unsupported silent mode: " + silentMode);
+ Slogf.w(TAG, "Unsupported silent mode: %s", silentMode);
}
}
@@ -192,16 +192,16 @@
updateKernelSilentMode(silentMode);
mService.notifySilentModeChange(silentMode);
}
- Slog.i(TAG, "Now in forced " + (silentMode ? "silent" : "non-silent") + " mode: monitoring "
- + mHwStateMonitoringFileName + " is disabled");
+ Slogf.i(TAG, "Now in forced %s mode: monitoring %s is disabled",
+ silentMode ? "silent" : "non-silent", mHwStateMonitoringFileName);
}
private void switchToNonForcedMode() {
boolean updated = false;
synchronized (mLock) {
if (mForcedMode) {
- Slog.i(TAG, "Now in non forced mode: monitoring " + mHwStateMonitoringFileName
- + " is started");
+ Slogf.i(TAG, "Now in non forced mode: monitoring %s is started",
+ mHwStateMonitoringFileName);
mForcedMode = false;
updated = true;
}
@@ -214,8 +214,8 @@
private void startMonitoringSilentModeHwState() {
File monitorFile = new File(mHwStateMonitoringFileName);
if (!monitorFile.exists()) {
- Slog.w(TAG, "Failed to start monitoring Silent Mode HW state: "
- + mHwStateMonitoringFileName + " doesn't exist");
+ Slogf.w(TAG, "Failed to start monitoring Silent Mode HW state: %s doesn't exist",
+ mHwStateMonitoringFileName);
return;
}
FileObserver fileObserver = new FileObserver(monitorFile, FileObserver.MODIFY) {
@@ -234,10 +234,10 @@
String contents = IoUtils.readFileAsString(mHwStateMonitoringFileName)
.trim();
mSilentModeByHwState = VALUE_SILENT_MODE.equals(contents);
- Slog.i(TAG, mHwStateMonitoringFileName + " indicates "
- + (mSilentModeByHwState ? "silent" : "non-silent") + " mode");
+ Slogf.i(TAG, "%s indicates %s mode", mHwStateMonitoringFileName,
+ mSilentModeByHwState ? "silent" : "non-silent");
} catch (Exception e) {
- Slog.w(TAG, "Failed to read " + mHwStateMonitoringFileName, e);
+ Slogf.w(TAG, e, "Failed to read %s: %s", mHwStateMonitoringFileName);
return;
}
newSilentMode = mSilentModeByHwState;
diff --git a/service/src/com/android/car/telemetry/CarTelemetryService.java b/service/src/com/android/car/telemetry/CarTelemetryService.java
new file mode 100644
index 0000000..b68e020
--- /dev/null
+++ b/service/src/com/android/car/telemetry/CarTelemetryService.java
@@ -0,0 +1,86 @@
+/*
+ * 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.telemetry;
+
+import android.annotation.NonNull;
+import android.car.Car;
+import android.car.telemetry.ICarTelemetryService;
+import android.car.telemetry.ICarTelemetryServiceListener;
+import android.content.Context;
+import android.util.IndentingPrintWriter;
+import android.util.Slog;
+
+import com.android.car.CarServiceBase;
+
+/**
+ * CarTelemetryService manages OEM telemetry collection, processing and communication
+ * with a data upload service.
+ */
+public class CarTelemetryService extends ICarTelemetryService.Stub implements CarServiceBase {
+
+ private static final boolean DEBUG = false;
+ private static final String TAG = CarTelemetryService.class.getSimpleName();
+
+ private final Context mContext;
+
+ private ICarTelemetryServiceListener mListener;
+
+ public CarTelemetryService(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public void init() {
+ // nothing to do
+ }
+
+ @Override
+ public void release() {
+ // nothing to do
+ }
+
+ @Override
+ public void dump(IndentingPrintWriter writer) {
+ writer.println("Car Telemetry service");
+ }
+
+ /**
+ * Registers a listener with CarTelemetryService for the service to send data to cloud app.
+ */
+ @Override
+ public void setListener(@NonNull ICarTelemetryServiceListener listener) {
+ // TODO(b/150978930): verify that only a hardcoded app can set the listener
+ mContext.enforceCallingOrSelfPermission(
+ Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
+ if (DEBUG) {
+ Slog.d(TAG, "Setting the listener for car telemetry service");
+ }
+ mListener = listener;
+ }
+
+ /**
+ * Clears the listener registered with CarTelemetryService.
+ */
+ @Override
+ public void clearListener() {
+ mContext.enforceCallingOrSelfPermission(
+ Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
+ if (DEBUG) {
+ Slog.d(TAG, "Clearing listener");
+ }
+ mListener = null;
+ }
+}
diff --git a/service/src/com/android/car/telemetry/LogFilter.java b/service/src/com/android/car/telemetry/LogFilter.java
new file mode 100644
index 0000000..8f72fe8
--- /dev/null
+++ b/service/src/com/android/car/telemetry/LogFilter.java
@@ -0,0 +1,205 @@
+/*
+ * 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.telemetry;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.IntDef;
+import android.util.Log;
+
+import com.android.car.telemetry.TelemetryProto.LogListener;
+import com.android.car.telemetry.TelemetryProto.Manifest;
+
+import java.lang.annotation.Retention;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+/**
+ * Encapsulates a filter that can be matched on log entries. Contains builder function to create
+ * list of LogFilter given the loglistener.
+ */
+public final class LogFilter {
+
+ private static final String TAG = LogFilter.class.getSimpleName();
+
+ /**
+ * Types of filters to match against the logs. Different filter types will match in different
+ * ways.
+ */
+ @Retention(SOURCE)
+ @IntDef({FILTER_TAG, SUBSTRING})
+ @interface FilterType {}
+ static final int FILTER_TAG = 0;
+ static final int SUBSTRING = 1;
+
+ private final int mLogListenerIndex;
+ private final @FilterType int mFilterType;
+ private final String mFilter;
+
+ LogFilter(int logListenerIndex, @FilterType int filterType, String filter) {
+ this.mLogListenerIndex = logListenerIndex;
+ this.mFilterType = filterType;
+ if (filter == null) {
+ throw new NullPointerException("Null filter");
+ }
+ this.mFilter = filter;
+ }
+
+ int getLogListenerIndex() {
+ return mLogListenerIndex;
+ }
+
+ @LogFilter.FilterType int getFilterType() {
+ return mFilterType;
+ }
+
+ String getFilter() {
+ return mFilter;
+ }
+
+ /**
+ * Creates a LogFilter instance.
+ *
+ * @param logListenerIndex the index of the logListener associated with the filter.
+ * @param filterType the type of filter.
+ * @param filter the value of the filter.
+ * @return created LogFilter instance.
+ */
+ static LogFilter create(int logListenerIndex, @FilterType int filterType, String filter) {
+ return new LogFilter(logListenerIndex, filterType, filter);
+ }
+
+ /**
+ * Builds a List of {@link LogFilter} instances from {@link Manifest} and the logListener name.
+ *
+ * @param manifest {@link Manifest} file that contains list of logListeners.
+ * @param logListenerName the name of the logListener as registered in the {@link Manifest}.
+ * @return List of {@link LogFilter} instances.
+ */
+ static List<LogFilter> buildFilters(Manifest manifest, String logListenerName) {
+ int logListenerIndex = getLogListenerIndexFromName(manifest, logListenerName);
+ if (logListenerIndex == -1) {
+ Log.w(TAG, "log listener with name " + logListenerName
+ + " does not exist in manifest.");
+ return Collections.unmodifiableList(new ArrayList<>());
+ }
+
+ List<LogFilter> result = new ArrayList<>();
+ result.addAll(
+ manifest.getLogListeners(logListenerIndex).getTagsList().stream()
+ .map(tag -> LogFilter.create(logListenerIndex, FILTER_TAG, tag))
+ .collect(Collectors.toList()));
+ result.addAll(
+ manifest.getLogListeners(logListenerIndex).getTypesList().stream()
+ .map(LogFilter::typeToFilter)
+ .filter(Optional::isPresent)
+ .map(filter ->
+ LogFilter.create(logListenerIndex, SUBSTRING, filter.get()))
+ .collect(Collectors.toList()));
+ return Collections.unmodifiableList(result);
+ }
+
+ /**
+ * Gets the index of the {@link LogListener} in the manifest's list of logListeners.
+ * Returns -1 if not found.
+ *
+ * @param manifest the {@link Manifest} that contains the definitions of the logListeners.
+ * @param logListenerName name of the {@link LogListener} to get index for.
+ * @return index of the logListener, -1 if not found.
+ */
+ static int getLogListenerIndexFromName(Manifest manifest, String logListenerName) {
+ for (int i = 0; i < manifest.getLogListenersCount(); i++) {
+ LogListener listener = manifest.getLogListeners(i);
+ if (listener.getName().equals(logListenerName)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Converts log message filter {@link LogListener.Type} to corresponding filter string.
+ *
+ * @param type the filter {@link LogListener.Type}.
+ * @return the corresponding filter string for the logListener type.
+ */
+ private static Optional<String> typeToFilter(LogListener.Type type) {
+ switch (type) {
+ case TYPE_UNSPECIFIED:
+ return Optional.empty();
+ case EXCEPTIONS:
+ return Optional.of("Exception: ");
+ }
+ return Optional.empty();
+ }
+
+ /**
+ * Matches in different ways against {@link LogcatReader.LogEntry} components depending on
+ * filterType.
+ *
+ * @param entry the {@link LogcatReader.LogEntry} whose components will be matched against the
+ * filters.
+ * @return boolean denoting whether the filter can match the {@link LogcatReader.LogEntry}.
+ */
+ boolean matches(LogcatReader.LogEntry entry) {
+ switch (getFilterType()) {
+ case FILTER_TAG:
+ return entry.mTag.equals(getFilter());
+ case SUBSTRING:
+ return entry.mMessage.contains(getFilter());
+ }
+ return false;
+ }
+
+ @Override
+ public String toString() {
+ return "LogFilter{"
+ + "mLogListenerIndex=" + mLogListenerIndex + ", "
+ + "mFilterType=" + mFilterType + ", "
+ + "mFilter=" + mFilter
+ + "}";
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) {
+ return true;
+ }
+ if (o instanceof LogFilter) {
+ LogFilter that = (LogFilter) o;
+ return this.mLogListenerIndex == that.getLogListenerIndex()
+ && this.mFilterType == that.getFilterType()
+ && this.mFilter.equals(that.getFilter());
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 1;
+ hash *= 1000003;
+ hash ^= mLogListenerIndex;
+ hash *= 1000003;
+ hash ^= Integer.hashCode(mFilterType);
+ hash *= 1000003;
+ hash ^= mFilter.hashCode();
+ return hash;
+ }
+}
+
diff --git a/service/src/com/android/car/telemetry/LogcatReader.java b/service/src/com/android/car/telemetry/LogcatReader.java
new file mode 100644
index 0000000..a83a72e
--- /dev/null
+++ b/service/src/com/android/car/telemetry/LogcatReader.java
@@ -0,0 +1,302 @@
+/*
+ * 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.telemetry;
+
+import android.annotation.Nullable;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.os.Bundle;
+import android.os.Process;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.ObjIntConsumer;
+import java.util.function.Supplier;
+
+/**
+ * Reads Android logs while there are log listeners registered to it and sends the event through the
+ * mLogEventConsumer.
+ */
+public class LogcatReader {
+ private static final String TAG = LogcatReader.class.getSimpleName();
+
+ // TODO(b/180515554). Find a proper place for LOG_* constants.
+ // They will be used in ScriptExecutor as well.
+ /** The value of key to retrieve log seconds since epoch time from bundle. */
+ private static final String LOG_SEC_KEY = "log.sec";
+
+ /** The value of key to retrieve log nanoseconds from bundle. */
+ private static final String LOG_NSEC_KEY = "log.nsec";
+
+ /** The value of key to retrieve log tag from bundle. */
+ private static final String LOG_TAG_KEY = "log.tag";
+
+ /** The value of key to retrieve log message from bundle. */
+ private static final String LOG_MESSAGE_KEY = "log.message";
+
+ // Defined in system/core/liblog/include/log/log_read.h
+ private static final int LOGGER_ENTRY_MAX_LEN = 5 * 1024;
+
+ // The entry sizes differ in Android 10. See
+ // https://cs.android.com/android/platform/superproject/+/android-10.0.0_r30:system/core/liblog/include/log/log_read.h
+ public static final int ENTRY_V1_SIZE = 20;
+ public static final int ENTRY_V2_V3_SIZE = 24;
+ public static final int ENTRY_V4_SIZE = 28;
+
+ private final AtomicBoolean mRunning = new AtomicBoolean(false);
+
+ private final ObjIntConsumer<Bundle> mLogEventConsumer;
+ private final Supplier<LocalSocket> mLocalSocketSupplier;
+ private final Executor mExecutor;
+
+ private final HashSet<LogFilter> mLogFilters = new HashSet<>();
+
+ private synchronized boolean logFiltersEmpty() {
+ return mLogFilters.isEmpty();
+ }
+
+ /**
+ * Replicates {@code struct log_msg} from system/core/liblog/include/log/log_read.h and {@code
+ * struct AndroidLogEntry} from system/core/liblog/include/log/logprint.h.
+ */
+ static class LogEntry {
+ long mTvSec; // seconds since Epoch
+ long mTvNSec; // nanoseconds
+ int mPid; // generating process's pid
+ long mTid; // generating process's tid
+ long mUid; // generating process's uid
+ int mPriority; // log priority, e.g. {@link Log#INFO}.
+ String mTag;
+ String mMessage;
+
+ /**
+ * Parses raw bytes received from {@code logd}.
+ *
+ * @param data raw bytes
+ * @param readSize number of bytes received from logd.
+ */
+ @Nullable
+ static LogEntry parse(byte[] data, int readSize) {
+ // Parsing log_msg struct defined in system/core/liblog/include/log/log_read.h.
+ // Only first headerSize is used to create LogEntry. Following message messageSize bytes
+ // define log message.
+ ByteBuffer dataBytes = ByteBuffer.wrap(data);
+ dataBytes.order(ByteOrder.LITTLE_ENDIAN);
+ int messageSize = dataBytes.getShort();
+ int headerSize = dataBytes.getShort();
+ if (readSize < messageSize + headerSize) {
+ Log.w(
+ TAG, "Invalid log message size "
+ + (messageSize + headerSize)
+ + ", received only "
+ + readSize);
+ return null;
+ }
+ LogEntry entry = new LogEntry();
+ entry.mPid = dataBytes.getInt();
+ entry.mTid = dataBytes.getInt();
+ entry.mTvSec = dataBytes.getInt();
+ entry.mTvNSec = dataBytes.getInt();
+ if (headerSize >= ENTRY_V2_V3_SIZE) {
+ dataBytes.position(dataBytes.position() + 4); // lid is not used here.
+ }
+ if (headerSize >= ENTRY_V4_SIZE) {
+ entry.mUid = dataBytes.getInt();
+ }
+
+ // Parsing log message.
+ // See android_log_processLogBuffer() in system/core/liblog/logprint.cpp for details.
+ // message format: <priority:1><tag:N>\0<message:N>\0
+ // TODO(b/180516393): improve message parsing that were not transferred
+ // from the cpp above. Also verify this mechanism is ok from selinux perspective.
+
+ if (messageSize < 3) {
+ Log.w(TAG, "Log message is too small, size=" + messageSize);
+ return null;
+ }
+ if (headerSize != dataBytes.position()) {
+ Log.w(TAG, "Invalid header size " + headerSize + ", expected "
+ + dataBytes.position());
+ return null;
+ }
+ int msgStart = -1;
+ int msgEnd = -1;
+ for (int i = 1; i < messageSize; i++) {
+ if (data[headerSize + i] == 0) {
+ if (msgStart == -1) {
+ msgStart = i + 1 + headerSize;
+ } else {
+ msgEnd = i + headerSize;
+ break;
+ }
+ }
+ }
+ if (msgStart == -1) {
+ Log.w(TAG, "Invalid log message");
+ return null;
+ }
+ if (msgEnd == -1) {
+ msgEnd = Math.max(msgStart, messageSize - 1);
+ }
+ entry.mPriority = data[headerSize];
+ entry.mTag =
+ new String(data, headerSize + 1, msgStart - headerSize - 2,
+ StandardCharsets.US_ASCII);
+ entry.mMessage =
+ new String(data, msgStart, msgEnd - msgStart, StandardCharsets.US_ASCII);
+ return entry;
+ }
+ }
+
+ /**
+ * Constructs {@link LogcatReader}.
+ *
+ * @param logEventConsumer a consumer that's called when a filter matches a log.
+ * @param localSocketSupplier a supplier for LocalSocket to connect logd.
+ * @param executor an {@link Executor} to run the LogcatReader instance.
+ */
+ public LogcatReader(
+ ObjIntConsumer<Bundle> logEventConsumer,
+ Supplier<LocalSocket> localSocketSupplier,
+ Executor executor) {
+ this.mLogEventConsumer = logEventConsumer;
+ this.mLocalSocketSupplier = localSocketSupplier;
+ this.mExecutor = executor;
+ }
+
+ /** Runs {@link LogcatReader}. */
+ private void run() {
+ Log.d(TAG, "Running LogcatReader");
+
+ // Under the hood, logcat receives logs from logd, to remove the middleman, LogcatReader
+ // doesn't run logcat, but directly connects to logd socket and parses raw logs.
+ try (LocalSocket socket = mLocalSocketSupplier.get()) {
+ // Connect to /dev/socket/logdr
+ socket.connect(new LocalSocketAddress("logdr", LocalSocketAddress.Namespace.RESERVED));
+ try (OutputStreamWriter writer = new OutputStreamWriter(socket.getOutputStream());
+ InputStream reader = socket.getInputStream()) {
+ // Ask for streaming log and set tail=1 to get only new logs.
+ // See system/core/liblog/logd_reader.cpp for example on how to interact with logdr.
+ writer.write("stream tail=1");
+ writer.flush();
+ Log.d(TAG, "Sent request to logd and awaiting for logs");
+
+ byte[] data = new byte[LOGGER_ENTRY_MAX_LEN + 1];
+ while (!logFiltersEmpty()) {
+ int n = reader.read(data, 0, LOGGER_ENTRY_MAX_LEN);
+ if (n == -1) {
+ Log.e(TAG, "Disconnected from logd");
+ return;
+ }
+ LogEntry entry = LogEntry.parse(data, n);
+ if (entry == null) {
+ continue;
+ }
+
+ // Ignore the logs from the telemetry service. This
+ // makes sure a recursive log storm does not happen - i.e. app produces a log
+ // which in turn executes a code path that produces another log.
+ if (entry.mUid == Process.myUid()) {
+ continue;
+ }
+ // Check if it's running before processing the logs, because by the time we get
+ // here ScriptExecutor might get disconnected.
+ if (!mRunning.get()) {
+ Log.d(TAG, "Not running anymore, exiting.");
+ return;
+ }
+ // Keep track of which logListener an event for this entry has been sent,
+ // so that the same entry isn't sent multiple times for the same logListener
+ // if its multiple filters match.
+ HashSet<Integer> sentLogListenerIndices = new HashSet<>();
+ for (LogFilter filter : mLogFilters) {
+ if (!sentLogListenerIndices.contains(filter.getLogListenerIndex())
+ && filter.matches(entry)) {
+ sentLogListenerIndices.add(filter.getLogListenerIndex());
+ sendLogEvent(filter.getLogListenerIndex(), entry);
+ }
+ }
+ }
+ Log.d(TAG, "Log filters are empty, exiting.");
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to connect to logd", e);
+ } finally {
+ mRunning.set(false);
+ }
+ }
+
+ /**
+ * Sends the log event to the through the mLogEventConsumer.
+ *
+ * @param logListenerIndex the index of the logListener, whose function will receive the log
+ * event.
+ * @param entry the LogEntry instance to be bundled up and sent as event.
+ */
+ private void sendLogEvent(int logListenerIndex, LogEntry entry) {
+ Bundle event = new Bundle();
+ event.putLong(LOG_SEC_KEY, entry.mTvSec);
+ event.putLong(LOG_NSEC_KEY, entry.mTvNSec);
+ event.putString(LOG_TAG_KEY, entry.mTag);
+ event.putString(LOG_MESSAGE_KEY, entry.mMessage);
+ mLogEventConsumer.accept(event, logListenerIndex);
+ }
+
+ /**
+ * Subscribes the list of {@link LogFilter} instances.
+ *
+ * @param newLogFilters the list of new log filters to be added to mLogFilters.
+ */
+ synchronized void subscribeLogFilters(List<LogFilter> newLogFilters) {
+ mLogFilters.addAll(newLogFilters);
+ }
+
+ /**
+ * Unsubscribes all {@link LogFilter} associated with the logListenerIndex
+ *
+ * @param logListenerIndex the index of the logListener to unregister.
+ */
+ synchronized void unsubscribeLogListener(int logListenerIndex) {
+ mLogFilters.removeIf(lf -> lf.getLogListenerIndex() == logListenerIndex);
+ }
+
+ /** Starts the run method in a new thread if logcatReader isn't running already. */
+ void startAsyncIfNotStarted() {
+ if (mRunning.compareAndSet(false, true)) {
+ mExecutor.execute(this::run);
+ }
+ }
+
+ /** Gracefully stops {@link LogcatReader}. */
+ synchronized void stop() {
+ mRunning.set(false);
+ mLogFilters.clear();
+ }
+
+ /** Builds a {@link LocalSocket} to read from {@code logdr}. */
+ static LocalSocket buildLocalSocket() {
+ return new LocalSocket(LocalSocket.SOCKET_SEQPACKET);
+ }
+}
diff --git a/service/src/com/android/car/telemetry/proto/Android.bp b/service/src/com/android/car/telemetry/proto/Android.bp
new file mode 100644
index 0000000..7a8088d
--- /dev/null
+++ b/service/src/com/android/car/telemetry/proto/Android.bp
@@ -0,0 +1,22 @@
+// Copyright (C) 2021 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+java_library_static {
+ name: "cartelemetry-protos",
+ host_supported: true,
+ proto: {
+ type: "lite",
+ },
+ srcs: ["*.proto"],
+}
diff --git a/service/src/com/android/car/telemetry/proto/telemetry.proto b/service/src/com/android/car/telemetry/proto/telemetry.proto
new file mode 100644
index 0000000..422c4a2
--- /dev/null
+++ b/service/src/com/android/car/telemetry/proto/telemetry.proto
@@ -0,0 +1,269 @@
+/*
+ * 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.
+ */
+
+syntax = "proto2";
+
+package com.android.car.telemetry;
+
+option java_package = "com.android.car.telemetry";
+option java_outer_classname = "TelemetryProto";
+
+// A Tpk (Telemetry Package) manifest.
+// It contains a script and the rules how the script runs on devices.
+message Manifest {
+ // Required.
+ // Name of the Tpk, used to distinguish TPKs.
+ // Only alphanumeric and _ characters are allowed. Minimum length is 3 chars.
+ optional string name = 1;
+
+ // Required.
+ // A script that is executed in devices. Must contain all the functions
+ // defined in the listeners below.
+ optional string script = 2;
+
+ // Required.
+ // A unique version number of the Tpk, used to distinguish between Tpks of the
+ // same name.
+ optional int32 version = 3;
+
+ // Telemetry service subscribes these listeners to vehicle property change events.
+ // The functions must be present in the script.
+ repeated PropertyChangeListener property_change_listeners = 4;
+
+ // Telemetry service subscribes these listeners to Android log (logcat) events.
+ // The functions must be present in the script.
+ repeated LogListener log_listeners = 5;
+
+ // Telemetry service subscribes these listeners to stats anomalies, such as
+ // Alerts, Alarms, stats out-of-space anomaly and historical data anomaly.
+ // The number of listener is limited to 10 by statsd.
+ // The functions must be present in the script.
+ repeated StatsListener stats_listeners = 6;
+
+ // Dumpsys result listeners can be registered to retrieve dump report for
+ // specified report types.
+ // The functions must be present in the script.
+ repeated DumpsysResultListener dumpsys_result_listeners = 7;
+
+ // This timer executes functions when a specific event happens.
+ // The functions must be present in the script.
+ repeated Timer timers = 8;
+}
+
+// A function that is executed when a vehicle property change events occur.
+message PropertyChangeListener {
+ // Required.
+ // See packages/services/Car/car-lib/src/android/car/VehiclePropertyIds.java
+ optional int32 vehicle_property_id = 1;
+
+ // See
+ // packages/services/Car/car-lib/src/android/car/hardware/property/CarPropertyManager.java
+ // look for constants SENSOR_RATE_*;
+ optional float read_rate = 2;
+
+ // Required.
+ optional string function_name = 3;
+}
+
+// A function that receives Android log (logcat) events. The log event is passed
+// as a string argument to the function.
+//
+// The LogListener will receive log events if the log event matches any of the
+// "types" or "tags".
+//
+// Please see ../README.md for details.
+//
+// Example:
+// function processException(log_event_line) ... end
+message LogListener {
+ enum Type {
+ // Default value.
+ TYPE_UNSPECIFIED = 0;
+
+ // Matches logged exceptions.
+ EXCEPTIONS = 1;
+ }
+
+ // Required.
+ // Unique name of the logListener within the Tpk. Used to
+ // subscribe/unsubscribe from log events.
+ optional string name = 1;
+
+ // Required.
+ // Name of the function to be executed when the logs the logListener has a
+ // match with its filters
+ optional string function_name = 2;
+
+ // Message types to listen to in LogCat.
+ // At least one "types" or "tags" must be provided.
+ repeated Type types = 3;
+
+ // Tags to listen to in LogCat.
+ // At least one "types" or "tags" must be provided.
+ repeated string tags = 4;
+}
+
+// A function that receives stats reports.
+//
+// LogProcessor App provides limited statsd support. Please see
+// logprocessor/README.md for details.
+//
+// Script function semantics:
+// -- Param report_list is a table with 1-1 mapping
+// to ConfigMetricsReportList.
+// -- Param extra_stats is a table { config_uid, config_key, ... } coming
+// -- from PendingIntent described in StatsManager#setBroadcastSubscriber().
+// function onStatdEvent(report_list, extra_stats) end
+message StatsListener {
+ // Function to be called when the subscriptions below get triggered.
+ optional string function_name = 1;
+
+ // Serialized StatsdConfig defined in
+ // AOSP/frameworks/base/cmds/statsd/src/statsd_config.proto
+ // NOTE: Config ID and other generated IDs must be unique within this
+ // statsd_config.
+ optional bytes statsd_config = 2;
+
+ // Subscribe the telemetry service to stats broadcast events: Alarms and Alerts.
+ // Subscriptions must be defined in the statsd_config as
+ // BroadcastSubscriberDetails.
+ // NOTE: It must be unique within the statsd_config.
+ optional int64 subscriber_id = 3;
+}
+
+// A function that receives Dumpsys reports.
+//
+// The DumpsysResultListener will receive dumpsys reports of the specified
+// DumpsysReportType
+message DumpsysResultListener {
+ // Names of the system services that's allowed to run dumpsys on.
+ enum SystemServiceName {
+ UNSPECIFIED = 0;
+
+ // Matches car service com.android.car.CarService.
+ CAR_SERVICE = 1;
+
+ // Matches package manager service
+ // com.android.server.pm.PackageManagerService.
+ PACKAGE = 2; //
+ }
+
+ // Required.
+ // Unique name of the DumpsysResultListener within the Tpk.
+ // Used to register the specific DumpsysResultListener needed.
+ optional string name = 1;
+
+ // Required.
+ // Name of the function to be executed when a dumpsys report of the specified
+ // system services are retrieved.
+ optional string function_name = 2;
+
+ // Required.
+ // System service to run dumpsys for.
+ optional SystemServiceName system_service_name = 3;
+
+ // Extra arguments for dumpsys
+ optional string arguments = 4;
+}
+
+// Runs a script function after some delay when an event happens.
+message Timer {
+ enum TriggerType {
+ TRIGGER_TYPE_UNSPECIFIED = 0;
+
+ // When the device boots up.
+ //
+ // Note that when device wakes up from a deep sleep (suspend to ram), this
+ // trigger doesn't work.
+ //
+ // It doesn't guarantee immediate activation of the timer right after the
+ // boot, only after the system and the script is ready.
+ // "initial_delay_millis" parameter denotes a delay from boot (not from
+ // the script is initialized and ready).
+ TRIGGER_TYPE_BOOT = 1;
+ }
+
+ // Required.
+ // Name of the script function that will be executed.
+ optional string function_name = 1;
+
+ // Required.
+ // An event trigger that activates this timer.
+ optional TriggerType trigger_type = 2;
+
+ // Required.
+ // Delay after the trigger event before initial executing the function.
+ //
+ // Be careful putting long delays (>30 minutes), because the average
+ // driving duration might be shorter than the delay, and the function
+ // will be executed less frequently than desired amount.
+ optional int64 initial_delay_millis = 3;
+
+ // The max number of times the function will be periodically executed.
+ // When this number of execution is reached, the timer will be stopped.
+ // 0 means the timer is disabled.
+ // -1 means the timer never stops.
+ optional int32 max_count = 4;
+
+ // Time between successive function executions if max_count >= 2.
+ optional int64 period_millis = 5;
+}
+
+// A message that encapsulates different types of error or log metadata as
+// for informational and debugging purposes.
+//
+// When the app crashes or the system restarts, the timers will reset.
+message SecularTrendsLog {
+ // Required.
+ optional int64 timestamp = 1;
+
+ enum LogType {
+ // Default enum.
+ UNSPECIFIED = 0;
+
+ // Used when manifest is misconfigurated, such as missing required fields.
+ MANIFEST_MISCONFIGURATION = 1;
+
+ // Used when a Java service throws an exception.
+ JAVA_EXCEPTION = 2;
+
+ // Used when LogProc service failes to bind to sandbox service.
+ SANDBOX_SERVICE_CONNECTION_FAILURE = 3;
+
+ // Used when an error occurs in the native code.
+ NATIVE_RUNTIME_ERROR = 4;
+
+ // Used when an error occurs while executing the Lua script (such as
+ // errors returned by lua_pcall)
+ LUA_RUNTIME_ERROR = 5;
+
+ // Used when a service that telemetry service depends on crashes or fails.
+ DEPENDENT_SERVICE_FAILURE = 6;
+ }
+
+ // Required.
+ // A type that indicates where the log is generated.
+ optional LogType type = 2;
+
+ // Required.
+ // Human readable message explaining what’s wrong or how to fix it.
+ optional string message = 3;
+
+ // Optional.
+ // If there is an exception, there will be stack trace. However this
+ // information is not always available.
+ optional string stack_trace = 4;
+}
diff --git a/service/src/com/android/car/user/CarUserService.java b/service/src/com/android/car/user/CarUserService.java
index 685a7ce..4e24b56 100644
--- a/service/src/com/android/car/user/CarUserService.java
+++ b/service/src/com/android/car/user/CarUserService.java
@@ -44,6 +44,8 @@
import android.car.user.UserCreationResult;
import android.car.user.UserIdentificationAssociationResponse;
import android.car.user.UserRemovalResult;
+import android.car.user.UserStartResult;
+import android.car.user.UserStopResult;
import android.car.user.UserSwitchResult;
import android.car.userlib.HalCallback;
import android.car.userlib.UserHalHelper;
@@ -110,6 +112,7 @@
import com.android.internal.util.FunctionalUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.util.UserIcons;
+import com.android.server.utils.Slogf;
import java.io.PrintWriter;
import java.util.ArrayList;
@@ -964,10 +967,10 @@
sendUserSwitchResult(receiver, UserSwitchResult.STATUS_NOT_SWITCHABLE);
return;
}
- mHandler.post(()-> switchUserInternal(targetUser, timeoutMs, receiver));
+ mHandler.post(() -> handleSwitchUser(targetUser, timeoutMs, receiver));
}
- private void switchUserInternal(@NonNull UserInfo targetUser, int timeoutMs,
+ private void handleSwitchUser(@NonNull UserInfo targetUser, int timeoutMs,
@NonNull AndroidFuture<UserSwitchResult> receiver) {
int currentUser = ActivityManager.getCurrentUser();
int targetUserId = targetUser.id;
@@ -1129,10 +1132,10 @@
+ " can only remove itself");
}
}
- mHandler.post(()-> removeUserInternal(userId, hasCallerRestrictions, receiver));
+ mHandler.post(() -> handleRemoveUser(userId, hasCallerRestrictions, receiver));
}
- private void removeUserInternal(@UserIdInt int userId, boolean hasCallerRestrictions,
+ private void handleRemoveUser(@UserIdInt int userId, boolean hasCallerRestrictions,
AndroidFuture<UserRemovalResult> receiver) {
UserInfo userInfo = mUserManager.getUserInfo(userId);
if (userInfo == null) {
@@ -1301,12 +1304,12 @@
EventLog.writeEvent(EventLogTags.CAR_USER_SVC_CREATE_USER_REQ,
UserHelperLite.safeName(name), userType, flags, timeoutMs,
hasCallerRestrictions ? 1 : 0);
- mHandler.post(() -> createUserInternal(name, userType, flags, timeoutMs, receiver,
+ mHandler.post(() -> handleCreateUser(name, userType, flags, timeoutMs, receiver,
hasCallerRestrictions));
}
- private void createUserInternal(@Nullable String name, @NonNull String userType,
+ private void handleCreateUser(@Nullable String name, @NonNull String userType,
@UserInfoFlag int flags, int timeoutMs,
@NonNull AndroidFuture<UserCreationResult> receiver,
boolean hasCallerRestrictions) {
@@ -1840,6 +1843,58 @@
}
/**
+ * Starts the specified user in the background.
+ *
+ * @param userId user to start in background
+ * @param receiver to post results
+ */
+ public void startUserInBackground(@UserIdInt int userId,
+ @NonNull AndroidFuture<UserStartResult> receiver) {
+ checkManageOrCreateUsersPermission("startUserInBackground");
+ EventLog.writeEvent(EventLogTags.CAR_USER_SVC_START_USER_IN_BACKGROUND_REQ, userId);
+
+ mHandler.post(() -> handleStartUserInBackground(userId, receiver));
+ }
+
+ private void handleStartUserInBackground(@UserIdInt int userId,
+ @NonNull AndroidFuture<UserStartResult> receiver) {
+ // If the requested user is the current user, do nothing and return success.
+ if (ActivityManager.getCurrentUser() == userId) {
+ sendUserStartResult(
+ userId, UserStartResult.STATUS_SUCCESSFUL_USER_IS_CURRENT_USER, receiver);
+ return;
+ }
+ // If requested user does not exist, return error.
+ if (mUserManager.getUserInfo(userId) == null) {
+ Slogf.w(TAG, "User %d does not exist", userId);
+ sendUserStartResult(userId, UserStartResult.STATUS_USER_DOES_NOT_EXIST, receiver);
+ return;
+ }
+
+ try {
+ if (!mAm.startUserInBackground(userId)) {
+ Slogf.w(TAG, "Failed to start user %d in background", userId);
+ sendUserStartResult(userId, UserStartResult.STATUS_ANDROID_FAILURE, receiver);
+ return;
+ }
+ } catch (RemoteException e) {
+ Slogf.w(TAG, e, "Failed to start user %d in background", userId);
+ }
+
+ // TODO(b/181331178): We are not updating mBackgroundUsersToRestart or
+ // mBackgroundUsersRestartedHere, which were only used for the garage mode. Consider
+ // renaming them to make it more clear.
+ sendUserStartResult(userId, UserStartResult.STATUS_SUCCESSFUL, receiver);
+ }
+
+ private void sendUserStartResult(@UserIdInt int userId, @UserStartResult.Status int result,
+ @NonNull AndroidFuture<UserStartResult> receiver) {
+ EventLog.writeEvent(
+ EventLogTags.CAR_USER_SVC_START_USER_IN_BACKGROUND_RESP, userId, result);
+ receiver.complete(new UserStartResult(result));
+ }
+
+ /**
* Starts all background users that were active in system.
*
* @return list of background users started successfully.
@@ -1891,36 +1946,67 @@
}
/**
- * Stops all background users that were active in system.
+ * Stops the specified background user.
+ *
+ * @param userId user to stop
+ * @param receiver to post results
+ */
+ public void stopUser(@UserIdInt int userId, @NonNull AndroidFuture<UserStopResult> receiver) {
+ mHandler.post(() -> handleStopUser(userId, receiver));
+ }
+
+ private void handleStopUser(
+ @UserIdInt int userId, @NonNull AndroidFuture<UserStopResult> receiver) {
+ @UserStopResult.Status int userStopStatus = stopBackgroundUserInternal(userId);
+ sendUserStopResult(userStopStatus, receiver);
+ }
+
+ private void sendUserStopResult(@UserStopResult.Status int result,
+ @NonNull AndroidFuture<UserStopResult> receiver) {
+ // TODO(b/181331178): Add event log calls.
+ receiver.complete(new UserStopResult(result));
+ }
+
+ private @UserStopResult.Status int stopBackgroundUserInternal(@UserIdInt int userId) {
+ try {
+ int r = mAm.stopUserWithDelayedLocking(userId, true, null);
+ switch(r) {
+ case ActivityManager.USER_OP_SUCCESS:
+ return UserStopResult.STATUS_SUCCESSFUL;
+ case ActivityManager.USER_OP_ERROR_IS_SYSTEM:
+ Slogf.w(TAG, "Cannot stop the system user: %d", userId);
+ return UserStopResult.STATUS_FAILURE_SYSTEM_USER;
+ case ActivityManager.USER_OP_IS_CURRENT:
+ Slogf.w(TAG, "Cannot stop the current user: %d", userId);
+ return UserStopResult.STATUS_FAILURE_CURRENT_USER;
+ case ActivityManager.USER_OP_UNKNOWN_USER:
+ Slogf.w(TAG, "Cannot stop the user that does not exist: %d", userId);
+ return UserStopResult.STATUS_USER_DOES_NOT_EXIST;
+ default:
+ Slogf.w(TAG, "stopUser failed, user: %d, err: %d", userId, r);
+ }
+ } catch (RemoteException e) {
+ // ignore the exception
+ Slogf.w(TAG, e, "error while stopping user: %d", userId);
+ }
+ return UserStopResult.STATUS_ANDROID_FAILURE;
+ }
+
+ /**
+ * Stops a background user.
*
* @return whether stopping succeeds.
*/
public boolean stopBackgroundUser(@UserIdInt int userId) {
- if (userId == UserHandle.USER_SYSTEM) {
- return false;
- }
- if (userId == ActivityManager.getCurrentUser()) {
- Slog.i(TAG, "stopBackgroundUser, already a FG user:" + userId);
- return false;
- }
- try {
- int r = mAm.stopUserWithDelayedLocking(userId, true, null);
- if (r == ActivityManager.USER_OP_SUCCESS) {
- synchronized (mLockUser) {
- Integer user = userId;
- mBackgroundUsersRestartedHere.remove(user);
- }
- } else if (r == ActivityManager.USER_OP_IS_CURRENT) {
- return false;
- } else {
- Slog.i(TAG, "stopBackgroundUser failed, user:" + userId + " err:" + r);
- return false;
+ @UserStopResult.Status int userStopStatus = stopBackgroundUserInternal(userId);
+ if (UserStopResult.isSuccess(userStopStatus)) {
+ // Remove the stopped user from the mBackgroundUserRestartedHere list.
+ synchronized (mLockUser) {
+ mBackgroundUsersRestartedHere.remove(Integer.valueOf(userId));
}
- } catch (RemoteException e) {
- // ignore
- Slog.w(TAG, "error while stopping user", e);
+ return true;
}
- return true;
+ return false;
}
/**
diff --git a/service/src/com/android/car/watchdog/CarWatchdogService.java b/service/src/com/android/car/watchdog/CarWatchdogService.java
index 8b650ed..0b26e0a 100644
--- a/service/src/com/android/car/watchdog/CarWatchdogService.java
+++ b/service/src/com/android/car/watchdog/CarWatchdogService.java
@@ -63,8 +63,8 @@
* Service to implement CarWatchdogManager API.
*/
public final class CarWatchdogService extends ICarWatchdogService.Stub implements CarServiceBase {
- private static final boolean DEBUG = false; // STOPSHIP if true
- private static final String TAG = CarLog.tagFor(CarWatchdogService.class);
+ static final boolean DEBUG = false; // STOPSHIP if true
+ static final String TAG = CarLog.tagFor(CarWatchdogService.class);
private final Context mContext;
private final ICarWatchdogServiceForSystemImpl mWatchdogServiceForSystem;
@@ -85,9 +85,9 @@
mCarWatchdogDaemonHelper = new CarWatchdogDaemonHelper(TAG_WATCHDOG);
mWatchdogServiceForSystem = new ICarWatchdogServiceForSystemImpl(this);
mWatchdogProcessHandler = new WatchdogProcessHandler(mWatchdogServiceForSystem,
- mCarWatchdogDaemonHelper, DEBUG);
+ mCarWatchdogDaemonHelper);
mWatchdogPerfHandler = new WatchdogPerfHandler(mContext, mCarWatchdogDaemonHelper,
- mPackageInfoHandler, DEBUG);
+ mPackageInfoHandler);
}
@Override
diff --git a/service/src/com/android/car/watchdog/PackageInfoHandler.java b/service/src/com/android/car/watchdog/PackageInfoHandler.java
index c2b3323..f04f2b9 100644
--- a/service/src/com/android/car/watchdog/PackageInfoHandler.java
+++ b/service/src/com/android/car/watchdog/PackageInfoHandler.java
@@ -45,6 +45,8 @@
/* Cache of uid to package name mapping. */
@GuardedBy("mLock")
private final SparseArray<String> mPackageNamesByUid = new SparseArray<>();
+ @GuardedBy("mLock")
+ private List<String> mVendorPackagePrefixes = new ArrayList<>();
public PackageInfoHandler(PackageManager packageManager) {
mPackageManager = packageManager;
@@ -93,25 +95,31 @@
*/
public List<PackageInfo> getPackageInfosForUids(int[] uids,
List<String> vendorPackagePrefixes) {
+ synchronized (mLock) {
+ /*
+ * Vendor package prefixes don't change frequently because it changes only when the
+ * vendor configuration is updated. Thus caching this locally during this call should
+ * keep the cache up-to-date because the daemon issues this call frequently.
+ */
+ mVendorPackagePrefixes = vendorPackagePrefixes;
+ }
SparseArray<String> packageNamesByUid = getPackageNamesForUids(uids);
ArrayList<PackageInfo> packageInfos = new ArrayList<>(packageNamesByUid.size());
for (int i = 0; i < packageNamesByUid.size(); ++i) {
packageInfos.add(getPackageInfo(packageNamesByUid.keyAt(i),
- packageNamesByUid.valueAt(i), vendorPackagePrefixes));
+ packageNamesByUid.valueAt(i)));
}
return packageInfos;
}
- private PackageInfo getPackageInfo(
- int uid, String packageName, List<String> vendorPackagePrefixes) {
+ private PackageInfo getPackageInfo(int uid, String packageName) {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageIdentifier = new PackageIdentifier();
packageInfo.packageIdentifier.uid = uid;
packageInfo.packageIdentifier.name = packageName;
packageInfo.sharedUidPackages = new ArrayList<>();
packageInfo.componentType = ComponentType.UNKNOWN;
- // TODO(b/170741935): Identify application category type using the package names. Vendor
- // should define the mappings from package name to the application category type.
+ /* Application category type mapping is handled on the daemon side. */
packageInfo.appCategoryType = ApplicationCategoryType.OTHERS;
int userId = UserHandle.getUserId(uid);
int appId = UserHandle.getAppId(uid);
@@ -126,14 +134,13 @@
boolean seenVendor = false;
boolean seenSystem = false;
boolean seenThirdParty = false;
- /**
+ /*
* A shared UID has multiple packages associated with it and these packages may be
* mapped to different component types. Thus map the shared UID to the most restrictive
* component type.
*/
for (int i = 0; i < sharedUidPackages.length; ++i) {
- int componentType = getPackageComponentType(userId, sharedUidPackages[i],
- vendorPackagePrefixes);
+ int componentType = getPackageComponentType(userId, sharedUidPackages[i]);
switch(componentType) {
case ComponentType.VENDOR:
seenVendor = true;
@@ -159,35 +166,41 @@
}
} else {
packageInfo.componentType = getPackageComponentType(
- userId, packageName, vendorPackagePrefixes);
+ userId, packageName);
}
return packageInfo;
}
- private int getPackageComponentType(
- int userId, String packageName, List<String> vendorPackagePrefixes) {
+ private int getPackageComponentType(int userId, String packageName) {
try {
ApplicationInfo info = mPackageManager.getApplicationInfoAsUser(packageName,
/* flags= */ 0, userId);
- if ((info.privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0
- || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0
- || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_ODM) != 0) {
- return ComponentType.VENDOR;
- }
- if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0
- || (info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
- || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT) != 0
- || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT) != 0) {
- for (String prefix : vendorPackagePrefixes) {
+ return getComponentType(packageName, info);
+ } catch (PackageManager.NameNotFoundException e) {
+ Slogf.e(TAG, "Package '%s' not found for user %d: %s", packageName, userId, e);
+ }
+ return ComponentType.UNKNOWN;
+ }
+
+ /** Returns the component type for the given package and its application info. */
+ public int getComponentType(String packageName, ApplicationInfo info) {
+ if ((info.privateFlags & ApplicationInfo.PRIVATE_FLAG_OEM) != 0
+ || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_VENDOR) != 0
+ || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_ODM) != 0) {
+ return ComponentType.VENDOR;
+ }
+ if ((info.flags & ApplicationInfo.FLAG_SYSTEM) != 0
+ || (info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0
+ || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRODUCT) != 0
+ || (info.privateFlags & ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT) != 0) {
+ synchronized (mLock) {
+ for (String prefix : mVendorPackagePrefixes) {
if (packageName.startsWith(prefix)) {
return ComponentType.VENDOR;
}
}
- return ComponentType.SYSTEM;
}
- } catch (PackageManager.NameNotFoundException e) {
- Slogf.e(TAG, "Package '%s' not found for user %d: %s", packageName, userId, e);
- return ComponentType.UNKNOWN;
+ return ComponentType.SYSTEM;
}
return ComponentType.THIRD_PARTY;
}
diff --git a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
index 37fbbb3..80dcb82 100644
--- a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
+++ b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
@@ -34,11 +34,18 @@
import android.annotation.UserIdInt;
import android.app.ActivityThread;
import android.automotive.watchdog.ResourceType;
+import android.automotive.watchdog.internal.ApplicationCategoryType;
+import android.automotive.watchdog.internal.ComponentType;
import android.automotive.watchdog.internal.PackageIdentifier;
import android.automotive.watchdog.internal.PackageIoOveruseStats;
+import android.automotive.watchdog.internal.PackageMetadata;
import android.automotive.watchdog.internal.PackageResourceOveruseAction;
+import android.automotive.watchdog.internal.PerStateIoOveruseThreshold;
+import android.automotive.watchdog.internal.ResourceSpecificConfiguration;
import android.car.watchdog.CarWatchdogManager;
import android.car.watchdog.IResourceOveruseListener;
+import android.car.watchdog.IoOveruseAlertThreshold;
+import android.car.watchdog.IoOveruseConfiguration;
import android.car.watchdog.IoOveruseStats;
import android.car.watchdog.PackageKillableState;
import android.car.watchdog.PackageKillableState.KillableState;
@@ -48,18 +55,21 @@
import android.car.watchdoglib.CarWatchdogDaemonHelper;
import android.content.Context;
import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.RemoteException;
import android.os.UserHandle;
+import android.os.UserManager;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.IndentingPrintWriter;
import android.util.SparseArray;
-import com.android.car.CarLog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
@@ -78,9 +88,10 @@
* Handles system resource performance monitoring module.
*/
public final class WatchdogPerfHandler {
- private static final String TAG = CarLog.tagFor(CarWatchdogService.class);
+ public static final String INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS = "MAPS";
+ public static final String INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA = "MEDIA";
+ public static final String INTERNAL_APPLICATION_CATEGORY_TYPE_UNKNOWN = "UNKNOWN";
- private final boolean mIsDebugEnabled;
private final Context mContext;
private final CarWatchdogDaemonHelper mCarWatchdogDaemonHelper;
private final PackageInfoHandler mPackageInfoHandler;
@@ -102,10 +113,15 @@
new SparseArray<>();
@GuardedBy("mLock")
private ZonedDateTime mLastStatsReportUTC;
+ /* Set of safe-to-kill system and vendor packages. */
+ @GuardedBy("mLock")
+ public final Set<String> mSafeToKillPackages = new ArraySet<>();
+ /* Default killable state for packages when not updated by the user. */
+ @GuardedBy("mLock")
+ public final Set<String> mDefaultNotKillablePackages = new ArraySet<>();
public WatchdogPerfHandler(Context context, CarWatchdogDaemonHelper daemonHelper,
- PackageInfoHandler packageInfoHandler, boolean isDebugEnabled) {
- mIsDebugEnabled = isDebugEnabled;
+ PackageInfoHandler packageInfoHandler) {
mContext = context;
mCarWatchdogDaemonHelper = daemonHelper;
mPackageInfoHandler = packageInfoHandler;
@@ -119,31 +135,31 @@
* TODO(b/183947162): Opt-in to receive package change broadcast and handle package enabled
* state changes.
*
- * TODO(b/170741935): Read the current day's I/O overuse stats from database and push them
+ * TODO(b/185287136): Persist in-memory data:
+ * 1. Read the current day's I/O overuse stats from database and push them
* to the daemon.
+ * 2. Fetch the safe-to-kill from daemon on initialization and update mSafeToKillPackages.
*/
synchronized (mLock) {
checkAndHandleDateChangeLocked();
}
- if (mIsDebugEnabled) {
- Slogf.d(TAG, "WatchdogPerfHandler is initialized");
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "WatchdogPerfHandler is initialized");
}
}
/** Releases the handler */
public void release() {
- /*
- * TODO(b/170741935): Write daily usage to SQLite DB storage.
- */
- if (mIsDebugEnabled) {
- Slogf.d(TAG, "WatchdogPerfHandler is released");
+ /* TODO(b/185287136): Write daily usage to SQLite DB storage. */
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "WatchdogPerfHandler is released");
}
}
/** Dumps its state. */
public void dump(IndentingPrintWriter writer) {
- /**
- * TODO(b/170741935): Implement this method.
+ /*
+ * TODO(b/183436216): Implement this method.
*/
}
@@ -156,9 +172,29 @@
"Must provide valid resource overuse flag");
Preconditions.checkArgument((maxStatsPeriod > 0),
"Must provide valid maximum stats period");
- // TODO(b/170741935): Implement this method.
- return new ResourceOveruseStats.Builder("",
- UserHandle.getUserHandleForUid(Binder.getCallingUid())).build();
+ // When more resource stats are added, make this as optional.
+ Preconditions.checkArgument((resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) != 0,
+ "Must provide resource I/O overuse flag");
+ int callingUid = Binder.getCallingUid();
+ int callingUserId = UserHandle.getUserId(callingUid);
+ UserHandle callingUserHandle = UserHandle.of(callingUserId);
+ String callingPackageName =
+ mPackageInfoHandler.getPackageNamesForUids(new int[]{callingUid})
+ .get(callingUid, null);
+ if (callingPackageName == null) {
+ Slogf.w(CarWatchdogService.TAG, "Failed to fetch package info for uid %d", callingUid);
+ return new ResourceOveruseStats.Builder("", callingUserHandle).build();
+ }
+ ResourceOveruseStats.Builder statsBuilder =
+ new ResourceOveruseStats.Builder(callingPackageName, callingUserHandle);
+ statsBuilder.setIoOveruseStats(getIoOveruseStats(callingUserId, callingPackageName,
+ /* minimumBytesWritten= */ 0, maxStatsPeriod));
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "Returning all resource overuse stats for calling uid "
+ + "%d [user %d and package '%s']", callingUid, callingUserId,
+ callingPackageName);
+ }
+ return statsBuilder.build();
}
/** Returns resource overuse stats for all packages. */
@@ -171,8 +207,24 @@
"Must provide valid resource overuse flag");
Preconditions.checkArgument((maxStatsPeriod > 0),
"Must provide valid maximum stats period");
- // TODO(b/170741935): Implement this method.
- return new ArrayList<>();
+ // When more resource types are added, make this as optional.
+ Preconditions.checkArgument((resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) != 0,
+ "Must provide resource I/O overuse flag");
+ long minimumBytesWritten = getMinimumBytesWritten(minimumStatsFlag);
+ List<ResourceOveruseStats> allStats = new ArrayList<>();
+ for (PackageResourceUsage usage : mUsageByUserPackage.values()) {
+ ResourceOveruseStats.Builder statsBuilder = usage.getResourceOveruseStatsBuilder();
+ IoOveruseStats ioOveruseStats = getIoOveruseStats(usage.userId, usage.packageName,
+ minimumBytesWritten, maxStatsPeriod);
+ if (ioOveruseStats == null) {
+ continue;
+ }
+ allStats.add(statsBuilder.setIoOveruseStats(ioOveruseStats).build());
+ }
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "Returning all resource overuse stats");
+ }
+ return allStats;
}
/** Returns resource overuse stats for the specified user package. */
@@ -183,12 +235,25 @@
@CarWatchdogManager.StatsPeriod int maxStatsPeriod) {
Objects.requireNonNull(packageName, "Package name must be non-null");
Objects.requireNonNull(userHandle, "User handle must be non-null");
+ Preconditions.checkArgument((userHandle != UserHandle.ALL),
+ "Must provide the user handle for a specific user");
Preconditions.checkArgument((resourceOveruseFlag > 0),
"Must provide valid resource overuse flag");
Preconditions.checkArgument((maxStatsPeriod > 0),
"Must provide valid maximum stats period");
- // TODO(b/170741935): Implement this method.
- return new ResourceOveruseStats.Builder("", userHandle).build();
+ // When more resource types are added, make this as optional.
+ Preconditions.checkArgument((resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) != 0,
+ "Must provide resource I/O overuse flag");
+ ResourceOveruseStats.Builder statsBuilder =
+ new ResourceOveruseStats.Builder(packageName, userHandle);
+ statsBuilder.setIoOveruseStats(getIoOveruseStats(userHandle.getIdentifier(), packageName,
+ /* minimumBytesWritten= */ 0, maxStatsPeriod));
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG,
+ "Returning resource overuse stats for user %d, package '%s'",
+ userHandle.getIdentifier(), packageName);
+ }
+ return statsBuilder.build();
}
/** Adds the resource overuse listener. */
@@ -238,18 +303,107 @@
boolean isKillable) {
Objects.requireNonNull(packageName, "Package name must be non-null");
Objects.requireNonNull(userHandle, "User handle must be non-null");
- /*
- * TODO(b/170741935): Add/remove the package from the user do-no-kill list.
- * If the {@code userHandle == UserHandle.ALL}, update the settings for all users.
- */
+ if (userHandle == UserHandle.ALL) {
+ synchronized (mLock) {
+ for (PackageResourceUsage usage : mUsageByUserPackage.values()) {
+ if (!usage.packageName.equals(packageName)) {
+ continue;
+ }
+ if (!usage.setKillableState(isKillable)) {
+ Slogf.e(CarWatchdogService.TAG,
+ "Cannot set killable state for package '%s'", packageName);
+ throw new IllegalArgumentException(
+ "Package killable state is not updatable");
+ }
+ }
+ if (!isKillable) {
+ mDefaultNotKillablePackages.add(packageName);
+ } else {
+ mDefaultNotKillablePackages.remove(packageName);
+ }
+ }
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG,
+ "Successfully set killable package state for all users");
+ }
+ return;
+ }
+ int userId = userHandle.getIdentifier();
+ String key = getUserPackageUniqueId(userId, packageName);
+ synchronized (mLock) {
+ /*
+ * When the queried package is not cached in {@link mUsageByUserPackage}, the set API
+ * will update the killable state even when the package should never be killed.
+ * But the get API will return the correct killable state. This behavior is tolerable
+ * because in production the set API should be called only after the get API.
+ * For instance, when this case happens by mistake and the package overuses resource
+ * between the set and the get API calls, the daemon will provide correct killable
+ * state when pushing the latest stats. Ergo, the invalid killable state doesn't have
+ * any effect.
+ */
+ PackageResourceUsage usage = mUsageByUserPackage.getOrDefault(key,
+ new PackageResourceUsage(userId, packageName));
+ if (!usage.setKillableState(isKillable)) {
+ Slogf.e(CarWatchdogService.TAG,
+ "User %d cannot set killable state for package '%s'",
+ userHandle.getIdentifier(), packageName);
+ throw new IllegalArgumentException("Package killable state is not updatable");
+ }
+ mUsageByUserPackage.put(key, usage);
+ }
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "Successfully set killable package state for user %d",
+ userId);
+ }
}
/** Returns the list of package killable states on resource overuse for the user. */
@NonNull
public List<PackageKillableState> getPackageKillableStatesAsUser(UserHandle userHandle) {
Objects.requireNonNull(userHandle, "User handle must be non-null");
- // TODO(b/170741935): Implement this method.
- return new ArrayList<>();
+ PackageManager pm = mContext.getPackageManager();
+ if (userHandle != UserHandle.ALL) {
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "Returning all package killable states for user %d",
+ userHandle.getIdentifier());
+ }
+ return getPackageKillableStatesForUserId(userHandle.getIdentifier(), pm);
+ }
+ List<PackageKillableState> packageKillableStates = new ArrayList<>();
+ UserManager userManager = UserManager.get(mContext);
+ List<UserInfo> userInfos = userManager.getAliveUsers();
+ for (UserInfo userInfo : userInfos) {
+ packageKillableStates.addAll(getPackageKillableStatesForUserId(userInfo.id, pm));
+ }
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "Returning all package killable states for all users");
+ }
+ return packageKillableStates;
+ }
+
+ private List<PackageKillableState> getPackageKillableStatesForUserId(int userId,
+ PackageManager pm) {
+ List<PackageInfo> packageInfos = pm.getInstalledPackagesAsUser(/* flags= */0, userId);
+ List<PackageKillableState> states = new ArrayList<>();
+ synchronized (mLock) {
+ for (int i = 0; i < packageInfos.size(); ++i) {
+ PackageInfo packageInfo = packageInfos.get(i);
+ String key = getUserPackageUniqueId(userId, packageInfo.packageName);
+ PackageResourceUsage usage = mUsageByUserPackage.getOrDefault(key,
+ new PackageResourceUsage(userId, packageInfo.packageName));
+ int killableState = usage.syncAndFetchKillableStateLocked(
+ mPackageInfoHandler.getComponentType(packageInfo.packageName,
+ packageInfo.applicationInfo));
+ mUsageByUserPackage.put(key, usage);
+ states.add(
+ new PackageKillableState(packageInfo.packageName, userId, killableState));
+ }
+ }
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG,
+ "Returning the package killable states for a user package");
+ }
+ return states;
}
/** Sets the given resource overuse configurations. */
@@ -257,11 +411,18 @@
List<ResourceOveruseConfiguration> configurations,
@CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag) {
Objects.requireNonNull(configurations, "Configurations must be non-null");
+ Preconditions.checkArgument((configurations.size() > 0),
+ "Must provide at least one configuration");
Preconditions.checkArgument((resourceOveruseFlag > 0),
"Must provide valid resource overuse flag");
Set<Integer> seenComponentTypes = new ArraySet<>();
+ List<android.automotive.watchdog.internal.ResourceOveruseConfiguration> internalConfigs =
+ new ArrayList<>();
for (ResourceOveruseConfiguration config : configurations) {
int componentType = config.getComponentType();
+ if (toComponentTypeStr(componentType).equals("UNKNOWN")) {
+ throw new IllegalArgumentException("Invalid component type in the configuration");
+ }
if (seenComponentTypes.contains(componentType)) {
throw new IllegalArgumentException(
"Cannot provide duplicate configurations for the same component type");
@@ -271,8 +432,24 @@
throw new IllegalArgumentException("Must provide I/O overuse configuration");
}
seenComponentTypes.add(config.getComponentType());
+ internalConfigs.add(toInternalResourceOveruseConfiguration(config,
+ resourceOveruseFlag));
}
- // TODO(b/170741935): Implement this method.
+
+ // TODO(b/186119640): Add retry logic when daemon is not available.
+ try {
+ mCarWatchdogDaemonHelper.updateResourceOveruseConfigurations(internalConfigs);
+ } catch (IllegalArgumentException e) {
+ Slogf.w(CarWatchdogService.TAG, "Failed to set resource overuse configurations: %s", e);
+ throw e;
+ } catch (RemoteException | RuntimeException e) {
+ Slogf.w(CarWatchdogService.TAG, "Failed to set resource overuse configurations: %s", e);
+ throw new IllegalStateException(e);
+ }
+ /* TODO(b/185287136): Fetch safe-to-kill list from daemon and update mSafeToKillPackages. */
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "Set the resource overuse configuration successfully");
+ }
}
/** Returns the available resource overuse configurations. */
@@ -281,8 +458,25 @@
@CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag) {
Preconditions.checkArgument((resourceOveruseFlag > 0),
"Must provide valid resource overuse flag");
- // TODO(b/170741935): Implement this method.
- return new ArrayList<>();
+ List<android.automotive.watchdog.internal.ResourceOveruseConfiguration> internalConfigs =
+ new ArrayList<>();
+ // TODO(b/186119640): Add retry logic when daemon is not available.
+ try {
+ internalConfigs = mCarWatchdogDaemonHelper.getResourceOveruseConfigurations();
+ } catch (RemoteException | RuntimeException e) {
+ Slogf.w(CarWatchdogService.TAG, "Failed to fetch resource overuse configurations: %s",
+ e);
+ throw new IllegalStateException(e);
+ }
+ List<ResourceOveruseConfiguration> configs = new ArrayList<>();
+ for (android.automotive.watchdog.internal.ResourceOveruseConfiguration internalConfig
+ : internalConfigs) {
+ configs.add(toResourceOveruseConfiguration(internalConfig, resourceOveruseFlag));
+ }
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "Returning the resource overuse configuration");
+ }
+ return configs;
}
/** Processes the latest I/O overuse stats */
@@ -308,9 +502,12 @@
* and only the daemon is aware of such packages. Thus the flag is used to
* indicate which packages should be notified.
*/
- notifyResourceOveruseStatsLocked(stats.uid,
- usage.getResourceOveruseStatsWithIo());
+ ResourceOveruseStats resourceOveruseStats =
+ usage.getResourceOveruseStatsBuilder().setIoOveruseStats(
+ usage.getIoOveruseStats()).build();
+ notifyResourceOveruseStatsLocked(stats.uid, resourceOveruseStats);
}
+
if (!usage.ioUsage.exceedsThreshold()) {
continue;
}
@@ -326,7 +523,6 @@
* #2 The package has no recurring overuse behavior and the user opted to not
* kill the package so honor the user's decision.
*/
- String userPackageId = getUserPackageUniqueId(userId, packageName);
int killableState = usage.getKillableState();
if (killableState == KILLABLE_STATE_NEVER) {
mOveruseActionsByUserPackage.add(overuseAction);
@@ -363,8 +559,8 @@
usage.oldEnabledState = oldEnabledState;
}
} catch (RemoteException e) {
- Slogf.e(TAG, "Failed to disable application enabled setting for user %d, "
- + "package '%s'", userId, packageName);
+ Slogf.e(CarWatchdogService.TAG, "Failed to disable application enabled setting "
+ + "for user %d, package '%s'", userId, packageName);
}
mOveruseActionsByUserPackage.add(overuseAction);
}
@@ -373,6 +569,9 @@
WatchdogPerfHandler::notifyActionsTakenOnOveruse, this));
}
}
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "Processed latest I/O overuse stats");
+ }
}
/** Notify daemon about the actions take on resource overuse */
@@ -387,9 +586,13 @@
}
try {
mCarWatchdogDaemonHelper.actionTakenOnResourceOveruse(actions);
- } catch (RemoteException e) {
- Slogf.w(TAG, "Failed to notify car watchdog daemon of actions taken on "
- + "resource overuse: %s", e);
+ } catch (RemoteException | RuntimeException e) {
+ Slogf.w(CarWatchdogService.TAG, "Failed to notify car watchdog daemon of actions taken "
+ + "on resource overuse: %s", e);
+ }
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG,
+ "Notified car watchdog daemon of actions taken on resource overuse");
}
}
@@ -401,7 +604,7 @@
try {
listenerInfo.listener.onOveruse(resourceOveruseStats);
} catch (RemoteException e) {
- Slogf.e(TAG,
+ Slogf.e(CarWatchdogService.TAG,
"Failed to notify listener(uid %d, package '%s') on resource overuse: %s",
uid, resourceOveruseStats, e);
}
@@ -415,11 +618,15 @@
try {
systemListenerInfo.listener.onOveruse(resourceOveruseStats);
} catch (RemoteException e) {
- Slogf.e(TAG, "Failed to notify system listener(uid %d, pid: %d) of resource "
- + "overuse by package(uid %d, package '%s'): %s",
+ Slogf.e(CarWatchdogService.TAG, "Failed to notify system listener(uid %d, pid: %d) "
+ + "of resource overuse by package(uid %d, package '%s'): %s",
systemListenerInfo.uid, systemListenerInfo.pid, uid, packageName, e);
}
}
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG,
+ "Notified resource overuse stats to listening applications");
+ }
}
private void checkAndHandleDateChangeLocked() {
@@ -443,7 +650,7 @@
usage.oldEnabledState,
/* flags= */ 0, usage.userId, mContext.getPackageName());
} catch (RemoteException e) {
- Slogf.e(TAG,
+ Slogf.e(CarWatchdogService.TAG,
"Failed to reset enabled setting for disabled package '%s', user %d",
usage.packageName, usage.userId);
}
@@ -451,6 +658,9 @@
/* TODO(b/170741935): Stash the old usage into SQLite DB storage. */
usage.ioUsage.clear();
}
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "Handled date change successfully");
+ }
}
private PackageResourceUsage cacheAndFetchUsageLocked(@UserIdInt int userId, String packageName,
@@ -458,19 +668,39 @@
String key = getUserPackageUniqueId(userId, packageName);
PackageResourceUsage usage = mUsageByUserPackage.getOrDefault(key,
new PackageResourceUsage(userId, packageName));
- usage.update(internalStats);
+ usage.updateLocked(internalStats);
mUsageByUserPackage.put(key, usage);
return usage;
}
private boolean isRecurringOveruseLocked(PackageResourceUsage ioUsage) {
/*
- * TODO(b/170741935): Look up I/O overuse history and determine whether or not the package
+ * TODO(b/185287136): Look up I/O overuse history and determine whether or not the package
* has recurring I/O overuse behavior.
*/
return false;
}
+ private IoOveruseStats getIoOveruseStats(int userId, String packageName,
+ long minimumBytesWritten, @CarWatchdogManager.StatsPeriod int maxStatsPeriod) {
+ String key = getUserPackageUniqueId(userId, packageName);
+ PackageResourceUsage usage = mUsageByUserPackage.get(key);
+ if (usage == null) {
+ return null;
+ }
+ IoOveruseStats stats = usage.getIoOveruseStats();
+ long totalBytesWritten = stats != null ? stats.getTotalBytesWritten() : 0;
+ /*
+ * TODO(b/185431129): When maxStatsPeriod > current day, populate the historical stats
+ * from the local database. Also handle the case where the package doesn't have current
+ * day stats but has historical stats.
+ */
+ if (totalBytesWritten < minimumBytesWritten) {
+ return null;
+ }
+ return stats;
+ }
+
private void addResourceOveruseListenerLocked(
@CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag,
@NonNull IResourceOveruseListener listener,
@@ -495,20 +725,21 @@
try {
listenerInfo.linkToDeath();
} catch (RemoteException e) {
- Slogf.w(TAG, "Cannot add %s: linkToDeath to listener failed", listenerType);
+ Slogf.w(CarWatchdogService.TAG, "Cannot add %s: linkToDeath to listener failed",
+ listenerType);
return;
}
if (existingListenerInfo != null) {
- Slogf.w(TAG, "Overwriting existing %s: pid %d, uid: %d", listenerType,
- existingListenerInfo.pid, existingListenerInfo.uid);
+ Slogf.w(CarWatchdogService.TAG, "Overwriting existing %s: pid %d, uid: %d",
+ listenerType, existingListenerInfo.pid, existingListenerInfo.uid);
existingListenerInfo.unlinkToDeath();
}
listenerInfosByUid.put(callingUid, listenerInfo);
- if (mIsDebugEnabled) {
- Slogf.d(TAG, "The %s (pid: %d, uid: %d) is added", listenerType,
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "The %s (pid: %d, uid: %d) is added", listenerType,
callingPid, callingUid);
}
}
@@ -522,14 +753,15 @@
ResourceOveruseListenerInfo listenerInfo = listenerInfosByUid.get(callingUid, null);
if (listenerInfo == null || listenerInfo.listener != listener) {
- Slogf.w(TAG, "Cannot remove the %s: it has not been registered before", listenerType);
+ Slogf.w(CarWatchdogService.TAG,
+ "Cannot remove the %s: it has not been registered before", listenerType);
return;
}
listenerInfo.unlinkToDeath();
listenerInfosByUid.remove(callingUid);
- if (mIsDebugEnabled) {
- Slogf.d(TAG, "The %s (pid: %d, uid: %d) is removed", listenerType, listenerInfo.pid,
- listenerInfo.uid);
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "The %s (pid: %d, uid: %d) is removed", listenerType,
+ listenerInfo.pid, listenerInfo.uid);
}
}
@@ -561,9 +793,8 @@
private static PerStateBytes toPerStateBytes(
android.automotive.watchdog.PerStateBytes internalPerStateBytes) {
- PerStateBytes perStateBytes = new PerStateBytes(internalPerStateBytes.foregroundBytes,
+ return new PerStateBytes(internalPerStateBytes.foregroundBytes,
internalPerStateBytes.backgroundBytes, internalPerStateBytes.garageModeBytes);
- return perStateBytes;
}
private static long totalPerStateBytes(
@@ -575,7 +806,231 @@
internalPerStateBytes.backgroundBytes), internalPerStateBytes.garageModeBytes);
}
- private static final class PackageResourceUsage {
+ private static long getMinimumBytesWritten(
+ @CarWatchdogManager.MinimumStatsFlag int minimumStatsIoFlag) {
+ switch (minimumStatsIoFlag) {
+ case 0:
+ return 0;
+ case CarWatchdogManager.FLAG_MINIMUM_STATS_IO_1_MB:
+ return 1024 * 1024;
+ case CarWatchdogManager.FLAG_MINIMUM_STATS_IO_100_MB:
+ return 100 * 1024 * 1024;
+ case CarWatchdogManager.FLAG_MINIMUM_STATS_IO_1_GB:
+ return 1024 * 1024 * 1024;
+ default:
+ throw new IllegalArgumentException(
+ "Must provide valid minimum stats flag for I/O resource");
+ }
+ }
+
+ private static android.automotive.watchdog.internal.ResourceOveruseConfiguration
+ toInternalResourceOveruseConfiguration(ResourceOveruseConfiguration config,
+ @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag) {
+ android.automotive.watchdog.internal.ResourceOveruseConfiguration internalConfig =
+ new android.automotive.watchdog.internal.ResourceOveruseConfiguration();
+ internalConfig.componentType = config.getComponentType();
+ internalConfig.safeToKillPackages = config.getSafeToKillPackages();
+ internalConfig.vendorPackagePrefixes = config.getVendorPackagePrefixes();
+ internalConfig.packageMetadata = new ArrayList<>();
+ for (Map.Entry<String, String> entry : config.getPackagesToAppCategoryTypes().entrySet()) {
+ if (entry.getKey().isEmpty()) {
+ continue;
+ }
+ PackageMetadata metadata = new PackageMetadata();
+ metadata.packageName = entry.getKey();
+ switch(entry.getValue()) {
+ case ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS:
+ metadata.appCategoryType = ApplicationCategoryType.MAPS;
+ break;
+ case ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MEDIA:
+ metadata.appCategoryType = ApplicationCategoryType.MEDIA;
+ break;
+ default:
+ continue;
+ }
+ internalConfig.packageMetadata.add(metadata);
+ }
+ internalConfig.resourceSpecificConfigurations = new ArrayList<>();
+ if ((resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) != 0
+ && config.getIoOveruseConfiguration() != null) {
+ internalConfig.resourceSpecificConfigurations.add(
+ toResourceSpecificConfiguration(config.getComponentType(),
+ config.getIoOveruseConfiguration()));
+ }
+ return internalConfig;
+ }
+
+ private static ResourceSpecificConfiguration
+ toResourceSpecificConfiguration(int componentType, IoOveruseConfiguration config) {
+ android.automotive.watchdog.internal.IoOveruseConfiguration internalConfig =
+ new android.automotive.watchdog.internal.IoOveruseConfiguration();
+ internalConfig.componentLevelThresholds = toPerStateIoOveruseThreshold(
+ toComponentTypeStr(componentType), config.getComponentLevelThresholds());
+ internalConfig.packageSpecificThresholds = toPerStateIoOveruseThresholds(
+ config.getPackageSpecificThresholds());
+ internalConfig.categorySpecificThresholds = toPerStateIoOveruseThresholds(
+ config.getAppCategorySpecificThresholds());
+ for (PerStateIoOveruseThreshold threshold : internalConfig.categorySpecificThresholds) {
+ switch(threshold.name) {
+ case ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS:
+ threshold.name = INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS;
+ break;
+ case ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MEDIA:
+ threshold.name = INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA;
+ break;
+ default:
+ threshold.name = INTERNAL_APPLICATION_CATEGORY_TYPE_UNKNOWN;
+ }
+ }
+ internalConfig.systemWideThresholds = toInternalIoOveruseAlertThresholds(
+ config.getSystemWideThresholds());
+
+ ResourceSpecificConfiguration resourceSpecificConfig = new ResourceSpecificConfiguration();
+ resourceSpecificConfig.setIoOveruseConfiguration(internalConfig);
+ return resourceSpecificConfig;
+ }
+
+ @VisibleForTesting
+ static String toComponentTypeStr(int componentType) {
+ switch(componentType) {
+ case ComponentType.SYSTEM:
+ return "SYSTEM";
+ case ComponentType.VENDOR:
+ return "VENDOR";
+ case ComponentType.THIRD_PARTY:
+ return "THIRD_PARTY";
+ default:
+ return "UNKNOWN";
+ }
+ }
+
+ private static List<PerStateIoOveruseThreshold> toPerStateIoOveruseThresholds(
+ Map<String, PerStateBytes> thresholds) {
+ List<PerStateIoOveruseThreshold> internalThresholds = new ArrayList<>();
+ for (Map.Entry<String, PerStateBytes> entry : thresholds.entrySet()) {
+ if (!entry.getKey().isEmpty()) {
+ internalThresholds.add(toPerStateIoOveruseThreshold(entry.getKey(),
+ entry.getValue()));
+ }
+ }
+ return internalThresholds;
+ }
+
+ private static PerStateIoOveruseThreshold toPerStateIoOveruseThreshold(String name,
+ PerStateBytes perStateBytes) {
+ PerStateIoOveruseThreshold threshold = new PerStateIoOveruseThreshold();
+ threshold.name = name;
+ threshold.perStateWriteBytes = new android.automotive.watchdog.PerStateBytes();
+ threshold.perStateWriteBytes.foregroundBytes = perStateBytes.getForegroundModeBytes();
+ threshold.perStateWriteBytes.backgroundBytes = perStateBytes.getBackgroundModeBytes();
+ threshold.perStateWriteBytes.garageModeBytes = perStateBytes.getGarageModeBytes();
+ return threshold;
+ }
+
+ private static List<android.automotive.watchdog.internal.IoOveruseAlertThreshold>
+ toInternalIoOveruseAlertThresholds(List<IoOveruseAlertThreshold> thresholds) {
+ List<android.automotive.watchdog.internal.IoOveruseAlertThreshold> internalThresholds =
+ new ArrayList<>();
+ for (IoOveruseAlertThreshold threshold : thresholds) {
+ if (threshold.getDurationInSeconds() == 0
+ || threshold.getWrittenBytesPerSecond() == 0) {
+ continue;
+ }
+ android.automotive.watchdog.internal.IoOveruseAlertThreshold internalThreshold =
+ new android.automotive.watchdog.internal.IoOveruseAlertThreshold();
+ internalThreshold.durationInSeconds = threshold.getDurationInSeconds();
+ internalThreshold.writtenBytesPerSecond = threshold.getWrittenBytesPerSecond();
+ internalThresholds.add(internalThreshold);
+ }
+ return internalThresholds;
+ }
+
+ private static ResourceOveruseConfiguration toResourceOveruseConfiguration(
+ android.automotive.watchdog.internal.ResourceOveruseConfiguration internalConfig,
+ @CarWatchdogManager.ResourceOveruseFlag int resourceOveruseFlag) {
+ Map<String, String> packagesToAppCategoryTypes = new ArrayMap<>();
+ for (PackageMetadata metadata : internalConfig.packageMetadata) {
+ String categoryTypeStr;
+ switch (metadata.appCategoryType) {
+ case ApplicationCategoryType.MAPS:
+ categoryTypeStr = ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS;
+ break;
+ case ApplicationCategoryType.MEDIA:
+ categoryTypeStr = ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MEDIA;
+ break;
+ default:
+ continue;
+ }
+ packagesToAppCategoryTypes.put(metadata.packageName, categoryTypeStr);
+ }
+ ResourceOveruseConfiguration.Builder configBuilder =
+ new ResourceOveruseConfiguration.Builder(
+ internalConfig.componentType,
+ internalConfig.safeToKillPackages,
+ internalConfig.vendorPackagePrefixes,
+ packagesToAppCategoryTypes);
+ for (ResourceSpecificConfiguration resourceSpecificConfig :
+ internalConfig.resourceSpecificConfigurations) {
+ if (resourceSpecificConfig.getTag()
+ == ResourceSpecificConfiguration.ioOveruseConfiguration
+ && (resourceOveruseFlag & FLAG_RESOURCE_OVERUSE_IO) != 0) {
+ configBuilder.setIoOveruseConfiguration(toIoOveruseConfiguration(
+ resourceSpecificConfig.getIoOveruseConfiguration()));
+ }
+ }
+ return configBuilder.build();
+ }
+
+ private static IoOveruseConfiguration toIoOveruseConfiguration(
+ android.automotive.watchdog.internal.IoOveruseConfiguration internalConfig) {
+ PerStateBytes componentLevelThresholds =
+ toPerStateBytes(internalConfig.componentLevelThresholds.perStateWriteBytes);
+ Map<String, PerStateBytes> packageSpecificThresholds =
+ toPerStateBytesMap(internalConfig.packageSpecificThresholds);
+ Map<String, PerStateBytes> appCategorySpecificThresholds =
+ toPerStateBytesMap(internalConfig.categorySpecificThresholds);
+ replaceKey(appCategorySpecificThresholds, INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS,
+ ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS);
+ replaceKey(appCategorySpecificThresholds, INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA,
+ ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MEDIA);
+ List<IoOveruseAlertThreshold> systemWideThresholds =
+ toIoOveruseAlertThresholds(internalConfig.systemWideThresholds);
+
+ IoOveruseConfiguration.Builder configBuilder = new IoOveruseConfiguration.Builder(
+ componentLevelThresholds, packageSpecificThresholds, appCategorySpecificThresholds,
+ systemWideThresholds);
+ return configBuilder.build();
+ }
+
+ private static Map<String, PerStateBytes> toPerStateBytesMap(
+ List<PerStateIoOveruseThreshold> thresholds) {
+ Map<String, PerStateBytes> thresholdsMap = new ArrayMap<>();
+ for (PerStateIoOveruseThreshold threshold : thresholds) {
+ thresholdsMap.put(threshold.name, toPerStateBytes(threshold.perStateWriteBytes));
+ }
+ return thresholdsMap;
+ }
+
+ private static List<IoOveruseAlertThreshold> toIoOveruseAlertThresholds(
+ List<android.automotive.watchdog.internal.IoOveruseAlertThreshold> internalThresholds) {
+ List<IoOveruseAlertThreshold> thresholds = new ArrayList<>();
+ for (android.automotive.watchdog.internal.IoOveruseAlertThreshold internalThreshold
+ : internalThresholds) {
+ thresholds.add(new IoOveruseAlertThreshold(internalThreshold.durationInSeconds,
+ internalThreshold.writtenBytesPerSecond));
+ }
+ return thresholds;
+ }
+
+ private static void replaceKey(Map<String, PerStateBytes> map, String oldKey, String newKey) {
+ PerStateBytes perStateBytes = map.get(oldKey);
+ if (perStateBytes != null) {
+ map.put(newKey, perStateBytes);
+ map.remove(oldKey);
+ }
+ }
+
+ private final class PackageResourceUsage {
public final String packageName;
public @UserIdInt final int userId;
public final PackageIoUsage ioUsage;
@@ -583,15 +1038,17 @@
private @KillableState int mKillableState;
+ /** Must be called only after acquiring {@link mLock} */
PackageResourceUsage(@UserIdInt int userId, String packageName) {
this.packageName = packageName;
this.userId = userId;
this.ioUsage = new PackageIoUsage();
this.oldEnabledState = -1;
- this.mKillableState = KILLABLE_STATE_YES;
+ this.mKillableState = mDefaultNotKillablePackages.contains(packageName)
+ ? KILLABLE_STATE_NO : KILLABLE_STATE_YES;
}
- public void update(android.automotive.watchdog.IoOveruseStats internalStats) {
+ public void updateLocked(android.automotive.watchdog.IoOveruseStats internalStats) {
if (!internalStats.killableOnOveruse) {
/*
* Killable value specified in the internal stats is provided by the native daemon.
@@ -600,20 +1057,28 @@
* vendor services and doesn't reflect the user choices. Thus if the internal stats
* specify the application is not killable, the application is not safe-to-kill.
*/
- this.mKillableState = KILLABLE_STATE_NEVER;
+ mKillableState = KILLABLE_STATE_NEVER;
+ } else if (mKillableState == KILLABLE_STATE_NEVER) {
+ /*
+ * This case happens when a previously unsafe to kill system/vendor package was
+ * recently marked as safe-to-kill so update the old state to the default value.
+ */
+ mKillableState = mDefaultNotKillablePackages.contains(packageName)
+ ? KILLABLE_STATE_NO : KILLABLE_STATE_YES;
}
ioUsage.update(internalStats);
}
- public ResourceOveruseStats getResourceOveruseStatsWithIo() {
- IoOveruseStats ioOveruseStats = null;
- if (ioUsage.hasUsage()) {
- ioOveruseStats = ioUsage.getStatsBuilder().setKillableOnOveruse(
- mKillableState != PackageKillableState.KILLABLE_STATE_NEVER).build();
- }
+ public ResourceOveruseStats.Builder getResourceOveruseStatsBuilder() {
+ return new ResourceOveruseStats.Builder(packageName, UserHandle.of(userId));
+ }
- return new ResourceOveruseStats.Builder(packageName, UserHandle.of(userId))
- .setIoOveruseStats(ioOveruseStats).build();
+ public IoOveruseStats getIoOveruseStats() {
+ if (!ioUsage.hasUsage()) {
+ return null;
+ }
+ return ioUsage.getStatsBuilder().setKillableOnOveruse(
+ mKillableState != KILLABLE_STATE_NEVER).build();
}
public @KillableState int getKillableState() {
@@ -621,12 +1086,30 @@
}
public boolean setKillableState(boolean isKillable) {
- if (mKillableState == PackageKillableState.KILLABLE_STATE_NEVER) {
+ if (mKillableState == KILLABLE_STATE_NEVER) {
return false;
}
mKillableState = isKillable ? KILLABLE_STATE_YES : KILLABLE_STATE_NO;
return true;
}
+
+ public int syncAndFetchKillableStateLocked(int myComponentType) {
+ /*
+ * The killable state goes out-of-sync:
+ * 1. When the on-device safe-to-kill list is recently updated and the user package
+ * didn't have any resource usage so the native daemon didn't update the killable state.
+ * 2. When a package has no resource usage and is initialized outside of processing the
+ * latest resource usage stats.
+ */
+ if (myComponentType != ComponentType.THIRD_PARTY
+ && !mSafeToKillPackages.contains(packageName)) {
+ mKillableState = KILLABLE_STATE_NEVER;
+ } else if (mKillableState == KILLABLE_STATE_NEVER) {
+ mKillableState = mDefaultNotKillablePackages.contains(packageName)
+ ? KILLABLE_STATE_NO : KILLABLE_STATE_YES;
+ }
+ return mKillableState;
+ }
}
private static final class PackageIoUsage {
@@ -697,7 +1180,7 @@
@Override
public void binderDied() {
- Slogf.w(TAG, "Resource overuse listener%s (pid: %d) died",
+ Slogf.w(CarWatchdogService.TAG, "Resource overuse listener%s (pid: %d) died",
isListenerForSystem ? " for system" : "", pid);
onResourceOveruseListenerDeath(uid, isListenerForSystem);
unlinkToDeath();
diff --git a/service/src/com/android/car/watchdog/WatchdogProcessHandler.java b/service/src/com/android/car/watchdog/WatchdogProcessHandler.java
index 59e76c7..2754755 100644
--- a/service/src/com/android/car/watchdog/WatchdogProcessHandler.java
+++ b/service/src/com/android/car/watchdog/WatchdogProcessHandler.java
@@ -37,7 +37,6 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
-import com.android.car.CarLog;
import com.android.internal.annotations.GuardedBy;
import com.android.server.utils.Slogf;
@@ -47,11 +46,9 @@
* Handles clients' health status checking and reporting the statuses to the watchdog daemon.
*/
public final class WatchdogProcessHandler {
- private static final String TAG = CarLog.tagFor(CarWatchdogService.class);
private static final int[] ALL_TIMEOUTS =
{ TIMEOUT_CRITICAL, TIMEOUT_MODERATE, TIMEOUT_NORMAL };
- private final boolean mIsDebugEnabled;
private final ICarWatchdogServiceForSystem mWatchdogServiceForSystem;
private final CarWatchdogDaemonHelper mCarWatchdogDaemonHelper;
private final Handler mMainHandler = new Handler(Looper.getMainLooper());
@@ -85,8 +82,7 @@
private final SparseBooleanArray mStoppedUser = new SparseBooleanArray();
public WatchdogProcessHandler(ICarWatchdogServiceForSystem serviceImpl,
- CarWatchdogDaemonHelper daemonHelper, boolean isDebugEnabled) {
- mIsDebugEnabled = isDebugEnabled;
+ CarWatchdogDaemonHelper daemonHelper) {
mWatchdogServiceForSystem = serviceImpl;
mCarWatchdogDaemonHelper = daemonHelper;
}
@@ -98,8 +94,8 @@
mPingedClientMap.put(timeout, new SparseArray<ClientInfo>());
mClientCheckInProgress.put(timeout, false);
}
- if (mIsDebugEnabled) {
- Slogf.d(TAG, "WatchdogProcessHandler is initialized");
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "WatchdogProcessHandler is initialized");
}
}
@@ -136,14 +132,15 @@
synchronized (mLock) {
ArrayList<ClientInfo> clients = mClientMap.get(timeout);
if (clients == null) {
- Slogf.w(TAG, "Cannot register the client: invalid timeout");
+ Slogf.w(CarWatchdogService.TAG, "Cannot register the client: invalid timeout");
return;
}
IBinder binder = client.asBinder();
for (int i = 0; i < clients.size(); i++) {
ClientInfo clientInfo = clients.get(i);
if (binder == clientInfo.client.asBinder()) {
- Slogf.w(TAG, "Cannot register the client: the client(pid: %d) has been already "
+ Slogf.w(CarWatchdogService.TAG,
+ "Cannot register the client: the client(pid: %d) has been already "
+ "registered", clientInfo.pid);
return;
}
@@ -154,12 +151,13 @@
try {
clientInfo.linkToDeath();
} catch (RemoteException e) {
- Slogf.w(TAG, "Cannot register the client: linkToDeath to the client failed");
+ Slogf.w(CarWatchdogService.TAG,
+ "Cannot register the client: linkToDeath to the client failed");
return;
}
clients.add(clientInfo);
- if (mIsDebugEnabled) {
- Slogf.d(TAG, "Client(pid: %d) is registered", pid);
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "Client(pid: %d) is registered", pid);
}
}
}
@@ -177,14 +175,16 @@
}
clientInfo.unlinkToDeath();
clients.remove(i);
- if (mIsDebugEnabled) {
- Slogf.d(TAG, "Client(pid: %d) is unregistered", clientInfo.pid);
+ if (CarWatchdogService.DEBUG) {
+ Slogf.d(CarWatchdogService.TAG, "Client(pid: %d) is unregistered",
+ clientInfo.pid);
}
return;
}
}
}
- Slogf.w(TAG, "Cannot unregister the client: the client has not been registered before");
+ Slogf.w(CarWatchdogService.TAG,
+ "Cannot unregister the client: the client has not been registered before");
return;
}
@@ -297,8 +297,8 @@
try {
clientInfo.client.onCheckHealthStatus(clientInfo.sessionId, timeout);
} catch (RemoteException e) {
- Slogf.w(TAG, "Sending a ping message to client(pid: %d) failed: %s", clientInfo.pid,
- e);
+ Slogf.w(CarWatchdogService.TAG,
+ "Sending a ping message to client(pid: %d) failed: %s", clientInfo.pid, e);
synchronized (mLock) {
pingedClients.remove(clientInfo.sessionId);
}
@@ -348,7 +348,8 @@
try {
clientInfo.client.onPrepareProcessTermination();
} catch (RemoteException e) {
- Slogf.w(TAG, "Notifying onPrepareProcessTermination to client(pid: %d) failed: %s",
+ Slogf.w(CarWatchdogService.TAG,
+ "Notifying onPrepareProcessTermination to client(pid: %d) failed: %s",
clientInfo.pid, e);
}
}
@@ -357,7 +358,8 @@
mCarWatchdogDaemonHelper.tellCarWatchdogServiceAlive(
mWatchdogServiceForSystem, clientsNotResponding, sessionId);
} catch (RemoteException | RuntimeException e) {
- Slogf.w(TAG, "Cannot respond to car watchdog daemon (sessionId=%d): %s", sessionId, e);
+ Slogf.w(CarWatchdogService.TAG,
+ "Cannot respond to car watchdog daemon (sessionId=%d): %s", sessionId, e);
}
}
@@ -380,7 +382,7 @@
case TIMEOUT_NORMAL:
return "normal";
default:
- Slogf.w(TAG, "Unknown timeout value");
+ Slogf.w(CarWatchdogService.TAG, "Unknown timeout value");
return "unknown";
}
}
@@ -394,7 +396,7 @@
case TIMEOUT_NORMAL:
return 10000L;
default:
- Slogf.w(TAG, "Unknown timeout value");
+ Slogf.w(CarWatchdogService.TAG, "Unknown timeout value");
return 10000L;
}
}
@@ -416,7 +418,7 @@
@Override
public void binderDied() {
- Slogf.w(TAG, "Client(pid: %d) died", pid);
+ Slogf.w(CarWatchdogService.TAG, "Client(pid: %d) died", pid);
onClientDeath(client, timeout);
}
diff --git a/tests/CarEvsCameraPreviewApp/Android.bp b/tests/CarEvsCameraPreviewApp/Android.bp
index eb5f704..99f0505 100644
--- a/tests/CarEvsCameraPreviewApp/Android.bp
+++ b/tests/CarEvsCameraPreviewApp/Android.bp
@@ -13,6 +13,10 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
android_app {
name: "CarEvsCameraPreviewApp",
diff --git a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/jni/Android.bp b/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/jni/Android.bp
index 59cdedc..d06620a 100644
--- a/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/jni/Android.bp
+++ b/tests/CarEvsCameraPreviewApp/src/com/google/android/car/evs/jni/Android.bp
@@ -14,6 +14,10 @@
//
//
//#################################
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
cc_library_shared {
name: "libcarevsglrenderer_jni",
diff --git a/tests/CarLibTests/src/android/car/CarTelemetryManagerTest.java b/tests/CarLibTests/src/android/car/CarTelemetryManagerTest.java
new file mode 100644
index 0000000..90b5f7a
--- /dev/null
+++ b/tests/CarLibTests/src/android/car/CarTelemetryManagerTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Application;
+import android.car.telemetry.CarTelemetryManager;
+import android.car.testapi.CarTelemetryController;
+import android.car.testapi.FakeCar;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+import java.util.concurrent.Executor;
+
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class CarTelemetryManagerTest {
+ @Rule
+ public MockitoRule rule = MockitoJUnit.rule();
+
+ private static final Executor DIRECT_EXECUTOR = Runnable::run;
+
+ private CarTelemetryController mCarTelemetryController;
+ private CarTelemetryManager mCarTelemetryManager;
+ @Mock
+ private CarTelemetryManager.CarTelemetryResultsListener mListener;
+
+
+
+ @Before
+ public void setUp() {
+ Application context = ApplicationProvider.getApplicationContext();
+ FakeCar fakeCar = FakeCar.createFakeCar(context);
+ Car carApi = fakeCar.getCar();
+
+ mCarTelemetryManager =
+ (CarTelemetryManager) carApi.getCarManager(Car.CAR_TELEMETRY_SERVICE);
+ mCarTelemetryController = fakeCar.getCarTelemetryController();
+ }
+
+ @Test
+ public void setListener_shouldSucceed() {
+ mCarTelemetryManager.setListener(DIRECT_EXECUTOR, mListener);
+
+ assertThat(mCarTelemetryController.isListenerSet()).isTrue();
+ }
+
+ @Test
+ public void clearListener_shouldSucceed() {
+ mCarTelemetryManager.setListener(DIRECT_EXECUTOR, mListener);
+ mCarTelemetryManager.clearListener();
+
+ assertThat(mCarTelemetryController.isListenerSet()).isFalse();
+ }
+}
diff --git a/tests/CarSecurityPermissionTest/src/com/android/car/admin/CarDevicePolicyManagerPermissionTest.java b/tests/CarSecurityPermissionTest/src/com/android/car/admin/CarDevicePolicyManagerPermissionTest.java
index 51c1ea3..f1d4b52 100644
--- a/tests/CarSecurityPermissionTest/src/com/android/car/admin/CarDevicePolicyManagerPermissionTest.java
+++ b/tests/CarSecurityPermissionTest/src/com/android/car/admin/CarDevicePolicyManagerPermissionTest.java
@@ -74,4 +74,11 @@
assertThat(e.getMessage()).contains(CREATE_USERS);
assertThat(e.getMessage()).contains(MANAGE_USERS);
}
+ @Test
+ public void testStartUserInBackgrounPermission() throws Exception {
+ Exception e = expectThrows(SecurityException.class,
+ () -> mManager.startUserInBackground(UserHandle.of(100)));
+ assertThat(e.getMessage()).contains(CREATE_USERS);
+ assertThat(e.getMessage()).contains(MANAGE_USERS);
+ }
}
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/bluetooth_devices.xml b/tests/EmbeddedKitchenSinkApp/res/layout/bluetooth_devices.xml
new file mode 100644
index 0000000..c856233
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/bluetooth_devices.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:columnCount = "2">
+ <TextView
+ android:id="@+id/bluetooth_device"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"/>
+ <Button
+ android:id="@+id/bluetooth_pick_device"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/bluetooth_pick_device"/>
+ <TableLayout
+ android:id="@+id/PairedDeviceTable"
+ android:layout_width="409dp"
+ android:layout_height="190dp"
+ tools:layout_editor_absoluteX="1dp"
+ tools:layout_editor_absoluteY="1dp"/>
+</GridLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
index acbc2ad..01748a6 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/KitchenSinkActivity.java
@@ -47,6 +47,7 @@
import com.google.android.car.kitchensink.assistant.CarAssistantFragment;
import com.google.android.car.kitchensink.audio.AudioTestFragment;
import com.google.android.car.kitchensink.audio.CarAudioInputTestFragment;
+import com.google.android.car.kitchensink.bluetooth.BluetoothDeviceFragment;
import com.google.android.car.kitchensink.bluetooth.BluetoothHeadsetFragment;
import com.google.android.car.kitchensink.bluetooth.MapMceTestFragment;
import com.google.android.car.kitchensink.carboard.KeyboardTestFragment;
@@ -173,6 +174,7 @@
new FragmentMenuEntry("assistant", CarAssistantFragment.class),
new FragmentMenuEntry("audio", AudioTestFragment.class),
new FragmentMenuEntry("Audio Input", CarAudioInputTestFragment.class),
+ new FragmentMenuEntry("BT device", BluetoothDeviceFragment.class),
new FragmentMenuEntry("BT headset", BluetoothHeadsetFragment.class),
new FragmentMenuEntry("BT messaging", MapMceTestFragment.class),
new FragmentMenuEntry("carapi", CarApiTestFragment.class),
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothDeviceFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothDeviceFragment.java
new file mode 100644
index 0000000..c1de700
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothDeviceFragment.java
@@ -0,0 +1,162 @@
+/*
+ * 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.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothDevicePicker;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TableLayout;
+import android.widget.TableRow;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+
+import com.google.android.car.kitchensink.R;
+
+import java.util.Set;
+import java.util.concurrent.Executor;
+
+public class BluetoothDeviceFragment extends Fragment {
+ private static final String TAG = "CAR.BLUETOOTH.KS";
+ BluetoothAdapter mBluetoothAdapter;
+ BluetoothDevice mPickedDevice;
+ Executor mExecutor;
+ BluetoothDeviceTypeChecker mDeviceTypeChecker;
+
+ TextView mPickedDeviceText;
+ Button mDevicePicker;
+ TableLayout mTableLayout;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.bluetooth_devices, container, false);
+ mDeviceTypeChecker = new BluetoothDeviceTypeChecker(getContext(), true);
+ mDevicePicker = v.findViewById(R.id.bluetooth_pick_device);
+ mTableLayout = v.findViewById(R.id.PairedDeviceTable);
+ mExecutor = new ThreadPerTaskExecutor();
+
+ // Pick a bluetooth device
+ mDevicePicker.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ launchDevicePicker();
+ }
+ });
+
+ return v;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ checkAllDevices();
+ }
+
+ void launchDevicePicker() {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothDevicePicker.ACTION_DEVICE_SELECTED);
+ getContext().registerReceiver(mPickerReceiver, filter);
+
+ Intent intent = new Intent(BluetoothDevicePicker.ACTION_LAUNCH);
+ intent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+ getContext().startActivity(intent);
+ }
+
+ void checkAllDevices() {
+ BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+ if (bluetoothAdapter == null) {
+ Log.w(TAG, "Bluetooth Adapter not available");
+ return;
+ }
+ mTableLayout.removeAllViews();
+ Set<BluetoothDevice> bondedDevices = bluetoothAdapter.getBondedDevices();
+ Context context = getContext();
+ for (BluetoothDevice device : bondedDevices) {
+ TableRow row = new TableRow(context);
+ TextView deviceName = new TextView(context);
+ deviceName.setText(device.getName());
+ TextView deviceType = new TextView(context);
+ deviceType.setText(Boolean.toString(mDeviceTypeChecker.isIapDevice(device)));
+ row.addView(deviceName);
+ row.addView(deviceType);
+ mTableLayout.addView(row);
+ }
+ }
+
+ private void addDeviceToTable(BluetoothDevice device, String value) {
+ getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ Context context = getContext();
+ TableRow row = new TableRow(context);
+ TextView deviceName = new TextView(context);
+ TextView deviceValue = new TextView(context);
+ deviceName.setText(device.getName());
+ deviceValue.setText(value);
+ row.addView(deviceName);
+ row.addView(deviceValue);
+ mTableLayout.addView(row);
+ }
+ });
+ }
+
+ private final BroadcastReceiver mPickerReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ Log.v(TAG, "mPickerReceiver got " + action);
+
+ if (BluetoothDevicePicker.ACTION_DEVICE_SELECTED.equals(action)) {
+ final BluetoothDevice device =
+ intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ Log.v(TAG, "mPickerReceiver got " + device);
+ if (device == null) {
+ Toast.makeText(getContext(), "No device selected", Toast.LENGTH_SHORT).show();
+ return;
+ }
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ addDeviceToTable(device,
+ Boolean.toString(mDeviceTypeChecker.isIapDevice(device)));
+ Log.w(TAG, "Is iAP" + mDeviceTypeChecker.isIapDevice(device));
+ }
+ });
+ Log.w(TAG, "Dispatched");
+ getContext().unregisterReceiver(mPickerReceiver);
+ }
+ }
+ };
+
+ private class ThreadPerTaskExecutor implements Executor {
+ public void execute(Runnable r) {
+ new Thread(r).start();
+ }
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothDeviceTypeChecker.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothDeviceTypeChecker.java
new file mode 100644
index 0000000..1523634
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/BluetoothDeviceTypeChecker.java
@@ -0,0 +1,137 @@
+/*
+ * 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.bluetooth.BluetoothDevice;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+
+public class BluetoothDeviceTypeChecker {
+ private static final ParcelUuid IAP_UUID =
+ ParcelUuid.fromString("00000000-deca-fade-deca-deafdecacafe");
+ private static final int MAX_SECONDS_TO_BLOCK = 10;
+ private static final String TAG = "BluetoothDeviceTypeChecker";
+
+ private final Context mContext;
+ private final boolean mAllowBlocking;
+ private final Object mLock = new Object();
+
+ CompletableFuture<ParcelUuid[]> mDeviceUUidsFuture;
+ BluetoothDevice mBluetoothDevice;
+
+ /**
+ * BluetoothDeviceTypeChecker
+ * Class designed to fetch and check UUID records for matches based on either cached or live
+ * records. Live records are fetched if allowBlocking is enabled and there is nothing in the
+ * cache. Paired devices should always have records in the cache if the BluetoothAdapter is on.
+ *
+ * @param context The context on which to receive updates if live records are necessary
+ * @param allowBlocking If cached SDP records are not available allow methods to block in a
+ * best effort of acquiring them.
+ */
+ public BluetoothDeviceTypeChecker(Context context, boolean allowBlocking) {
+ mContext = context;
+ if (mContext != null) {
+ mAllowBlocking = allowBlocking;
+ } else {
+ mAllowBlocking = false;
+ }
+ }
+
+ /**
+ * isIapDevice
+ * Check if device is indicating support for iAP
+ * @param device
+ * @return
+ */
+ public boolean isIapDevice(BluetoothDevice device) {
+ return deviceContainsUuid(device, IAP_UUID);
+ }
+
+ /**
+ * deviceContainsUuid
+ * Check if device contains a specific UUID record
+ * @param device to perform a lookup on
+ * @param uuid to check in the records
+ * @return
+ */
+ public boolean deviceContainsUuid(BluetoothDevice device, ParcelUuid uuid) {
+ if (device == null) return false;
+ if (uuid == null) return false;
+
+ synchronized (mLock) {
+ mBluetoothDevice = device;
+ ParcelUuid[] uuidsArray = device.getUuids();
+ if (mAllowBlocking && (uuidsArray == null || uuidsArray.length == 0)) {
+ uuidsArray = blockingFetchUuids(device);
+ }
+ if (uuidsArray == null || uuidsArray.length == 0) {
+ return false;
+ }
+ for (int i = 0; i < uuidsArray.length; i++) {
+ if (uuid.equals(uuidsArray[i])) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /*
+ Perform a blocking fetch of the UUIDs on specified BluetoothDevice
+ */
+ private ParcelUuid[] blockingFetchUuids(BluetoothDevice device) {
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(BluetoothDevice.ACTION_UUID);
+ mContext.registerReceiver(mUuidReceiver, filter);
+ mDeviceUUidsFuture = new CompletableFuture<>();
+ if (!device.fetchUuidsWithSdp()) {
+ Log.w(TAG, "fetching UUIDs failed.");
+ mContext.unregisterReceiver(mUuidReceiver);
+ return new ParcelUuid[0];
+ }
+ try {
+ return mDeviceUUidsFuture.get(MAX_SECONDS_TO_BLOCK, TimeUnit.SECONDS);
+ } catch (Exception e) {
+ mContext.unregisterReceiver(mUuidReceiver);
+ return new ParcelUuid[0];
+ }
+ }
+
+ /*
+ Broadcast receiver on which to receive updates to Bluetooth UUID records.
+ */
+ private BroadcastReceiver mUuidReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ BluetoothDevice device =
+ intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
+ if (mBluetoothDevice.equals(device)
+ && BluetoothDevice.ACTION_UUID.equals(intent.getAction())) {
+ mDeviceUUidsFuture.complete(device.getUuids());
+ mContext.unregisterReceiver(this);
+ }
+ }
+ };
+}
diff --git a/tests/NetworkPreferenceApp/res/layout/manager.xml b/tests/NetworkPreferenceApp/res/layout/manager.xml
index 9b545ee..fe06102 100644
--- a/tests/NetworkPreferenceApp/res/layout/manager.xml
+++ b/tests/NetworkPreferenceApp/res/layout/manager.xml
@@ -212,6 +212,26 @@
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/button_apply_wifi_configuration"/>
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:weightSum="2">
+ <Button
+ style="@style/Button"
+ android:id="@+id/connectToOemPaidWifiButton"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/button_connect_to_oem_paid_wifi"/>
+ <Button
+ style="@style/Button"
+ android:id="@+id/connectToOemPrivateWifiButton"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:text="@string/button_connect_to_oem_private_wifi"/>
+ </LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
diff --git a/tests/NetworkPreferenceApp/res/values/strings.xml b/tests/NetworkPreferenceApp/res/values/strings.xml
index 38deeae..3b333e7 100644
--- a/tests/NetworkPreferenceApp/res/values/strings.xml
+++ b/tests/NetworkPreferenceApp/res/values/strings.xml
@@ -35,9 +35,11 @@
<string name="label_metric_total_oem_managed" translatable="false">Total OEM Managed</string>
<!-- Wifi PANS SSID control labels -->
- <string name="oem_paid_wifi_ssids_text" translatable="false">Wifi SSIDs with OEM_PAID capability (comma separated)</string>
- <string name="oem_private_wifi_ssids_text" translatable="false">Wifi SSIDs with OEM_PRIVATE capability (comma separated)</string>
- <string name="button_apply_wifi_configuration" translatable="false">Apply WiFi changes</string>
+ <string name="oem_paid_wifi_ssids_text" translatable="false">Wifi SSIDs with OEM_PAID capability (ssid1[:password],ssid2[:password2],...))</string>
+ <string name="oem_private_wifi_ssids_text" translatable="false">Wifi SSIDs with OEM_PRIVATE capability (ssid1[:password],ssid2[:password2],...))</string>
+ <string name="button_apply_wifi_configuration" translatable="false">Apply WiFi suggestions</string>
+ <string name="button_connect_to_oem_paid_wifi" translatable="false">Connect to OEM_PAID wifi</string>
+ <string name="button_connect_to_oem_private_wifi" translatable="false">Connect to OEM_PRIVATE wifi</string>
<string name="no" translatable="false">No</string>
<string name="on" translatable="false">On</string>
diff --git a/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/fragments/ManagerFragment.java b/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/fragments/ManagerFragment.java
index 3ffb851..dc396c0 100644
--- a/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/fragments/ManagerFragment.java
+++ b/tests/NetworkPreferenceApp/src/com/google/android/car/networking/preferenceupdater/fragments/ManagerFragment.java
@@ -15,13 +15,18 @@
*/
package com.google.android.car.networking.preferenceupdater.fragments;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PAID;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_OEM_PRIVATE;
+import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_NO_FALLBACK;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PAID_ONLY;
import static android.net.OemNetworkPreferences.OEM_NETWORK_PREFERENCE_OEM_PRIVATE_ONLY;
import android.content.Context;
+import android.net.ConnectivityManager;
import android.net.NetworkIdentity;
+import android.net.NetworkRequest;
import android.net.NetworkTemplate;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
@@ -63,6 +68,7 @@
private OemNetworkPreferencesAdapter mOemNetworkPreferencesAdapter;
private CarDriverDistractionManagerAdapter mCarDriverDistractionManagerAdapter;
private WifiManager mWifiManager;
+ private ConnectivityManager mConnectivityManager;
// Metric Display components
private MetricDisplay mMetricDisplay;
@@ -94,6 +100,8 @@
private Button mApplyConfigurationBtn;
private Button mResetNetworkPreferencesBtn;
private Button mApplyWifiCapabilitiesBtn;
+ private Button mConnectToOemPaidWifiBtn;
+ private Button mConnectToOemPrivateWifiBtn;
// Wifi SSIDs
private EditText mOEMPaidWifiSSIDsEditText;
@@ -122,6 +130,7 @@
mMetricDisplay.startWatching();
mWifiManager = context.getSystemService(WifiManager.class);
+ mConnectivityManager = context.getSystemService(ConnectivityManager.class);
return v;
}
@@ -146,6 +155,8 @@
mApplyConfigurationBtn = v.findViewById(R.id.applyConfigurationBtn);
mResetNetworkPreferencesBtn = v.findViewById(R.id.resetNetworkPreferencesBtn);
mApplyWifiCapabilitiesBtn = v.findViewById(R.id.applyWifiCapabilitiesButton);
+ mConnectToOemPaidWifiBtn = v.findViewById(R.id.connectToOemPaidWifiButton);
+ mConnectToOemPrivateWifiBtn = v.findViewById(R.id.connectToOemPrivateWifiButton);
// Since our Metric Display is going to be alive, we want to pass our TextView components
// into MetricDisplay instance to simplify refresh logic.
mOemPaidRxBytesTextView = v.findViewById(R.id.oemPaidRxBytesTextView);
@@ -184,6 +195,29 @@
(buttonView, isChecked) ->
mPersonalStorage.saveReapplyPansOnBootCompleteState(true));
mResetNetworkPreferencesBtn.setOnClickListener(view -> resetNetworkPreferences());
+ mConnectToOemPaidWifiBtn.setOnClickListener(view -> onConnectWifiBtnClick(true));
+ mConnectToOemPrivateWifiBtn.setOnClickListener(view -> onConnectWifiBtnClick(false));
+ }
+
+ private void onConnectWifiBtnClick(boolean isOemPaid) {
+ try {
+ mConnectivityManager.requestNetwork(
+ new NetworkRequest.Builder()
+ .addTransportType(TRANSPORT_WIFI)
+ .addCapability(
+ isOemPaid
+ ? NET_CAPABILITY_OEM_PAID
+ : NET_CAPABILITY_OEM_PRIVATE)
+ .build(),
+ new ConnectivityManager.NetworkCallback());
+ } catch (Exception ex) {
+ Toast.makeText(getActivity(), ex.toString(), Toast.LENGTH_SHORT).show();
+ String msg =
+ String.format(
+ "Attempt to connect to wifi with %s cabaility failed!",
+ isOemPaid ? "OEM_PAID" : "OEM_PRIVATE");
+ Log.e(TAG, msg, ex);
+ }
}
private void resetNetworkPreferences() {
@@ -217,28 +251,34 @@
mCurrentPANSStatusTextView.setText(status ? "Yes" : "No");
}
+ private WifiNetworkSuggestion buildWifiSuggestion(String ssid, boolean isOemPaid) {
+ WifiNetworkSuggestion.Builder builder = new WifiNetworkSuggestion.Builder();
+ String[] elements = ssid.split(":");
+ builder.setSsid(WifiInfo.sanitizeSsid(elements[0]));
+ if (elements.length > 1) {
+ builder.setWpa2Passphrase(elements[1]);
+ }
+ if (isOemPaid) {
+ builder.setOemPaid(true);
+ } else {
+ builder.setOemPrivate(true);
+ }
+ return builder.build();
+ }
+
private void onApplyWifiCapabilitiesBtnClick() {
Log.d(TAG, "Applying WiFi settings");
Set<String> ssidsWithOemPaid = Utils.toSet(mOEMPaidWifiSSIDsEditText.getText().toString());
Set<String> ssidsWithOemPrivate =
Utils.toSet(mOEMPrivateWifiSSIDsEditText.getText().toString());
-
try {
ArrayList<WifiNetworkSuggestion> list = new ArrayList<>();
for (String ssid : ssidsWithOemPaid) {
- list.add(
- new WifiNetworkSuggestion.Builder()
- .setSsid(WifiInfo.sanitizeSsid(ssid))
- .setOemPaid(true)
- .build());
+ list.add(buildWifiSuggestion(ssid, true));
}
for (String ssid : ssidsWithOemPrivate) {
- list.add(
- new WifiNetworkSuggestion.Builder()
- .setSsid(WifiInfo.sanitizeSsid(ssid))
- .setOemPrivate(true)
- .build());
+ list.add(buildWifiSuggestion(ssid, false));
}
mWifiManager.removeNetworkSuggestions(new ArrayList<>());
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 b3973aa..8ce4c27 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
@@ -27,6 +27,7 @@
import android.car.admin.CarDevicePolicyManager;
import android.car.admin.CreateUserResult;
import android.car.admin.RemoveUserResult;
+import android.car.admin.StartUserInBackgroundResult;
import android.content.Context;
import android.content.pm.UserInfo;
import android.os.PowerManager;
@@ -63,11 +64,10 @@
UserInfo user = createUser();
Log.d(TAG, "removing user " + user.toFullString());
-
RemoveUserResult result = mCarDpm.removeUser(user.getUserHandle());
Log.d(TAG, "result: " + result);
- assertWithMessage("Failed to remove user%s: %s", user.toFullString(), result)
+ assertWithMessage("Result of removeUser %s: %s", user.toFullString(), result)
.that(result.isSuccess()).isTrue();
}
@@ -122,7 +122,6 @@
String name = "CarDevicePolicyManagerTest.testCreateUser";
int type = CarDevicePolicyManager.USER_TYPE_REGULAR;
Log.d(TAG, "creating new user with name " + name + " and type " + type);
-
CreateUserResult result = mCarDpm.createUser(name, type);
Log.d(TAG, "result: " + result);
UserHandle user = result.getUserHandle();
@@ -138,6 +137,25 @@
}
@Test
+ public void testStartUserInBackground() throws Exception {
+ assertInitialUserIsAdmin();
+
+ UserInfo user = createUser();
+ Log.d(TAG, "starting user in background " + user.toFullString());
+ StartUserInBackgroundResult result = mCarDpm.startUserInBackground(user.getUserHandle());
+ Log.d(TAG, "result: " + result);
+
+ try {
+ assertWithMessage("Result of startUserInBackground %s: %s", user.toFullString(), result)
+ .that(result.isSuccess()).isTrue();
+ } finally {
+ // Clean up the created user.
+ removeUser(user.id);
+ waitForUserRemoval(user.id);
+ }
+ }
+
+ @Test
public void testLockNow_safe() throws Exception {
lockNowTest(/* safe= */ true);
}
diff --git a/tests/carservice_test/src/com/android/car/audio/CarVolumeGroupTest.java b/tests/carservice_test/src/com/android/car/audio/CarVolumeGroupTest.java
deleted file mode 100644
index cb78b50..0000000
--- a/tests/carservice_test/src/com/android/car/audio/CarVolumeGroupTest.java
+++ /dev/null
@@ -1,593 +0,0 @@
-/*
- * Copyright (C) 2019 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.google.common.truth.Truth.assertThat;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-import static org.testng.Assert.expectThrows;
-
-import android.annotation.UserIdInt;
-import android.app.ActivityManager;
-import android.car.test.mocks.AbstractExtendedMockitoTestCase;
-import android.os.UserHandle;
-import android.util.SparseBooleanArray;
-import android.util.SparseIntArray;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import com.google.common.primitives.Ints;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mockito;
-
-import java.util.List;
-
-@RunWith(AndroidJUnit4.class)
-public class CarVolumeGroupTest extends AbstractExtendedMockitoTestCase{
- private static final int STEP_VALUE = 2;
- private static final int MIN_GAIN = 0;
- private static final int MAX_GAIN = 5;
- private static final int DEFAULT_GAIN = 0;
- private static final int TEST_USER_10 = 10;
- private static final int TEST_USER_11 = 11;
- private static final String OTHER_ADDRESS = "other_address";
- private static final String MEDIA_DEVICE_ADDRESS = "music";
- private static final String NAVIGATION_DEVICE_ADDRESS = "navigation";
-
-
- private CarAudioDeviceInfo mMediaDevice;
- private CarAudioDeviceInfo mNavigationDevice;
-
- @Override
- protected void onSessionBuilder(CustomMockitoSessionBuilder session) {
- session.spyStatic(ActivityManager.class);
- }
-
- @Before
- public void setUp() {
- mMediaDevice = generateCarAudioDeviceInfo(MEDIA_DEVICE_ADDRESS);
- mNavigationDevice = generateCarAudioDeviceInfo(NAVIGATION_DEVICE_ADDRESS);
- }
-
- @Test
- public void bind_associatesDeviceAddresses() {
- CarVolumeGroup carVolumeGroup =
- getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
- carVolumeGroup.bind(CarAudioContext.MUSIC, mMediaDevice);
- assertEquals(1, carVolumeGroup.getAddresses().size());
-
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, mNavigationDevice);
-
- List<String> addresses = carVolumeGroup.getAddresses();
- assertEquals(2, addresses.size());
- assertTrue(addresses.contains(MEDIA_DEVICE_ADDRESS));
- assertTrue(addresses.contains(NAVIGATION_DEVICE_ADDRESS));
- }
-
- @Test
- public void bind_checksForSameStepSize() {
- CarVolumeGroup carVolumeGroup =
- getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
- carVolumeGroup.bind(CarAudioContext.MUSIC, mMediaDevice);
- CarAudioDeviceInfo differentStepValueDevice = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE + 1,
- MIN_GAIN, MAX_GAIN);
-
- IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
- () -> carVolumeGroup.bind(CarAudioContext.NAVIGATION, differentStepValueDevice));
- assertThat(thrown).hasMessageThat()
- .contains("Gain controls within one group must have same step value");
- }
-
- @Test
- public void bind_updatesMinGainToSmallestValue() {
- CarVolumeGroup carVolumeGroup =
- getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
- CarAudioDeviceInfo largestMinGain = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, 1, 10, 10);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, largestMinGain);
-
- assertEquals(0, carVolumeGroup.getMaxGainIndex());
-
- CarAudioDeviceInfo smallestMinGain = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, 1, 2, 10);
- carVolumeGroup.bind(CarAudioContext.NOTIFICATION, smallestMinGain);
-
- assertEquals(8, carVolumeGroup.getMaxGainIndex());
-
- CarAudioDeviceInfo middleMinGain = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, 1, 7, 10);
- carVolumeGroup.bind(CarAudioContext.VOICE_COMMAND, middleMinGain);
-
- assertEquals(8, carVolumeGroup.getMaxGainIndex());
- }
-
- @Test
- public void bind_updatesMaxGainToLargestValue() {
- CarVolumeGroup carVolumeGroup =
- getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
- CarAudioDeviceInfo smallestMaxGain = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, 1, 1, 5);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, smallestMaxGain);
-
- assertEquals(4, carVolumeGroup.getMaxGainIndex());
-
- CarAudioDeviceInfo largestMaxGain = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, 1, 1, 10);
- carVolumeGroup.bind(CarAudioContext.NOTIFICATION, largestMaxGain);
-
- assertEquals(9, carVolumeGroup.getMaxGainIndex());
-
- CarAudioDeviceInfo middleMaxGain = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, 1, 1, 7);
- carVolumeGroup.bind(CarAudioContext.VOICE_COMMAND, middleMaxGain);
-
- assertEquals(9, carVolumeGroup.getMaxGainIndex());
- }
-
- @Test
- public void bind_checksThatTheSameContextIsNotBoundTwice() {
- CarVolumeGroup carVolumeGroup =
- getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, mMediaDevice);
-
- IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
- () -> carVolumeGroup.bind(CarAudioContext.NAVIGATION, mMediaDevice));
- assertThat(thrown).hasMessageThat()
- .contains("Context NAVIGATION has already been bound to " + MEDIA_DEVICE_ADDRESS);
- }
-
- @Test
- public void getContexts_returnsAllContextsBoundToVolumeGroup() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
- int[] contexts = carVolumeGroup.getContexts();
-
- assertEquals(6, contexts.length);
-
- List<Integer> contextsList = Ints.asList(contexts);
- assertTrue(contextsList.contains(CarAudioContext.MUSIC));
- assertTrue(contextsList.contains(CarAudioContext.CALL));
- assertTrue(contextsList.contains(CarAudioContext.CALL_RING));
- assertTrue(contextsList.contains(CarAudioContext.NAVIGATION));
- assertTrue(contextsList.contains(CarAudioContext.ALARM));
- assertTrue(contextsList.contains(CarAudioContext.NOTIFICATION));
- }
-
- @Test
- public void getContextsForAddress_returnsContextsBoundToThatAddress() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
- List<Integer> contextsList = carVolumeGroup.getContextsForAddress(MEDIA_DEVICE_ADDRESS);
-
- assertThat(contextsList).containsExactly(CarAudioContext.MUSIC,
- CarAudioContext.CALL, CarAudioContext.CALL_RING);
- }
-
- @Test
- public void getContextsForAddress_returnsEmptyArrayIfAddressNotBound() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
- List<Integer> contextsList = carVolumeGroup.getContextsForAddress(OTHER_ADDRESS);
-
- assertThat(contextsList).isEmpty();
- }
-
- @Test
- public void getCarAudioDeviceInfoForAddress_returnsExpectedDevice() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
- CarAudioDeviceInfo actualDevice = carVolumeGroup.getCarAudioDeviceInfoForAddress(
- MEDIA_DEVICE_ADDRESS);
-
- assertEquals(mMediaDevice, actualDevice);
- }
-
- @Test
- public void getCarAudioDeviceInfoForAddress_returnsNullIfAddressNotBound() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
- CarAudioDeviceInfo actualDevice = carVolumeGroup.getCarAudioDeviceInfoForAddress(
- OTHER_ADDRESS);
-
- assertNull(actualDevice);
- }
-
- @Test
- public void setCurrentGainIndex_setsGainOnAllBoundDevices() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
- carVolumeGroup.setCurrentGainIndex(2);
- verify(mMediaDevice).setCurrentGain(4);
- verify(mNavigationDevice).setCurrentGain(4);
- }
-
- @Test
- public void setCurrentGainIndex_updatesCurrentGainIndex() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
- carVolumeGroup.setCurrentGainIndex(2);
-
- assertEquals(2, carVolumeGroup.getCurrentGainIndex());
- }
-
- @Test
- public void setCurrentGainIndex_checksNewGainIsAboveMin() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
- IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
- () -> carVolumeGroup.setCurrentGainIndex(-1));
- assertThat(thrown).hasMessageThat().contains("Gain out of range (0:5) -2index -1");
- }
-
- @Test
- public void setCurrentGainIndex_checksNewGainIsBelowMax() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
- IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
- () -> carVolumeGroup.setCurrentGainIndex(3));
- assertThat(thrown).hasMessageThat().contains("Gain out of range (0:5) 6index 3");
- }
-
- @Test
- public void getMinGainIndex_alwaysReturnsZero() {
- CarVolumeGroup carVolumeGroup =
- getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
- CarAudioDeviceInfo minGainPlusOneDevice = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, 10, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, minGainPlusOneDevice);
-
- assertEquals(0, carVolumeGroup.getMinGainIndex());
-
- CarAudioDeviceInfo minGainDevice = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, 1, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NOTIFICATION, minGainDevice);
-
- assertEquals(0, carVolumeGroup.getMinGainIndex());
- }
-
- @Test
- public void loadVolumesSettingsForUser_setsCurrentGainIndexForUser() {
- CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
- .setGainIndexForUser(TEST_USER_10, 2)
- .setGainIndexForUser(TEST_USER_11, 0)
- .build();
-
- CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, false);
-
- CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
- carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
-
- assertEquals(2, carVolumeGroup.getCurrentGainIndex());
-
- carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_11);
-
- assertEquals(0, carVolumeGroup.getCurrentGainIndex());
- }
-
- @Test
- public void loadVolumesSettingsForUser_setsCurrentGainIndexToDefault() {
- CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
- .setGainIndexForUser(TEST_USER_10, 10)
- .build();
- CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, false);
-
- CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-
- carVolumeGroup.setCurrentGainIndex(2);
-
- assertEquals(2, carVolumeGroup.getCurrentGainIndex());
-
- carVolumeGroup.loadVolumesSettingsForUser(0);
-
- assertEquals(0, carVolumeGroup.getCurrentGainIndex());
- }
-
- @Test
- public void setCurrentGainIndex_setsCurrentGainIndexForUser() {
- CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
- .setGainIndexForUser(TEST_USER_11, 2)
- .build();
- CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, false);
-
- CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
- carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_11);
-
- carVolumeGroup.setCurrentGainIndex(MIN_GAIN);
-
- verify(settings).storeVolumeGainIndexForUser(TEST_USER_11, 0, 0, MIN_GAIN);
- }
-
- @Test
- public void setCurrentGainIndex_setsCurrentGainIndexForDefaultUser() {
- CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
- .setGainIndexForUser(UserHandle.USER_CURRENT, 2)
- .build();
- CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, false);
-
- CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-
- carVolumeGroup.setCurrentGainIndex(MIN_GAIN);
-
- verify(settings)
- .storeVolumeGainIndexForUser(UserHandle.USER_CURRENT, 0, 0, MIN_GAIN);
- }
-
- @Test
- public void bind_setsCurrentGainIndexToStoredGainIndex() {
- CarVolumeGroup carVolumeGroup =
- getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
- CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-
-
- assertEquals(2, carVolumeGroup.getCurrentGainIndex());
- }
-
- @Test
- public void getAddressForContext_returnsExpectedDeviceAddress() {
- CarVolumeGroup carVolumeGroup =
- getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
- carVolumeGroup.bind(CarAudioContext.MUSIC, mMediaDevice);
-
- String mediaAddress = carVolumeGroup.getAddressForContext(CarAudioContext.MUSIC);
-
- assertEquals(mMediaDevice.getAddress(), mediaAddress);
- }
-
- @Test
- public void getAddressForContext_returnsNull() {
- CarVolumeGroup carVolumeGroup =
- getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
- String nullAddress = carVolumeGroup.getAddressForContext(CarAudioContext.MUSIC);
-
- assertNull(nullAddress);
- }
-
- @Test
- public void isMuted_whenDefault_returnsFalse() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
-
- assertThat(carVolumeGroup.isMuted()).isFalse();
- }
-
- @Test
- public void isMuted_afterMuting_returnsTrue() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
- carVolumeGroup.setMute(true);
-
- assertThat(carVolumeGroup.isMuted()).isTrue();
- }
-
- @Test
- public void isMuted_afterUnMuting_returnsFalse() {
- CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
- carVolumeGroup.setMute(false);
-
- assertThat(carVolumeGroup.isMuted()).isFalse();
- }
-
- @Test
- public void setMute_withMutedState_storesValueToSetting() {
- CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
- .setMuteForUser(TEST_USER_10, false)
- .setIsPersistVolumeGroupEnabled(true)
- .build();
- CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, true);
- CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
- carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
-
- carVolumeGroup.setMute(true);
-
- verify(settings)
- .storeVolumeGroupMuteForUser(TEST_USER_10, 0, 0, true);
- }
-
- @Test
- public void setMute_withUnMutedState_storesValueToSetting() {
- CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
- .setMuteForUser(TEST_USER_10, false)
- .setIsPersistVolumeGroupEnabled(true)
- .build();
- CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, true);
- CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
- carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
-
- carVolumeGroup.setMute(false);
-
- verify(settings)
- .storeVolumeGroupMuteForUser(TEST_USER_10, 0, 0, false);
- }
-
- @Test
- public void loadVolumesSettingsForUser_withMutedState_loadsMuteStateForUser() {
- CarVolumeGroup carVolumeGroup = getVolumeGroupWithMuteForUser(true, true,
- TEST_USER_10);
- CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-
- carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
-
- assertEquals(true, carVolumeGroup.isMuted());
- }
-
- @Test
- public void loadVolumesSettingsForUser_withDisabledUseVolumeGroupMute_doesNotLoadMute() {
- CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
- .setMuteForUser(TEST_USER_10, true)
- .setIsPersistVolumeGroupEnabled(true)
- .build();
- CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, false);
- CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-
- carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
-
- assertEquals(false, carVolumeGroup.isMuted());
- }
-
- @Test
- public void loadVolumesSettingsForUser_withUnMutedState_loadsMuteStateForUser() {
- CarVolumeGroup carVolumeGroup = getVolumeGroupWithMuteForUser(false, true,
- TEST_USER_10);
- CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-
- carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
-
- assertEquals(false, carVolumeGroup.isMuted());
- }
-
- @Test
- public void loadVolumesSettingsForUser_withMutedStateAndNoPersist_returnsDefaultMuteState() {
- CarVolumeGroup carVolumeGroup = getVolumeGroupWithMuteForUser(true, false,
- TEST_USER_10);
- CarAudioDeviceInfo deviceInfo = generateCarAudioDeviceInfo(
- NAVIGATION_DEVICE_ADDRESS, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, deviceInfo);
-
- carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
-
- assertEquals(false, carVolumeGroup.isMuted());
- }
-
- CarVolumeGroup getVolumeGroupWithGainAndUser(int gain, @UserIdInt int userId) {
- CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
- .setGainIndexForUser(userId, gain)
- .build();
- CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, false);
-
- return carVolumeGroup;
- }
-
- CarVolumeGroup getVolumeGroupWithMuteForUser(boolean isMuted, boolean persistMute,
- @UserIdInt int userId) {
- CarAudioSettings settings = new CarVolumeGroupSettingsBuilder(0, 0)
- .setMuteForUser(userId, isMuted)
- .setIsPersistVolumeGroupEnabled(persistMute)
- .build();
- CarVolumeGroup carVolumeGroup = new CarVolumeGroup(0, 0, settings, true);
-
- return carVolumeGroup;
- }
-
- private CarVolumeGroup testVolumeGroupSetup() {
- CarVolumeGroup carVolumeGroup =
- getVolumeGroupWithGainAndUser(2, UserHandle.USER_CURRENT);
-
- carVolumeGroup.bind(CarAudioContext.MUSIC, mMediaDevice);
- carVolumeGroup.bind(CarAudioContext.CALL, mMediaDevice);
- carVolumeGroup.bind(CarAudioContext.CALL_RING, mMediaDevice);
-
- carVolumeGroup.bind(CarAudioContext.NAVIGATION, mNavigationDevice);
- carVolumeGroup.bind(CarAudioContext.ALARM, mNavigationDevice);
- carVolumeGroup.bind(CarAudioContext.NOTIFICATION, mNavigationDevice);
-
- return carVolumeGroup;
- }
-
- private CarAudioDeviceInfo generateCarAudioDeviceInfo(String address) {
- return generateCarAudioDeviceInfo(address, STEP_VALUE, MIN_GAIN, MAX_GAIN);
- }
-
- private CarAudioDeviceInfo generateCarAudioDeviceInfo(String address, int stepValue,
- int minGain, int maxGain) {
- CarAudioDeviceInfo cadiMock = Mockito.mock(CarAudioDeviceInfo.class);
- when(cadiMock.getStepValue()).thenReturn(stepValue);
- when(cadiMock.getDefaultGain()).thenReturn(DEFAULT_GAIN);
- when(cadiMock.getMaxGain()).thenReturn(maxGain);
- when(cadiMock.getMinGain()).thenReturn(minGain);
- when(cadiMock.getAddress()).thenReturn(address);
- return cadiMock;
- }
-
- private static final class CarVolumeGroupSettingsBuilder {
- private SparseIntArray mStoredGainIndexes = new SparseIntArray();
- private SparseBooleanArray mStoreMuteStates = new SparseBooleanArray();
- private boolean mPersistMute;
- private final int mZoneId;
- private final int mGroupId;
-
- CarVolumeGroupSettingsBuilder(int zoneId, int groupId) {
- mZoneId = zoneId;
- mGroupId = groupId;
- }
-
- CarVolumeGroupSettingsBuilder setGainIndexForUser(@UserIdInt int userId, int gainIndex) {
- mStoredGainIndexes.put(userId, gainIndex);
- return this;
- }
-
- CarVolumeGroupSettingsBuilder setMuteForUser(@UserIdInt int userId, boolean mute) {
- mStoreMuteStates.put(userId, mute);
- return this;
- }
-
- CarVolumeGroupSettingsBuilder setIsPersistVolumeGroupEnabled(boolean persistMute) {
- mPersistMute = persistMute;
- return this;
- }
-
- CarAudioSettings build() {
- CarAudioSettings settingsMock = Mockito.mock(CarAudioSettings.class);
- for (int storeIndex = 0; storeIndex < mStoredGainIndexes.size(); storeIndex++) {
- int gainUserId = mStoredGainIndexes.keyAt(storeIndex);
- when(settingsMock
- .getStoredVolumeGainIndexForUser(gainUserId, mZoneId,
- mGroupId)).thenReturn(mStoredGainIndexes.get(gainUserId, DEFAULT_GAIN));
- }
- for (int muteIndex = 0; muteIndex < mStoreMuteStates.size(); muteIndex++) {
- int muteUserId = mStoreMuteStates.keyAt(muteIndex);
- when(settingsMock.getVolumeGroupMuteForUser(muteUserId, mZoneId, mGroupId))
- .thenReturn(mStoreMuteStates.get(muteUserId, false));
- when(settingsMock.isPersistVolumeGroupMuteEnabled(muteUserId))
- .thenReturn(mPersistMute);
- }
- return settingsMock;
- }
- }
-}
diff --git a/tests/carservice_unit_test/src/android/car/admin/CarDevicePolicyManagerUnitTest.java b/tests/carservice_unit_test/src/android/car/admin/CarDevicePolicyManagerUnitTest.java
index 19ffc0b..230dda9 100644
--- a/tests/carservice_unit_test/src/android/car/admin/CarDevicePolicyManagerUnitTest.java
+++ b/tests/carservice_unit_test/src/android/car/admin/CarDevicePolicyManagerUnitTest.java
@@ -32,6 +32,7 @@
import android.car.test.util.UserTestingHelper.UserInfoBuilder;
import android.car.user.UserCreationResult;
import android.car.user.UserRemovalResult;
+import android.car.user.UserStartResult;
import android.content.pm.UserInfo;
import android.os.RemoteException;
import android.os.UserHandle;
@@ -125,6 +126,34 @@
assertThrows(SecurityException.class, () -> mMgr.createUser("TheDude", 100));
}
+ @Test
+ public void testStartUserInBackground_success() throws Exception {
+ mockStartUserInBackground(100, UserStartResult.STATUS_SUCCESSFUL);
+
+ StartUserInBackgroundResult result = mMgr.startUserInBackground(UserHandle.of(100));
+
+ assertThat(result.isSuccess()).isTrue();
+ assertThat(result.getStatus()).isEqualTo(StartUserInBackgroundResult.STATUS_SUCCESS);
+ }
+
+ @Test
+ public void testStartUserInBackground_remoteException() throws Exception {
+ doThrow(new RemoteException("D'OH!"))
+ .when(mService).startUserInBackground(eq(100), notNull());
+ mockHandleRemoteExceptionFromCarServiceWithDefaultValue(mCar);
+
+ StartUserInBackgroundResult result = mMgr.startUserInBackground(UserHandle.of(100));
+
+ assertThat(result.isSuccess()).isFalse();
+ assertThat(result.getStatus())
+ .isEqualTo(StartUserInBackgroundResult.STATUS_FAILURE_GENERIC);
+ }
+
+ @Test
+ public void testStartUserInBackground_nullUser() {
+ assertThrows(NullPointerException.class, () -> mMgr.startUserInBackground(null));
+ }
+
private void mockRemoveUser(@UserIdInt int userId, int status) throws Exception {
doAnswer((invocation) -> {
@SuppressWarnings("unchecked")
@@ -144,4 +173,14 @@
return null;
}).when(mService).createUser(eq(name), eq(user.id), notNull());
}
+
+ private void mockStartUserInBackground(@UserIdInt int userId, int status) throws Exception {
+ doAnswer((invocation) -> {
+ @SuppressWarnings("unchecked")
+ AndroidFuture<UserStartResult> future =
+ (AndroidFuture<UserStartResult>) invocation.getArguments()[1];
+ future.complete(new UserStartResult(status));
+ return null;
+ }).when(mService).startUserInBackground(eq(userId), notNull());
+ }
}
diff --git a/tests/carservice_unit_test/src/android/car/admin/StartUserInBackgroundResultTest.java b/tests/carservice_unit_test/src/android/car/admin/StartUserInBackgroundResultTest.java
new file mode 100644
index 0000000..595687b
--- /dev/null
+++ b/tests/carservice_unit_test/src/android/car/admin/StartUserInBackgroundResultTest.java
@@ -0,0 +1,65 @@
+/*
+ * 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.admin;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.car.user.UserStartResult;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+@RunWith(Parameterized.class)
+public final class StartUserInBackgroundResultTest {
+ private final int mUserStartStatus;
+ private final int mStartUserStatus;
+ private final boolean mIsSuccess;
+
+ public StartUserInBackgroundResultTest(
+ int userStartStatus, int startUserStatus, boolean isSuccess) {
+ mUserStartStatus = userStartStatus;
+ mStartUserStatus = startUserStatus;
+ mIsSuccess = isSuccess;
+ }
+
+ @Parameterized.Parameters
+ public static Collection provideParams() {
+ return Arrays.asList(
+ new Object[][] {
+ {UserStartResult.STATUS_SUCCESSFUL,
+ StartUserInBackgroundResult.STATUS_SUCCESS, true},
+ {UserStartResult.STATUS_SUCCESSFUL_USER_IS_CURRENT_USER,
+ StartUserInBackgroundResult.STATUS_SUCCESS_CURRENT_USER, true},
+ {UserStartResult.STATUS_ANDROID_FAILURE,
+ StartUserInBackgroundResult.STATUS_FAILURE_GENERIC, false},
+ {UserStartResult.STATUS_USER_DOES_NOT_EXIST,
+ StartUserInBackgroundResult.STATUS_FAILURE_USER_DOES_NOT_EXIST,
+ false}
+ });
+ }
+
+ @Test
+ public void testStatus() {
+ StartUserInBackgroundResult result = new StartUserInBackgroundResult(mUserStartStatus);
+
+ assertThat(result.getStatus()).isEqualTo(mStartUserStatus);
+ assertThat(result.isSuccess()).isEqualTo(mIsSuccess);
+ }
+}
diff --git a/tests/carservice_unit_test/src/android/car/test/mocks/JavaMockitoHelperTest.java b/tests/carservice_unit_test/src/android/car/test/mocks/JavaMockitoHelperTest.java
new file mode 100644
index 0000000..f83deee
--- /dev/null
+++ b/tests/carservice_unit_test/src/android/car/test/mocks/JavaMockitoHelperTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.test.mocks;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+public final class JavaMockitoHelperTest {
+
+ private static final long TIMEOUT_MS = 1_000L;
+
+ @Test
+ public void testAwait_Semaphore() throws InterruptedException {
+ Semaphore semaphore = new Semaphore(1);
+
+ JavaMockitoHelper.await(semaphore, TIMEOUT_MS);
+
+ assertThat(semaphore.availablePermits()).isEqualTo(0);
+ }
+
+ @Test
+ public void testAwait_CountDownLatch() throws InterruptedException {
+ CountDownLatch latch = new CountDownLatch(1);
+ new Thread(() -> latch.countDown(), "testAwait_CountDownLatch").start();
+
+ JavaMockitoHelper.await(latch, TIMEOUT_MS);
+
+ assertThat(latch.getCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void testSilentAwait_notCalled() {
+ CountDownLatch latch = new CountDownLatch(1);
+
+ assertThat(JavaMockitoHelper.silentAwait(latch, 5L)).isFalse();
+ assertThat(latch.getCount()).isEqualTo(1);
+ }
+
+ @Test
+ public void testSilentAwait_called() {
+ CountDownLatch latch = new CountDownLatch(1);
+ new Thread(() -> latch.countDown(), "testSilentAwait_called").start();
+
+ assertThat(JavaMockitoHelper.silentAwait(latch, TIMEOUT_MS)).isTrue();
+ assertThat(latch.getCount()).isEqualTo(0);
+ }
+
+ @Test
+ public void testGetResult() throws InterruptedException, ExecutionException, TimeoutException {
+ Future<String> future = mock(Future.class);
+ when(future.get(anyLong(), any())).thenReturn("done");
+
+ assertThat(JavaMockitoHelper.getResult(future)).isEqualTo("done");
+ }
+
+ @Test
+ public void testGetResult_withCustomTimeout()
+ throws InterruptedException, ExecutionException, TimeoutException {
+ Future<String> future = mock(Future.class);
+ when(future.get(anyLong(), any(TimeUnit.class))).thenReturn("done");
+
+ assertThat(JavaMockitoHelper.getResult(future, TIMEOUT_MS)).isEqualTo("done");
+ verify(future).get(TIMEOUT_MS, TimeUnit.MILLISECONDS);
+ }
+}
diff --git a/tests/carservice_unit_test/src/android/car/test/util/SyncAnswerTest.java b/tests/carservice_unit_test/src/android/car/test/mocks/SyncAnswerTest.java
similarity index 97%
rename from tests/carservice_unit_test/src/android/car/test/util/SyncAnswerTest.java
rename to tests/carservice_unit_test/src/android/car/test/mocks/SyncAnswerTest.java
index 2e26e7e..4847588 100644
--- a/tests/carservice_unit_test/src/android/car/test/util/SyncAnswerTest.java
+++ b/tests/carservice_unit_test/src/android/car/test/mocks/SyncAnswerTest.java
@@ -14,15 +14,13 @@
* limitations under the License.
*/
-package android.car.test.util;
+package android.car.test.mocks;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
-import android.car.test.mocks.SyncAnswer;
-
import org.junit.Before;
import org.junit.Test;
diff --git a/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java b/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
index d682969..92dfb9a 100644
--- a/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
+++ b/tests/carservice_unit_test/src/android/car/watchdoglib/CarWatchdogDaemonHelperTest.java
@@ -20,11 +20,15 @@
import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.google.common.truth.Truth.assertThat;
+
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
import static org.testng.Assert.assertThrows;
+import android.automotive.watchdog.internal.ComponentType;
import android.automotive.watchdog.internal.ICarWatchdog;
import android.automotive.watchdog.internal.ICarWatchdogMonitor;
import android.automotive.watchdog.internal.ICarWatchdogServiceForSystem;
@@ -32,6 +36,7 @@
import android.automotive.watchdog.internal.PackageIoOveruseStats;
import android.automotive.watchdog.internal.PackageResourceOveruseAction;
import android.automotive.watchdog.internal.PowerCycle;
+import android.automotive.watchdog.internal.ResourceOveruseConfiguration;
import android.automotive.watchdog.internal.StateType;
import android.os.Binder;
import android.os.IBinder;
@@ -47,6 +52,7 @@
import org.mockito.quality.Strictness;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
/**
@@ -154,6 +160,31 @@
}
@Test
+ public void testIndirectCall_updateResourceOveruseConfigurations() throws Exception {
+ ResourceOveruseConfiguration config = new ResourceOveruseConfiguration();
+ config.componentType = ComponentType.SYSTEM;
+ List<ResourceOveruseConfiguration> configs = new ArrayList<>(Collections.singleton(config));
+
+ mCarWatchdogDaemonHelper.updateResourceOveruseConfigurations(configs);
+
+ verify(mFakeCarWatchdog).updateResourceOveruseConfigurations(eq(configs));
+ }
+
+ @Test
+ public void testIndirectCall_getResourceOveruseConfigurations() throws Exception {
+ ResourceOveruseConfiguration config = new ResourceOveruseConfiguration();
+ config.componentType = ComponentType.SYSTEM;
+ List<ResourceOveruseConfiguration> expected =
+ new ArrayList<>(Collections.singleton(config));
+ when(mFakeCarWatchdog.getResourceOveruseConfigurations()).thenReturn(expected);
+
+ List<ResourceOveruseConfiguration> actual =
+ mCarWatchdogDaemonHelper.getResourceOveruseConfigurations();
+
+ assertThat(actual).isEqualTo(expected);
+ }
+
+ @Test
public void testIndirectCall_actionTakenOnResourceOveruse() throws Exception {
List<PackageResourceOveruseAction> actions = new ArrayList<>();
diff --git a/tests/carservice_unit_test/src/com/android/car/admin/CarDevicePolicyServiceTest.java b/tests/carservice_unit_test/src/com/android/car/admin/CarDevicePolicyServiceTest.java
index da1eb72..ed37a9b 100644
--- a/tests/carservice_unit_test/src/com/android/car/admin/CarDevicePolicyServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/admin/CarDevicePolicyServiceTest.java
@@ -26,6 +26,8 @@
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
import android.car.user.UserCreationResult;
import android.car.user.UserRemovalResult;
+import android.car.user.UserStartResult;
+import android.car.user.UserStopResult;
import android.content.pm.UserInfo;
import android.content.pm.UserInfo.UserInfoFlag;
import android.os.UserManager;
@@ -44,11 +46,13 @@
private CarDevicePolicyService mService;
- private AndroidFuture<UserRemovalResult> mUserRemovalResult =
- new AndroidFuture<UserRemovalResult>();
+ private AndroidFuture<UserRemovalResult> mUserRemovalResult = new AndroidFuture<>();
- private AndroidFuture<UserCreationResult> mUserCreationResult =
- new AndroidFuture<UserCreationResult>();
+ private AndroidFuture<UserCreationResult> mUserCreationResult = new AndroidFuture<>();
+
+ private AndroidFuture<UserStartResult> mUserStartResult = new AndroidFuture<>();
+
+ private AndroidFuture<UserStopResult> mUserStopResult = new AndroidFuture<>();
@Before
public void setFixtures() {
@@ -104,4 +108,18 @@
verify(mCarUserService).createUser(eq("name"), eq(userType), eq(flags),
/* timeoutMs= */ anyInt(), eq(mUserCreationResult));
}
+
+ @Test
+ public void testStartUserInBackground() {
+ mService.startUserInBackground(42, mUserStartResult);
+
+ verify(mCarUserService).startUserInBackground(42, mUserStartResult);
+ }
+
+ @Test
+ public void testStopUser() {
+ mService.stopUser(42, mUserStopResult);
+
+ verify(mCarUserService).stopUser(42, mUserStopResult);
+ }
}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextTest.java
index be2e6dd..88ac26c 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioContextTest.java
@@ -27,8 +27,10 @@
import static com.android.car.audio.CarAudioContext.INVALID;
import static com.android.car.audio.CarAudioContext.MUSIC;
import static com.android.car.audio.CarAudioContext.NAVIGATION;
+import static com.android.car.audio.CarAudioContext.isCriticalAudioContext;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import static org.testng.Assert.assertThrows;
@@ -125,4 +127,38 @@
assertThat(result).containsExactly(MUSIC, NAVIGATION, EMERGENCY);
}
+
+ @Test
+ public void isCriticalAudioContext_forNonCritialContexts_returnsFalse() {
+ assertWithMessage("Non-critical context INVALID")
+ .that(isCriticalAudioContext(CarAudioContext.INVALID)).isFalse();
+ assertWithMessage("Non-critical context MUSIC")
+ .that(isCriticalAudioContext(CarAudioContext.MUSIC)).isFalse();
+ assertWithMessage("Non-critical context NAVIGATION")
+ .that(isCriticalAudioContext(CarAudioContext.NAVIGATION)).isFalse();
+ assertWithMessage("Non-critical context VOICE_COMMAND")
+ .that(isCriticalAudioContext(CarAudioContext.VOICE_COMMAND)).isFalse();
+ assertWithMessage("Non-critical context CALL_RING")
+ .that(isCriticalAudioContext(CarAudioContext.CALL_RING)).isFalse();
+ assertWithMessage("Non-critical context CALL")
+ .that(isCriticalAudioContext(CarAudioContext.CALL)).isFalse();
+ assertWithMessage("Non-critical context ALARM")
+ .that(isCriticalAudioContext(CarAudioContext.ALARM)).isFalse();
+ assertWithMessage("Non-critical context NOTIFICATION")
+ .that(isCriticalAudioContext(CarAudioContext.NOTIFICATION)).isFalse();
+ assertWithMessage("Non-critical context SYSTEM_SOUND")
+ .that(isCriticalAudioContext(CarAudioContext.SYSTEM_SOUND)).isFalse();
+ assertWithMessage("Non-critical context VEHICLE_STATUS")
+ .that(isCriticalAudioContext(CarAudioContext.VEHICLE_STATUS)).isFalse();
+ assertWithMessage("Non-critical context ANNOUNCEMENT")
+ .that(isCriticalAudioContext(CarAudioContext.ANNOUNCEMENT)).isFalse();
+ }
+
+ @Test
+ public void isCriticalAudioContext_forCriticalContexts_returnsTrue() {
+ assertWithMessage("Critical context EMERGENCY")
+ .that(isCriticalAudioContext(CarAudioContext.EMERGENCY)).isTrue();
+ assertWithMessage("Critical context SAFETY")
+ .that(isCriticalAudioContext(CarAudioContext.SAFETY)).isTrue();
+ }
}
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPowerListenerTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPowerListenerTest.java
index 1002897..130bee3 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPowerListenerTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarAudioPowerListenerTest.java
@@ -67,7 +67,7 @@
listener.startListeningForPolicyChanges();
- verify(mMockCarAudioService).enableAudio();
+ verify(mMockCarAudioService).setAudioEnabled(true);
}
@Test
@@ -94,7 +94,7 @@
listener.startListeningForPolicyChanges();
- verify(mMockCarAudioService).enableAudio();
+ verify(mMockCarAudioService).setAudioEnabled(true);
}
@Test
@@ -105,7 +105,7 @@
listener.startListeningForPolicyChanges();
- verify(mMockCarAudioService).enableAudio();
+ verify(mMockCarAudioService).setAudioEnabled(true);
}
@Test
@@ -116,53 +116,53 @@
listener.startListeningForPolicyChanges();
- verify(mMockCarAudioService).disableAudio();
+ verify(mMockCarAudioService).setAudioEnabled(false);
}
@Test
public void onPolicyChange_withPowerSwitchingToEnabled_enablesAudio() throws Exception {
withAudioInitiallyDisabled();
ICarPowerPolicyListener changeListener = registerAndGetChangeListener();
- verify(mMockCarAudioService, never()).enableAudio();
+ verify(mMockCarAudioService, never()).setAudioEnabled(true);
changeListener.onPolicyChanged(EMPTY_POLICY, ENABLED_POLICY);
- verify(mMockCarAudioService).enableAudio();
+ verify(mMockCarAudioService).setAudioEnabled(true);
}
@Test
public void onPolicyChange_withPowerRemainingEnabled_doesNothing() throws Exception {
withAudioInitiallyEnabled();
ICarPowerPolicyListener changeListener = registerAndGetChangeListener();
- verify(mMockCarAudioService).enableAudio();
+ verify(mMockCarAudioService).setAudioEnabled(true);
changeListener.onPolicyChanged(EMPTY_POLICY, ENABLED_POLICY);
- verify(mMockCarAudioService).enableAudio();
- verify(mMockCarAudioService, never()).disableAudio();
+ verify(mMockCarAudioService).setAudioEnabled(true);
+ verify(mMockCarAudioService, never()).setAudioEnabled(false);
}
@Test
public void onPolicyChange_withPowerSwitchingToDisabled_disablesAudio() throws Exception {
withAudioInitiallyEnabled();
ICarPowerPolicyListener changeListener = registerAndGetChangeListener();
- verify(mMockCarAudioService, never()).disableAudio();
+ verify(mMockCarAudioService, never()).setAudioEnabled(false);
changeListener.onPolicyChanged(EMPTY_POLICY, DISABLED_POLICY);
- verify(mMockCarAudioService).disableAudio();
+ verify(mMockCarAudioService).setAudioEnabled(false);
}
@Test
public void onPolicyChange_withPowerStayingDisabled_doesNothing() throws Exception {
withAudioInitiallyDisabled();
ICarPowerPolicyListener changeListener = registerAndGetChangeListener();
- verify(mMockCarAudioService).disableAudio();
+ verify(mMockCarAudioService).setAudioEnabled(false);
changeListener.onPolicyChanged(EMPTY_POLICY, DISABLED_POLICY);
- verify(mMockCarAudioService).disableAudio();
- verify(mMockCarAudioService, never()).enableAudio();
+ verify(mMockCarAudioService).setAudioEnabled(false);
+ verify(mMockCarAudioService, never()).setAudioEnabled(true);
}
private void withAudioInitiallyEnabled() {
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupMutingTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupMutingTest.java
index 18bbbea..5c04921 100644
--- a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupMutingTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupMutingTest.java
@@ -16,8 +16,10 @@
package com.android.car.audio;
+import static com.android.car.audio.CarAudioContext.EMERGENCY;
import static com.android.car.audio.CarAudioContext.MUSIC;
import static com.android.car.audio.CarAudioContext.NAVIGATION;
+import static com.android.car.audio.CarAudioContext.SAFETY;
import static com.android.car.audio.CarAudioContext.VOICE_COMMAND;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -48,8 +50,10 @@
private static final String PRIMARY_MEDIA_ADDRESS = "media";
private static final String PRIMARY_NAVIGATION_ADDRESS = "navigation";
private static final String PRIMARY_VOICE_ADDRESS = "voice";
- private static final String SECONDARY_ADDRESS = "media";
- private static final String TERTIARY_ADDRESS = "media";
+ private static final String SECONDARY_ADDRESS = "secondary";
+ private static final String TERTIARY_ADDRESS = "tertiary";
+ private static final String EMERGENCY_ADDRESS = "emergency";
+ private static final String SAFETY_ADDRESS = "safety";
private static final int PRIMARY_ZONE_ID = CarAudioManager.PRIMARY_AUDIO_ZONE;
private static final int SECONDARY_ZONE_ID = CarAudioManager.PRIMARY_AUDIO_ZONE + 1;
private static final int TERTIARY_ZONE_ID = CarAudioManager.PRIMARY_AUDIO_ZONE + 2;
@@ -70,21 +74,12 @@
@Before
public void setUp() {
- mMusicCarVolumeGroup = new VolumeGroupBuilder()
- .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
- .build();
- mNavigationCarVolumeGroup = new VolumeGroupBuilder()
- .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
- .build();
- mVoiceCarVolumeGroup = new VolumeGroupBuilder()
- .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
- .build();
- mSecondaryZoneVolumeGroup = new VolumeGroupBuilder()
- .addDeviceAddressAndContexts(MUSIC, SECONDARY_ADDRESS)
- .build();
- mTertiaryZoneVolumeGroup = new VolumeGroupBuilder()
- .addDeviceAddressAndContexts(MUSIC, TERTIARY_ADDRESS)
- .build();
+ mMusicCarVolumeGroup = groupWithContextAndAddress(MUSIC, PRIMARY_MEDIA_ADDRESS);
+ mNavigationCarVolumeGroup = groupWithContextAndAddress(NAVIGATION,
+ PRIMARY_NAVIGATION_ADDRESS);
+ mVoiceCarVolumeGroup = groupWithContextAndAddress(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS);
+ mSecondaryZoneVolumeGroup = groupWithContextAndAddress(MUSIC, SECONDARY_ADDRESS);
+ mTertiaryZoneVolumeGroup = groupWithContextAndAddress(MUSIC, TERTIARY_ADDRESS);
mPrimaryAudioZone =
new TestCarAudioZoneBuilder("Primary Zone", PRIMARY_ZONE_ID)
@@ -93,20 +88,14 @@
.addVolumeGroup(mVoiceCarVolumeGroup)
.build();
- mSingleDevicePrimaryZone =
- new TestCarAudioZoneBuilder("Primary Zone", PRIMARY_ZONE_ID)
- .addVolumeGroup(mMusicCarVolumeGroup)
- .build();
+ mSingleDevicePrimaryZone = createAudioZone(mMusicCarVolumeGroup, "Primary Zone",
+ PRIMARY_ZONE_ID);
- mSingleDeviceSecondaryZone =
- new TestCarAudioZoneBuilder("Secondary Zone", SECONDARY_ZONE_ID)
- .addVolumeGroup(mSecondaryZoneVolumeGroup)
- .build();
+ mSingleDeviceSecondaryZone = createAudioZone(mSecondaryZoneVolumeGroup, "Secondary Zone",
+ SECONDARY_ZONE_ID);
- mSingleDeviceTertiaryZone =
- new TestCarAudioZoneBuilder("Tertiary Zone", TERTIARY_ZONE_ID)
- .addVolumeGroup(mTertiaryZoneVolumeGroup)
- .build();
+ mSingleDeviceTertiaryZone = createAudioZone(mTertiaryZoneVolumeGroup, "Tertiary Zone",
+ TERTIARY_ZONE_ID);
when(mMockAudioControlWrapper
.supportsFeature(AudioControlWrapper.AUDIOCONTROL_FEATURE_AUDIO_GROUP_MUTING))
@@ -234,7 +223,7 @@
carGroupMuting.carMuteChanged();
- List<MutingInfo> mutingInfo = captureMutingInfoList();
+ List<MutingInfo> mutingInfo = captureMutingInfoList();
MutingInfo info = mutingInfo.get(mutingInfo.size() - 1);
assertWithMessage("Device addresses to un-mute")
.that(info.deviceAddressesToUnmute).asList().containsExactly(
@@ -331,6 +320,49 @@
}
}
+ @Test
+ public void setRestrictMuting_isMutingRestrictedTrue_mutesNonCriticalVolumeGroups() {
+ setUpCarVolumeGroupIsMuted(mSecondaryZoneVolumeGroup, false);
+ setUpCarVolumeGroupIsMuted(mMusicCarVolumeGroup, false);
+ setUpCarVolumeGroupIsMuted(mTertiaryZoneVolumeGroup, false);
+ CarVolumeGroupMuting carGroupMuting =
+ new CarVolumeGroupMuting(getAudioZones(mSingleDevicePrimaryZone,
+ mSingleDeviceSecondaryZone, mSingleDeviceTertiaryZone),
+ mMockAudioControlWrapper);
+
+ carGroupMuting.setRestrictMuting(true);
+
+ for (MutingInfo info : captureMutingInfoList()) {
+ assertWithMessage("Devices addresses to mute for zone %s", info.zoneId)
+ .that(info.deviceAddressesToMute).asList().hasSize(1);
+ }
+ }
+
+ @Test
+ public void setRestrictMuting_isMutingRestrictedTrue_leavesCriticalGroupsAsIs() {
+ setUpCarVolumeGroupIsMuted(mMusicCarVolumeGroup, false);
+ setUpCarVolumeGroupHasCriticalAudioContexts(mMusicCarVolumeGroup);
+ setUpCarVolumeGroupIsMuted(mSecondaryZoneVolumeGroup, true);
+ setUpCarVolumeGroupHasCriticalAudioContexts(mSecondaryZoneVolumeGroup);
+ CarVolumeGroupMuting carGroupMuting = new CarVolumeGroupMuting(
+ getAudioZones(mSingleDevicePrimaryZone, mSingleDeviceSecondaryZone),
+ mMockAudioControlWrapper);
+
+ carGroupMuting.setRestrictMuting(true);
+
+ for (MutingInfo info : captureMutingInfoList()) {
+ if (info.zoneId == PRIMARY_ZONE_ID) {
+ assertWithMessage("Devices addresses to unmute for zone %s", info.zoneId)
+ .that(info.deviceAddressesToUnmute).asList().containsExactly(
+ PRIMARY_MEDIA_ADDRESS);
+
+ } else if (info.zoneId == SECONDARY_ZONE_ID) {
+ assertWithMessage("Devices addresses to mute for zone %s", info.zoneId)
+ .that(info.deviceAddressesToMute).asList().containsExactly(
+ SECONDARY_ADDRESS);
+ }
+ }
+ }
@Test
public void generateMutingInfoFromZone_withNoGroupsMuted_returnsEmptyMutedList() {
@@ -338,7 +370,8 @@
setUpCarVolumeGroupIsMuted(mNavigationCarVolumeGroup, false);
setUpCarVolumeGroupIsMuted(mVoiceCarVolumeGroup, false);
- MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone);
+ MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone,
+ /* isMutingRestricted= */ false);
assertWithMessage("Device addresses to mute")
.that(info.deviceAddressesToMute).asList().isEmpty();
@@ -350,7 +383,8 @@
setUpCarVolumeGroupIsMuted(mNavigationCarVolumeGroup, false);
setUpCarVolumeGroupIsMuted(mVoiceCarVolumeGroup, false);
- MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone);
+ MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone,
+ /* isMutingRestricted= */ false);
assertWithMessage("Device addresses to mute")
.that(info.deviceAddressesToMute).asList().containsExactly(PRIMARY_MEDIA_ADDRESS);
@@ -362,7 +396,8 @@
setUpCarVolumeGroupIsMuted(mNavigationCarVolumeGroup, true);
setUpCarVolumeGroupIsMuted(mVoiceCarVolumeGroup, true);
- MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone);
+ MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(mPrimaryAudioZone,
+ /* isMutingRestricted= */ false);
assertWithMessage("Device addresses to mute")
.that(info.deviceAddressesToMute).asList().containsExactly(PRIMARY_MEDIA_ADDRESS,
@@ -371,17 +406,16 @@
@Test
public void generateMutingInfoFromZone_withMutedMultiDeviceGroup_returnsAllDevicesMuted() {
- CarAudioZone primaryZone =
- new TestCarAudioZoneBuilder("Primary Zone", PRIMARY_ZONE_ID)
- .addVolumeGroup(new VolumeGroupBuilder()
- .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
- .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
- .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
- .setIsMuted(true)
- .build())
- .build();
+ CarAudioZone primaryZone = createAudioZone(
+ new VolumeGroupBuilder()
+ .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
+ .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
+ .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
+ .setIsMuted(true)
+ .build(), "Primary Zone", PRIMARY_ZONE_ID);
- MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone);
+ MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone,
+ /* isMutingRestricted= */ false);
assertWithMessage("Device addresses to mute")
.that(info.deviceAddressesToMute).asList().containsExactly(PRIMARY_MEDIA_ADDRESS,
@@ -390,35 +424,95 @@
@Test
public void generateMutingInfoFromZone_withUnMutedMultiDeviceGroup_returnsAllDevicesUnMuted() {
- CarAudioZone primaryZone =
- new TestCarAudioZoneBuilder("Primary Zone", PRIMARY_ZONE_ID)
- .addVolumeGroup(new VolumeGroupBuilder()
- .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
- .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
- .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
- .build())
- .build();
+ CarAudioZone primaryZone = createAudioZone(
+ new VolumeGroupBuilder()
+ .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
+ .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
+ .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
+ .build(), "Primary Zone", PRIMARY_ZONE_ID);
- MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone);
+ MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone,
+ /* isMutingRestricted= */ false);
assertWithMessage("Device addresses to un-mute")
.that(info.deviceAddressesToUnmute).asList().containsExactly(PRIMARY_MEDIA_ADDRESS,
PRIMARY_NAVIGATION_ADDRESS, PRIMARY_VOICE_ADDRESS);
}
+ @Test
+ public void generateMutingInfoFromZone_mutingRestricted_mutesAllNonCriticalDevices() {
+ CarAudioZone primaryZone = createAudioZone(
+ new VolumeGroupBuilder()
+ .addDeviceAddressAndContexts(MUSIC, PRIMARY_MEDIA_ADDRESS)
+ .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
+ .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
+ .build(), "Primary Zone", PRIMARY_ZONE_ID);
+
+ MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone,
+ /* isMutingRestricted= */ true);
+
+ assertWithMessage("Device addresses to un-mute")
+ .that(info.deviceAddressesToMute).asList().containsExactly(PRIMARY_MEDIA_ADDRESS,
+ PRIMARY_NAVIGATION_ADDRESS, PRIMARY_VOICE_ADDRESS);
+ }
+
+ @Test
+ public void generateMutingInfoFromZone_mutingRestricted_setsAllCriticalGroupsToTheirState() {
+ CarAudioZone primaryZone =
+ new TestCarAudioZoneBuilder("Primary Zone", PRIMARY_ZONE_ID)
+ .addVolumeGroup(new VolumeGroupBuilder()
+ .addDeviceAddressAndContexts(EMERGENCY, EMERGENCY_ADDRESS)
+ .addDeviceAddressAndContexts(VOICE_COMMAND, PRIMARY_VOICE_ADDRESS)
+ .build())
+ .addVolumeGroup(new VolumeGroupBuilder()
+ .addDeviceAddressAndContexts(SAFETY, SAFETY_ADDRESS)
+ .addDeviceAddressAndContexts(NAVIGATION, PRIMARY_NAVIGATION_ADDRESS)
+ .setIsMuted(true)
+ .build()
+ )
+ .build();
+ setUpCarVolumeGroupHasCriticalAudioContexts(primaryZone.getVolumeGroups()[0]);
+ setUpCarVolumeGroupHasCriticalAudioContexts(primaryZone.getVolumeGroups()[1]);
+
+ MutingInfo info = CarVolumeGroupMuting.generateMutingInfoFromZone(primaryZone,
+ /* isMutingRestricted= */ true);
+
+ assertWithMessage("Device addresses to mute")
+ .that(info.deviceAddressesToMute).asList()
+ .containsExactly(SAFETY_ADDRESS, PRIMARY_NAVIGATION_ADDRESS);
+ assertWithMessage("Device addresses to un-mute")
+ .that(info.deviceAddressesToUnmute).asList()
+ .containsExactly(EMERGENCY_ADDRESS, PRIMARY_VOICE_ADDRESS);
+ }
+
+
+ private CarAudioZone createAudioZone(CarVolumeGroup volumeGroup, String name, int zoneId) {
+ return new TestCarAudioZoneBuilder(name, zoneId)
+ .addVolumeGroup(volumeGroup)
+ .build();
+ }
+
+ private CarVolumeGroup groupWithContextAndAddress(int context, String address) {
+ return new VolumeGroupBuilder().addDeviceAddressAndContexts(context, address).build();
+ }
+
private List<MutingInfo> captureMutingInfoList() {
ArgumentCaptor<List<MutingInfo>> captor = ArgumentCaptor.forClass(List.class);
verify(mMockAudioControlWrapper).onDevicesToMuteChange(captor.capture());
return captor.getValue();
}
- private void setUpCarVolumeGroupIsMuted(CarVolumeGroup musicCarVolumeGroup, boolean muted) {
- when(musicCarVolumeGroup.isMuted()).thenReturn(muted);
+ private void setUpCarVolumeGroupIsMuted(CarVolumeGroup carVolumeGroup, boolean muted) {
+ when(carVolumeGroup.isMuted()).thenReturn(muted);
}
- private SparseArray<CarAudioZone> getAudioZones(CarAudioZone ...zones) {
+ private void setUpCarVolumeGroupHasCriticalAudioContexts(CarVolumeGroup carVolumeGroup) {
+ when(carVolumeGroup.hasCriticalAudioContexts()).thenReturn(true);
+ }
+
+ private SparseArray<CarAudioZone> getAudioZones(CarAudioZone... zones) {
SparseArray<CarAudioZone> audioZones = new SparseArray<>();
- for (CarAudioZone zone: zones) {
+ for (CarAudioZone zone : zones) {
audioZones.put(zone.getId(), zone);
}
return audioZones;
diff --git a/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupUnitTest.java b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupUnitTest.java
new file mode 100644
index 0000000..9e38583
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/audio/CarVolumeGroupUnitTest.java
@@ -0,0 +1,635 @@
+/*
+ * 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 com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.expectThrows;
+
+import android.annotation.UserIdInt;
+import android.os.UserHandle;
+import android.util.SparseBooleanArray;
+import android.util.SparseIntArray;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.List;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CarVolumeGroupUnitTest {
+ private static final int ZONE_ID = 0;
+ private static final int GROUP_ID = 0;
+ private static final int STEP_VALUE = 2;
+ private static final int MIN_GAIN = 3;
+ private static final int MAX_GAIN = 10;
+ private static final int DEFAULT_GAIN = 5;
+ private static final int DEFAULT_GAIN_INDEX = (DEFAULT_GAIN - MIN_GAIN) / STEP_VALUE;
+ private static final int MIN_GAIN_INDEX = 0;
+ private static final int MAX_GAIN_INDEX = (MAX_GAIN - MIN_GAIN) / STEP_VALUE;
+ private static final int TEST_GAIN_INDEX = 2;
+ private static final int TEST_USER_10 = 10;
+ private static final int TEST_USER_11 = 11;
+ private static final String MEDIA_DEVICE_ADDRESS = "music";
+ private static final String NAVIGATION_DEVICE_ADDRESS = "navigation";
+ private static final String OTHER_ADDRESS = "other_address";
+
+ private CarAudioDeviceInfo mMediaDeviceInfo;
+ private CarAudioDeviceInfo mNavigationDeviceInfo;
+
+ @Mock
+ CarAudioSettings mSettingsMock;
+
+ @Before
+ public void setUp() {
+ mMediaDeviceInfo = new InfoBuilder(MEDIA_DEVICE_ADDRESS).build();
+ mNavigationDeviceInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS).build();
+ }
+
+ @Test
+ public void setDeviceInfoForContext_associatesDeviceAddresses() {
+ CarVolumeGroup.Builder builder = getBuilder();
+
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, mNavigationDeviceInfo);
+ CarVolumeGroup carVolumeGroup = builder.build();
+
+ assertThat(carVolumeGroup.getAddresses()).containsExactly(MEDIA_DEVICE_ADDRESS,
+ NAVIGATION_DEVICE_ADDRESS);
+ }
+
+ @Test
+ public void setDeviceInfoForContext_associatesContexts() {
+ CarVolumeGroup.Builder builder = getBuilder();
+
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, mNavigationDeviceInfo);
+ CarVolumeGroup carVolumeGroup = builder.build();
+
+ assertThat(carVolumeGroup.getContexts()).asList().containsExactly(CarAudioContext.MUSIC,
+ CarAudioContext.NAVIGATION);
+ }
+
+ @Test
+ public void setDeviceInfoForContext_withDifferentStepSize_throws() {
+ CarVolumeGroup.Builder builder = getBuilder();
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ CarAudioDeviceInfo differentStepValueDevice = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
+ .setStepValue(mMediaDeviceInfo.getStepValue() + 1).build();
+
+ IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
+ () -> builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION,
+ differentStepValueDevice));
+
+ assertThat(thrown).hasMessageThat()
+ .contains("Gain controls within one group must have same step value");
+ }
+
+ @Test
+ public void setDeviceInfoForContext_withSameContext_throws() {
+ CarVolumeGroup.Builder builder = getBuilder();
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+
+ IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
+ () -> builder.setDeviceInfoForContext(CarAudioContext.MUSIC,
+ mNavigationDeviceInfo));
+
+ assertThat(thrown).hasMessageThat()
+ .contains("has already been set to");
+ }
+
+ @Test
+ public void setDeviceInfoForContext_withFirstCall_setsMinGain() {
+ CarVolumeGroup.Builder builder = getBuilder();
+
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+
+ assertThat(builder.mMinGain).isEqualTo(mMediaDeviceInfo.getMinGain());
+ }
+
+ @Test
+ public void setDeviceInfoForContext_withFirstCall_setsMaxGain() {
+ CarVolumeGroup.Builder builder = getBuilder();
+
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+
+ assertThat(builder.mMaxGain).isEqualTo(mMediaDeviceInfo.getMaxGain());
+ }
+
+ @Test
+ public void setDeviceInfoForContext_withFirstCall_setsDefaultGain() {
+ CarVolumeGroup.Builder builder = getBuilder();
+
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+
+ assertThat(builder.mDefaultGain).isEqualTo(mMediaDeviceInfo.getDefaultGain());
+ }
+
+ @Test
+ public void setDeviceInfoForContext_SecondCallWithSmallerMinGain_updatesMinGain() {
+ CarVolumeGroup.Builder builder = getBuilder();
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
+ .setMinGain(mMediaDeviceInfo.getMinGain() - 1).build();
+
+ builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+
+ assertThat(builder.mMinGain).isEqualTo(secondInfo.getMinGain());
+ }
+
+ @Test
+ public void setDeviceInfoForContext_SecondCallWithLargerMinGain_keepsFirstMinGain() {
+ CarVolumeGroup.Builder builder = getBuilder();
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
+ .setMinGain(mMediaDeviceInfo.getMinGain() + 1).build();
+
+ builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+
+ assertThat(builder.mMinGain).isEqualTo(mMediaDeviceInfo.getMinGain());
+ }
+
+ @Test
+ public void setDeviceInfoForContext_SecondCallWithLargerMaxGain_updatesMaxGain() {
+ CarVolumeGroup.Builder builder = getBuilder();
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
+ .setMaxGain(mMediaDeviceInfo.getMaxGain() + 1).build();
+
+ builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+
+ assertThat(builder.mMaxGain).isEqualTo(secondInfo.getMaxGain());
+ }
+
+ @Test
+ public void setDeviceInfoForContext_SecondCallWithSmallerMaxGain_keepsFirstMaxGain() {
+ CarVolumeGroup.Builder builder = getBuilder();
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
+ .setMaxGain(mMediaDeviceInfo.getMaxGain() - 1).build();
+
+ builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+
+ assertThat(builder.mMaxGain).isEqualTo(mMediaDeviceInfo.getMaxGain());
+ }
+
+ @Test
+ public void setDeviceInfoForContext_SecondCallWithLargerDefaultGain_updatesDefaultGain() {
+ CarVolumeGroup.Builder builder = getBuilder();
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
+ .setDefaultGain(mMediaDeviceInfo.getDefaultGain() + 1).build();
+
+ builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+
+ assertThat(builder.mDefaultGain).isEqualTo(secondInfo.getDefaultGain());
+ }
+
+ @Test
+ public void setDeviceInfoForContext_SecondCallWithSmallerDefaultGain_keepsFirstDefaultGain() {
+ CarVolumeGroup.Builder builder = getBuilder();
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ CarAudioDeviceInfo secondInfo = new InfoBuilder(NAVIGATION_DEVICE_ADDRESS)
+ .setDefaultGain(mMediaDeviceInfo.getDefaultGain() - 1).build();
+
+ builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, secondInfo);
+
+ assertThat(builder.mDefaultGain).isEqualTo(mMediaDeviceInfo.getDefaultGain());
+ }
+
+ @Test
+ public void builderBuild_withNoCallToSetDeviceInfoForContext_throws() {
+ CarVolumeGroup.Builder builder = getBuilder();
+
+ Exception e = expectThrows(IllegalArgumentException.class, builder::build);
+
+ assertThat(e).hasMessageThat().isEqualTo(
+ "setDeviceInfoForContext has to be called at least once before building");
+ }
+
+ @Test
+ public void builderBuild_withNoStoredGain_usesDefaultGain() {
+ CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(CarAudioContext.MUSIC,
+ mMediaDeviceInfo);
+ when(mSettingsMock.getStoredVolumeGainIndexForUser(UserHandle.USER_CURRENT, ZONE_ID,
+ GROUP_ID)).thenReturn(-1);
+
+
+ CarVolumeGroup carVolumeGroup = builder.build();
+
+ assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(DEFAULT_GAIN_INDEX);
+ }
+
+ @Test
+ public void builderBuild_withTooLargeStoredGain_usesDefaultGain() {
+ CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(CarAudioContext.MUSIC,
+ mMediaDeviceInfo);
+ when(mSettingsMock.getStoredVolumeGainIndexForUser(UserHandle.USER_CURRENT, ZONE_ID,
+ GROUP_ID)).thenReturn(MAX_GAIN_INDEX + 1);
+
+ CarVolumeGroup carVolumeGroup = builder.build();
+
+ assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(DEFAULT_GAIN_INDEX);
+ }
+
+ @Test
+ public void builderBuild_withTooSmallStoredGain_usesDefaultGain() {
+ CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(CarAudioContext.MUSIC,
+ mMediaDeviceInfo);
+ when(mSettingsMock.getStoredVolumeGainIndexForUser(UserHandle.USER_CURRENT, ZONE_ID,
+ GROUP_ID)).thenReturn(MIN_GAIN_INDEX - 1);
+
+ CarVolumeGroup carVolumeGroup = builder.build();
+
+ assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(DEFAULT_GAIN_INDEX);
+ }
+
+ @Test
+ public void builderBuild_withValidStoredGain_usesStoredGain() {
+ CarVolumeGroup.Builder builder = getBuilder().setDeviceInfoForContext(CarAudioContext.MUSIC,
+ mMediaDeviceInfo);
+ when(mSettingsMock.getStoredVolumeGainIndexForUser(UserHandle.USER_CURRENT, ZONE_ID,
+ GROUP_ID)).thenReturn(MAX_GAIN_INDEX - 1);
+
+ CarVolumeGroup carVolumeGroup = builder.build();
+
+ assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(MAX_GAIN_INDEX - 1);
+ }
+
+ @Test
+ public void getAddressForContext_withSupportedContext_returnsAddress() {
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+
+ assertThat(carVolumeGroup.getAddressForContext(CarAudioContext.MUSIC))
+ .isEqualTo(mMediaDeviceInfo.getAddress());
+ }
+
+ @Test
+ public void getAddressForContext_withUnsupportedContext_returnsNull() {
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+
+ assertThat(carVolumeGroup.getAddressForContext(CarAudioContext.NAVIGATION)).isNull();
+ }
+
+ @Test
+ public void isMuted_whenDefault_returnsFalse() {
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+
+ assertThat(carVolumeGroup.isMuted()).isFalse();
+ }
+
+ @Test
+ public void isMuted_afterMuting_returnsTrue() {
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+
+ carVolumeGroup.setMute(true);
+
+ assertThat(carVolumeGroup.isMuted()).isTrue();
+ }
+
+ @Test
+ public void isMuted_afterUnMuting_returnsFalse() {
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+
+ carVolumeGroup.setMute(false);
+
+ assertThat(carVolumeGroup.isMuted()).isFalse();
+ }
+
+ @Test
+ public void setMute_withMutedState_storesValueToSetting() {
+ CarAudioSettings settings = new SettingsBuilder(0, 0)
+ .setMuteForUser10(false)
+ .setIsPersistVolumeGroupEnabled(true)
+ .build();
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithNavigationBound(settings, true);
+ carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
+
+ carVolumeGroup.setMute(true);
+
+ verify(settings)
+ .storeVolumeGroupMuteForUser(TEST_USER_10, 0, 0, true);
+ }
+
+ @Test
+ public void setMute_withUnMutedState_storesValueToSetting() {
+ CarAudioSettings settings = new SettingsBuilder(0, 0)
+ .setMuteForUser10(false)
+ .setIsPersistVolumeGroupEnabled(true)
+ .build();
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithNavigationBound(settings, true);
+ carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
+
+ carVolumeGroup.setMute(false);
+
+ verify(settings)
+ .storeVolumeGroupMuteForUser(TEST_USER_10, 0, 0, false);
+ }
+
+ @Test
+ public void getContextsForAddress_returnsContextsBoundToThatAddress() {
+ CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+ List<Integer> contextsList = carVolumeGroup.getContextsForAddress(MEDIA_DEVICE_ADDRESS);
+
+ assertThat(contextsList).containsExactly(CarAudioContext.MUSIC,
+ CarAudioContext.CALL, CarAudioContext.CALL_RING);
+ }
+
+ @Test
+ public void getContextsForAddress_returnsEmptyArrayIfAddressNotBound() {
+ CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+ List<Integer> contextsList = carVolumeGroup.getContextsForAddress(OTHER_ADDRESS);
+
+ assertThat(contextsList).isEmpty();
+ }
+
+ @Test
+ public void getCarAudioDeviceInfoForAddress_returnsExpectedDevice() {
+ CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+ CarAudioDeviceInfo actualDevice = carVolumeGroup.getCarAudioDeviceInfoForAddress(
+ MEDIA_DEVICE_ADDRESS);
+
+ assertThat(actualDevice).isEqualTo(mMediaDeviceInfo);
+ }
+
+ @Test
+ public void getCarAudioDeviceInfoForAddress_returnsNullIfAddressNotBound() {
+ CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+ CarAudioDeviceInfo actualDevice = carVolumeGroup.getCarAudioDeviceInfoForAddress(
+ OTHER_ADDRESS);
+
+ assertThat(actualDevice).isNull();
+ }
+
+ @Test
+ public void setCurrentGainIndex_setsGainOnAllBoundDevices() {
+ CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+ carVolumeGroup.setCurrentGainIndex(TEST_GAIN_INDEX);
+
+ verify(mMediaDeviceInfo).setCurrentGain(7);
+ verify(mNavigationDeviceInfo).setCurrentGain(7);
+ }
+
+ @Test
+ public void setCurrentGainIndex_updatesCurrentGainIndex() {
+ CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+ carVolumeGroup.setCurrentGainIndex(TEST_GAIN_INDEX);
+
+ assertThat(carVolumeGroup.getCurrentGainIndex()).isEqualTo(TEST_GAIN_INDEX);
+ }
+
+ @Test
+ public void setCurrentGainIndex_checksNewGainIsAboveMin() {
+ CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+ IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
+ () -> carVolumeGroup.setCurrentGainIndex(MIN_GAIN_INDEX - 1));
+ assertThat(thrown).hasMessageThat()
+ .contains("Gain out of range (" + MIN_GAIN + ":" + MAX_GAIN + ")");
+ }
+
+ @Test
+ public void setCurrentGainIndex_checksNewGainIsBelowMax() {
+ CarVolumeGroup carVolumeGroup = testVolumeGroupSetup();
+
+ IllegalArgumentException thrown = expectThrows(IllegalArgumentException.class,
+ () -> carVolumeGroup.setCurrentGainIndex(MAX_GAIN_INDEX + 1));
+ assertThat(thrown).hasMessageThat()
+ .contains("Gain out of range (" + MIN_GAIN + ":" + MAX_GAIN + ")");
+ }
+
+ @Test
+ public void setCurrentGainIndex_setsCurrentGainIndexForUser() {
+ CarAudioSettings settings = new SettingsBuilder(0, 0)
+ .setGainIndexForUser(TEST_USER_11)
+ .build();
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithNavigationBound(settings, false);
+ carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_11);
+
+ carVolumeGroup.setCurrentGainIndex(MIN_GAIN);
+
+ verify(settings).storeVolumeGainIndexForUser(TEST_USER_11, 0, 0, MIN_GAIN);
+ }
+
+ @Test
+ public void setCurrentGainIndex_setsCurrentGainIndexForDefaultUser() {
+ CarAudioSettings settings = new SettingsBuilder(0, 0)
+ .setGainIndexForUser(UserHandle.USER_CURRENT)
+ .build();
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithNavigationBound(settings, false);
+
+ carVolumeGroup.setCurrentGainIndex(MIN_GAIN);
+
+ verify(settings)
+ .storeVolumeGainIndexForUser(UserHandle.USER_CURRENT, 0, 0, MIN_GAIN);
+ }
+
+ @Test
+ public void loadVolumesSettingsForUser_withMutedState_loadsMuteStateForUser() {
+ CarVolumeGroup carVolumeGroup = getVolumeGroupWithMuteAndNavBound(true, true, true);
+
+ carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
+
+ assertThat(carVolumeGroup.isMuted()).isTrue();
+ }
+
+ @Test
+ public void loadVolumesSettingsForUser_withDisabledUseVolumeGroupMute_doesNotLoadMute() {
+ CarVolumeGroup carVolumeGroup = getVolumeGroupWithMuteAndNavBound(true, true, false);
+
+ carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
+
+ assertThat(carVolumeGroup.isMuted()).isFalse();
+ }
+
+ @Test
+ public void loadVolumesSettingsForUser_withUnMutedState_loadsMuteStateForUser() {
+ CarVolumeGroup carVolumeGroup = getVolumeGroupWithMuteAndNavBound(false, true, true);
+
+ carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
+
+ assertThat(carVolumeGroup.isMuted()).isFalse();
+ }
+
+ @Test
+ public void loadVolumesSettingsForUser_withMutedStateAndNoPersist_returnsDefaultMuteState() {
+ CarVolumeGroup carVolumeGroup = getVolumeGroupWithMuteAndNavBound(true, false, true);
+
+ carVolumeGroup.loadVolumesSettingsForUser(TEST_USER_10);
+
+ assertThat(carVolumeGroup.isMuted()).isFalse();
+ }
+
+ @Test
+ public void hasCriticalAudioContexts_withoutCriticalContexts_returnsFalse() {
+ CarVolumeGroup carVolumeGroup = getCarVolumeGroupWithMusicBound();
+
+ assertThat(carVolumeGroup.hasCriticalAudioContexts()).isFalse();
+ }
+
+ @Test
+ public void hasCriticalAudioContexts_withCriticalContexts_returnsTrue() {
+ CarVolumeGroup carVolumeGroup = getBuilder()
+ .setDeviceInfoForContext(CarAudioContext.EMERGENCY, mMediaDeviceInfo)
+ .build();
+
+ assertThat(carVolumeGroup.hasCriticalAudioContexts()).isTrue();
+ }
+
+ private CarVolumeGroup getCarVolumeGroupWithMusicBound() {
+ return getBuilder()
+ .setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo)
+ .build();
+ }
+
+ private CarVolumeGroup getCarVolumeGroupWithNavigationBound(CarAudioSettings settings,
+ boolean useCarVolumeGroupMute) {
+ return new CarVolumeGroup.Builder(0, 0, settings, useCarVolumeGroupMute)
+ .setDeviceInfoForContext(CarAudioContext.NAVIGATION, mNavigationDeviceInfo)
+ .build();
+ }
+
+ CarVolumeGroup getVolumeGroupWithMuteAndNavBound(boolean isMuted, boolean persistMute,
+ boolean useCarVolumeGroupMute) {
+ CarAudioSettings settings = new SettingsBuilder(0, 0)
+ .setMuteForUser10(isMuted)
+ .setIsPersistVolumeGroupEnabled(persistMute)
+ .build();
+ return getCarVolumeGroupWithNavigationBound(settings, useCarVolumeGroupMute);
+ }
+
+ private CarVolumeGroup testVolumeGroupSetup() {
+ CarVolumeGroup.Builder builder = getBuilder();
+
+ builder.setDeviceInfoForContext(CarAudioContext.MUSIC, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(CarAudioContext.CALL, mMediaDeviceInfo);
+ builder.setDeviceInfoForContext(CarAudioContext.CALL_RING, mMediaDeviceInfo);
+
+ builder.setDeviceInfoForContext(CarAudioContext.NAVIGATION, mNavigationDeviceInfo);
+ builder.setDeviceInfoForContext(CarAudioContext.ALARM, mNavigationDeviceInfo);
+ builder.setDeviceInfoForContext(CarAudioContext.NOTIFICATION, mNavigationDeviceInfo);
+
+ return builder.build();
+ }
+
+ CarVolumeGroup.Builder getBuilder() {
+ return new CarVolumeGroup.Builder(ZONE_ID, GROUP_ID, mSettingsMock, true);
+ }
+
+ private static final class SettingsBuilder {
+ private final SparseIntArray mStoredGainIndexes = new SparseIntArray();
+ private final SparseBooleanArray mStoreMuteStates = new SparseBooleanArray();
+ private final int mZoneId;
+ private final int mGroupId;
+
+ private boolean mPersistMute;
+
+ SettingsBuilder(int zoneId, int groupId) {
+ mZoneId = zoneId;
+ mGroupId = groupId;
+ }
+
+ SettingsBuilder setGainIndexForUser(@UserIdInt int userId) {
+ mStoredGainIndexes.put(userId, TEST_GAIN_INDEX);
+ return this;
+ }
+
+ SettingsBuilder setMuteForUser10(boolean mute) {
+ mStoreMuteStates.put(CarVolumeGroupUnitTest.TEST_USER_10, mute);
+ return this;
+ }
+
+ SettingsBuilder setIsPersistVolumeGroupEnabled(boolean persistMute) {
+ mPersistMute = persistMute;
+ return this;
+ }
+
+ CarAudioSettings build() {
+ CarAudioSettings settingsMock = Mockito.mock(CarAudioSettings.class);
+ for (int storeIndex = 0; storeIndex < mStoredGainIndexes.size(); storeIndex++) {
+ int gainUserId = mStoredGainIndexes.keyAt(storeIndex);
+ when(settingsMock
+ .getStoredVolumeGainIndexForUser(gainUserId, mZoneId,
+ mGroupId)).thenReturn(
+ mStoredGainIndexes.get(gainUserId, DEFAULT_GAIN));
+ }
+ for (int muteIndex = 0; muteIndex < mStoreMuteStates.size(); muteIndex++) {
+ int muteUserId = mStoreMuteStates.keyAt(muteIndex);
+ when(settingsMock.getVolumeGroupMuteForUser(muteUserId, mZoneId, mGroupId))
+ .thenReturn(mStoreMuteStates.get(muteUserId, false));
+ when(settingsMock.isPersistVolumeGroupMuteEnabled(muteUserId))
+ .thenReturn(mPersistMute);
+ }
+ return settingsMock;
+ }
+ }
+
+ private static final class InfoBuilder {
+ private final String mAddress;
+
+ private int mStepValue = STEP_VALUE;
+ private int mDefaultGain = DEFAULT_GAIN;
+ private int mMinGain = MIN_GAIN;
+ private int mMaxGain = MAX_GAIN;
+
+ InfoBuilder(String address) {
+ mAddress = address;
+ }
+
+ InfoBuilder setStepValue(int stepValue) {
+ mStepValue = stepValue;
+ return this;
+ }
+
+ InfoBuilder setDefaultGain(int defaultGain) {
+ mDefaultGain = defaultGain;
+ return this;
+ }
+
+ InfoBuilder setMinGain(int minGain) {
+ mMinGain = minGain;
+ return this;
+ }
+
+ InfoBuilder setMaxGain(int maxGain) {
+ mMaxGain = maxGain;
+ return this;
+ }
+
+ CarAudioDeviceInfo build() {
+ CarAudioDeviceInfo infoMock = Mockito.mock(CarAudioDeviceInfo.class);
+ when(infoMock.getStepValue()).thenReturn(mStepValue);
+ when(infoMock.getDefaultGain()).thenReturn(mDefaultGain);
+ when(infoMock.getMaxGain()).thenReturn(mMaxGain);
+ when(infoMock.getMinGain()).thenReturn(mMinGain);
+ when(infoMock.getAddress()).thenReturn(mAddress);
+ return infoMock;
+ }
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/power/CarPowerManagementServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/power/CarPowerManagementServiceUnitTest.java
index b7151eb..80d9dd7 100644
--- a/tests/carservice_unit_test/src/com/android/car/power/CarPowerManagementServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/power/CarPowerManagementServiceUnitTest.java
@@ -356,6 +356,41 @@
mPowerSignalListener.waitFor(PowerHalService.SET_DEEP_SLEEP_EXIT, WAIT_TIMEOUT_MS);
}
+ @Test
+ public void testShutdownPostponeAfterSuspend() throws Exception {
+ // Start in the ON state
+ mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0));
+ mPowerSignalListener.waitFor(PowerHalService.SET_ON, WAIT_TIMEOUT_MS);
+ mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
+ VehicleApPowerStateShutdownParam.CAN_SLEEP));
+ mDisplayInterface.waitForDisplayOff(WAIT_TIMEOUT_MS);
+ assertStateReceivedForShutdownOrSleepWithPostpone(PowerHalService.SET_DEEP_SLEEP_ENTRY);
+ mPowerSignalListener.waitFor(PowerHalService.SET_DEEP_SLEEP_ENTRY, WAIT_TIMEOUT_MS);
+
+ // Send the finished signal
+ mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.FINISHED, 0));
+ mSystemStateInterface.setWakeupCausedByTimer(true);
+ mSystemStateInterface.waitForSleepEntryAndWakeup(WAIT_TIMEOUT_MS);
+ assertStateReceived(PowerHalService.SET_DEEP_SLEEP_EXIT, 0);
+ mPowerSignalListener.waitFor(PowerHalService.SET_DEEP_SLEEP_EXIT, WAIT_TIMEOUT_MS);
+ mService.scheduleNextWakeupTime(WAKE_UP_DELAY);
+ // Second processing after wakeup
+ assertThat(mDisplayInterface.isDisplayEnabled()).isFalse();
+
+ mService.setStateForWakeUp();
+
+ mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.ON, 0));
+ mDisplayInterface.waitForDisplayOn(WAIT_TIMEOUT_MS);
+ // Should wait until Handler has finished ON processing
+ CarServiceUtils.runOnLooperSync(mService.getHandlerThread().getLooper(), () -> { });
+
+ mPowerHal.setCurrentPowerState(new PowerState(VehicleApPowerStateReq.SHUTDOWN_PREPARE,
+ VehicleApPowerStateShutdownParam.CAN_SLEEP));
+
+ // Should suspend within timeout
+ mPowerSignalListener.waitFor(PowerHalService.SET_DEEP_SLEEP_ENTRY, WAIT_TIMEOUT_MS);
+ }
+
/**
* This test case tests the same scenario as {@link #testUserSwitchingOnResume_differentUser()},
* but indirectly triggering {@code switchUserOnResumeIfNecessary()} through HAL events.
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 adafb57..fcdc6ef 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
@@ -72,6 +72,8 @@
import android.car.user.UserCreationResult;
import android.car.user.UserIdentificationAssociationResponse;
import android.car.user.UserRemovalResult;
+import android.car.user.UserStartResult;
+import android.car.user.UserStopResult;
import android.car.user.UserSwitchResult;
import android.car.userlib.HalCallback;
import android.car.userlib.HalCallback.HalCallbackStatus;
@@ -528,9 +530,9 @@
UserInfo user2Info = new UserInfo(user2, "user2", NO_USER_INFO_FLAGS);
UserInfo user3Info = new UserInfo(user3, "user3", NO_USER_INFO_FLAGS);
- doReturn(user1Info).when(mMockedUserManager).getUserInfo(user1);
- doReturn(user2Info).when(mMockedUserManager).getUserInfo(user2);
- doReturn(user3Info).when(mMockedUserManager).getUserInfo(user3);
+ when(mMockedUserManager.getUserInfo(user1)).thenReturn(user1Info);
+ when(mMockedUserManager.getUserInfo(user2)).thenReturn(user2Info);
+ when(mMockedUserManager.getUserInfo(user3)).thenReturn(user3Info);
mockGetCurrentUser(user1);
sendUserUnlockedEvent(UserHandle.USER_SYSTEM);
@@ -540,40 +542,108 @@
sendUserUnlockedEvent(user1);
mockGetCurrentUser(user3);
sendUserUnlockedEvent(user3);
+ mockStopUserWithDelayedLocking(user3, ActivityManager.USER_OP_IS_CURRENT);
- assertEquals(new Integer[]{user3, user2},
- mCarUserService.getBackgroundUsersToRestart().toArray());
+ assertThat(mCarUserService.getBackgroundUsersToRestart()).containsExactly(user2, user3);
- doReturn(true).when(mMockedIActivityManager).startUserInBackground(user2);
- doReturn(true).when(mMockedIActivityManager).unlockUser(user2,
- null, null, null);
- assertEquals(new Integer[]{user2},
- mCarUserService.startAllBackgroundUsers().toArray());
+ when(mMockedIActivityManager.startUserInBackground(user2)).thenReturn(true);
+ when(mMockedIActivityManager.unlockUser(user2, null, null, null)).thenReturn(true);
+ assertThat(mCarUserService.startAllBackgroundUsers()).containsExactly(user2);
sendUserUnlockedEvent(user2);
- assertEquals(new Integer[]{user3, user2},
- mCarUserService.getBackgroundUsersToRestart().toArray());
+ assertThat(mCarUserService.getBackgroundUsersToRestart()).containsExactly(user2, user3);
- doReturn(ActivityManager.USER_OP_SUCCESS).when(mMockedIActivityManager).stopUser(user2,
- true, null);
+ when(mMockedIActivityManager.stopUser(user2, true, null))
+ .thenReturn(ActivityManager.USER_OP_SUCCESS);
// should not stop the current fg user
- assertFalse(mCarUserService.stopBackgroundUser(user3));
- assertTrue(mCarUserService.stopBackgroundUser(user2));
- assertEquals(new Integer[]{user3, user2},
- mCarUserService.getBackgroundUsersToRestart().toArray());
- assertEquals(new Integer[]{user3, user2},
- mCarUserService.getBackgroundUsersToRestart().toArray());
+ assertThat(mCarUserService.stopBackgroundUser(user3)).isFalse();
+ assertThat(mCarUserService.stopBackgroundUser(user2)).isTrue();
+ assertThat(mCarUserService.getBackgroundUsersToRestart()).containsExactly(user2, user3);
+ assertThat(mCarUserService.getBackgroundUsersToRestart()).containsExactly(user2, user3);
}
@Test
- public void testStopBackgroundUserForSystemUser() {
- assertFalse(mCarUserService.stopBackgroundUser(UserHandle.USER_SYSTEM));
+ public void testStopUser_success() throws Exception {
+ int userId = 101;
+ UserInfo userInfo = new UserInfo(userId, "user101", NO_USER_INFO_FLAGS);
+ mockStopUserWithDelayedLocking(userId, ActivityManager.USER_OP_SUCCESS);
+
+ AndroidFuture<UserStopResult> userStopResult = new AndroidFuture<>();
+ mCarUserService.stopUser(userId, userStopResult);
+
+ assertThat(getResult(userStopResult).getStatus())
+ .isEqualTo(UserStopResult.STATUS_SUCCESSFUL);
+ assertThat(getResult(userStopResult).isSuccess()).isTrue();
}
@Test
- public void testStopBackgroundUserForFgUser() throws RemoteException {
- int user1 = 101;
- mockGetCurrentUser(user1);
- assertFalse(mCarUserService.stopBackgroundUser(UserHandle.USER_SYSTEM));
+ public void testStopUser_fail() throws Exception {
+ int userId = 101;
+ UserInfo userInfo = new UserInfo(userId, "user101", NO_USER_INFO_FLAGS);
+ mockStopUserWithDelayedLockingThrowsRemoteException(userId);
+
+ AndroidFuture<UserStopResult> userStopResult = new AndroidFuture<>();
+ mCarUserService.stopUser(userId, userStopResult);
+
+ assertThat(getResult(userStopResult).getStatus())
+ .isEqualTo(UserStopResult.STATUS_ANDROID_FAILURE);
+ assertThat(getResult(userStopResult).isSuccess()).isFalse();
+ }
+
+ @Test
+ public void testStopUser_userDoesNotExist() throws Exception {
+ int userId = 101;
+ UserInfo userInfo = new UserInfo(userId, "user101", NO_USER_INFO_FLAGS);
+ mockStopUserWithDelayedLocking(userId, ActivityManager.USER_OP_UNKNOWN_USER);
+
+ AndroidFuture<UserStopResult> userStopResult = new AndroidFuture<>();
+ mCarUserService.stopUser(userId, userStopResult);
+
+ assertThat(getResult(userStopResult).getStatus())
+ .isEqualTo(UserStopResult.STATUS_USER_DOES_NOT_EXIST);
+ assertThat(getResult(userStopResult).isSuccess()).isFalse();
+ }
+
+ @Test
+ public void testStopUser_systemUser() throws Exception {
+ mockStopUserWithDelayedLocking(
+ UserHandle.USER_SYSTEM, ActivityManager.USER_OP_ERROR_IS_SYSTEM);
+
+ AndroidFuture<UserStopResult> userStopResult = new AndroidFuture<>();
+ mCarUserService.stopUser(UserHandle.USER_SYSTEM, userStopResult);
+
+ assertThat(getResult(userStopResult).getStatus())
+ .isEqualTo(UserStopResult.STATUS_FAILURE_SYSTEM_USER);
+ assertThat(getResult(userStopResult).isSuccess()).isFalse();
+ }
+
+ @Test
+ public void testStopUser_currentUser() throws Exception {
+ int userId = 101;
+ UserInfo userInfo = new UserInfo(userId, "user101", NO_USER_INFO_FLAGS);
+ mockStopUserWithDelayedLocking(userId, ActivityManager.USER_OP_IS_CURRENT);
+
+ AndroidFuture<UserStopResult> userStopResult = new AndroidFuture<>();
+ mCarUserService.stopUser(userId, userStopResult);
+
+ assertThat(getResult(userStopResult).getStatus())
+ .isEqualTo(UserStopResult.STATUS_FAILURE_CURRENT_USER);
+ assertThat(getResult(userStopResult).isSuccess()).isFalse();
+ }
+
+ @Test
+ public void testStopBackgroundUserForSystemUser() throws Exception {
+ mockStopUserWithDelayedLocking(
+ UserHandle.USER_SYSTEM, ActivityManager.USER_OP_ERROR_IS_SYSTEM);
+
+ assertThat(mCarUserService.stopBackgroundUser(UserHandle.USER_SYSTEM)).isFalse();
+ }
+
+ @Test
+ public void testStopBackgroundUserForFgUser() throws Exception {
+ int userId = 101;
+ mockStopUserWithDelayedLocking(userId, ActivityManager.USER_OP_IS_CURRENT);
+
+ assertThat(mCarUserService.stopBackgroundUser(userId)).isFalse();
}
@Test
@@ -1775,6 +1845,80 @@
}
@Test
+ public void testStartUserInBackground_success() throws Exception {
+ int userId = 101;
+ UserInfo userInfo = new UserInfo(userId, "user1", NO_USER_INFO_FLAGS);
+ mockCurrentUser(mRegularUser);
+ mockUmGetUserInfo(mMockedUserManager, userInfo);
+ mockAmStartUserInBackground(userId, true);
+
+ AndroidFuture<UserStartResult> userStartResult = new AndroidFuture<>();
+ mCarUserService.startUserInBackground(userId, userStartResult);
+
+ assertThat(getResult(userStartResult).getStatus())
+ .isEqualTo(UserStartResult.STATUS_SUCCESSFUL);
+ assertThat(getResult(userStartResult).isSuccess()).isTrue();
+ }
+
+ @Test
+ public void testStartUserInBackground_permissionDenied() throws Exception {
+ int userId = 101;
+ mockManageUsersPermission(android.Manifest.permission.MANAGE_USERS, false);
+ mockManageUsersPermission(android.Manifest.permission.CREATE_USERS, false);
+
+ AndroidFuture<UserStartResult> userStartResult = new AndroidFuture<>();
+ assertThrows(SecurityException.class,
+ () -> mCarUserService.startUserInBackground(userId, userStartResult));
+ }
+
+ @Test
+ public void testStartUserInBackground_fail() throws Exception {
+ int userId = 101;
+ UserInfo userInfo = new UserInfo(userId, "user1", NO_USER_INFO_FLAGS);
+ mockCurrentUser(mRegularUser);
+ mockUmGetUserInfo(mMockedUserManager, userInfo);
+ mockAmStartUserInBackground(userId, false);
+
+ AndroidFuture<UserStartResult> userStartResult = new AndroidFuture<>();
+ mCarUserService.startUserInBackground(userId, userStartResult);
+
+ assertThat(getResult(userStartResult).getStatus())
+ .isEqualTo(UserStartResult.STATUS_ANDROID_FAILURE);
+ assertThat(getResult(userStartResult).isSuccess()).isFalse();
+ }
+
+ @Test
+ public void testStartUserInBackground_currentUser() throws Exception {
+ int userId = 101;
+ UserInfo userInfo = new UserInfo(userId, "user1", NO_USER_INFO_FLAGS);
+ mockGetCurrentUser(userId);
+ mockUmGetUserInfo(mMockedUserManager, userInfo);
+ mockAmStartUserInBackground(userId, true);
+
+ AndroidFuture<UserStartResult> userStartResult = new AndroidFuture<>();
+ mCarUserService.startUserInBackground(userId, userStartResult);
+
+ assertThat(getResult(userStartResult).getStatus())
+ .isEqualTo(UserStartResult.STATUS_SUCCESSFUL_USER_IS_CURRENT_USER);
+ assertThat(getResult(userStartResult).isSuccess()).isTrue();
+ }
+
+ @Test
+ public void testStartUserInBackground_userDoesNotExist() throws Exception {
+ int userId = 101;
+ mockCurrentUser(mRegularUser);
+ when(mMockedUserManager.getUserInfo(userId)).thenReturn(null);
+ mockAmStartUserInBackground(userId, true);
+
+ AndroidFuture<UserStartResult> userStartResult = new AndroidFuture<>();
+ mCarUserService.startUserInBackground(userId, userStartResult);
+
+ assertThat(getResult(userStartResult).getStatus())
+ .isEqualTo(UserStartResult.STATUS_USER_DOES_NOT_EXIST);
+ assertThat(getResult(userStartResult).isSuccess()).isFalse();
+ }
+
+ @Test
public void testIsHalSupported() throws Exception {
when(mUserHal.isSupported()).thenReturn(true);
assertThat(mCarUserService.isUserHalSupported()).isTrue();
@@ -2286,6 +2430,11 @@
mockGetCurrentUser(user.id);
}
+ private void mockAmStartUserInBackground(@UserIdInt int userId, boolean result)
+ throws Exception {
+ when(mMockedIActivityManager.startUserInBackground(userId)).thenReturn(result);
+ }
+
private void mockAmSwitchUser(@NonNull UserInfo user, boolean result) throws Exception {
when(mMockedIActivityManager.switchUser(user.id)).thenReturn(result);
}
@@ -2318,6 +2467,18 @@
/* listener= */ null);
}
+ private void mockStopUserWithDelayedLocking(@UserIdInt int userId, int result)
+ throws Exception {
+ when(mMockedIActivityManager.stopUserWithDelayedLocking(userId, true, null))
+ .thenReturn(result);
+ }
+
+ private void mockStopUserWithDelayedLockingThrowsRemoteException(@UserIdInt int userId)
+ throws Exception {
+ when(mMockedIActivityManager.stopUserWithDelayedLocking(userId, true, null))
+ .thenThrow(new RemoteException());
+ }
+
private void mockHalGetInitialInfo(@UserIdInt int currentUserId,
@NonNull InitialUserInfoResponse response) {
UsersInfo usersInfo = newUsersInfo(currentUserId);
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
index fa4c30f..cdb3452 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
@@ -18,6 +18,7 @@
import static android.automotive.watchdog.internal.ResourceOveruseActionType.KILLED;
import static android.automotive.watchdog.internal.ResourceOveruseActionType.NOT_KILLED;
+import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetAliveUsers;
import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetAllUsers;
import static android.car.watchdog.CarWatchdogManager.TIMEOUT_CRITICAL;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
@@ -51,13 +52,21 @@
import android.automotive.watchdog.internal.PackageIdentifier;
import android.automotive.watchdog.internal.PackageInfo;
import android.automotive.watchdog.internal.PackageIoOveruseStats;
+import android.automotive.watchdog.internal.PackageMetadata;
import android.automotive.watchdog.internal.PackageResourceOveruseAction;
+import android.automotive.watchdog.internal.PerStateIoOveruseThreshold;
+import android.automotive.watchdog.internal.ResourceSpecificConfiguration;
import android.automotive.watchdog.internal.UidType;
import android.car.test.mocks.AbstractExtendedMockitoTestCase;
import android.car.watchdog.CarWatchdogManager;
import android.car.watchdog.ICarWatchdogServiceCallback;
import android.car.watchdog.IResourceOveruseListener;
+import android.car.watchdog.IoOveruseAlertThreshold;
+import android.car.watchdog.IoOveruseConfiguration;
import android.car.watchdog.IoOveruseStats;
+import android.car.watchdog.PackageKillableState;
+import android.car.watchdog.PerStateBytes;
+import android.car.watchdog.ResourceOveruseConfiguration;
import android.car.watchdog.ResourceOveruseStats;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -66,13 +75,16 @@
import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.IBinder;
+import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.ArrayMap;
+import android.util.ArraySet;
import android.util.SparseArray;
-import android.util.SparseBooleanArray;
+
+import com.android.internal.util.function.TriConsumer;
import com.google.common.truth.Correspondence;
@@ -89,6 +101,8 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
/**
* <p>This class contains unit tests for the {@link CarWatchdogService}.
@@ -102,9 +116,9 @@
@Mock private Context mMockContext;
@Mock private PackageManager mMockPackageManager;
- @Mock private UserManager mUserManager;
- @Mock private IBinder mBinder;
- @Mock private ICarWatchdog mCarWatchdogDaemon;
+ @Mock private UserManager mMockUserManager;
+ @Mock private IBinder mMockBinder;
+ @Mock private ICarWatchdog mMockCarWatchdogDaemon;
private CarWatchdogService mCarWatchdogService;
private ICarWatchdogServiceForSystem mWatchdogServiceForSystemImpl;
@@ -135,7 +149,7 @@
@Test
public void testCarWatchdogServiceHealthCheck() throws Exception {
mWatchdogServiceForSystemImpl.checkIfAlive(123456, TIMEOUT_CRITICAL);
- verify(mCarWatchdogDaemon,
+ verify(mMockCarWatchdogDaemon,
timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
eq(mWatchdogServiceForSystemImpl), any(int[].class), eq(123456));
}
@@ -178,6 +192,204 @@
}
@Test
+ public void testGetResourceOveruseStats() throws Exception {
+ SparseArray<String> packageNamesByUid = new SparseArray<>();
+ packageNamesByUid.put(Binder.getCallingUid(), mMockContext.getPackageName());
+ injectUidToPackageNameMapping(packageNamesByUid);
+
+ List<PackageIoOveruseStats> packageIoOveruseStats = new ArrayList<>(
+ Collections.singletonList(
+ constructPackageIoOveruseStats(
+ Binder.getCallingUid(), /* shouldNotify= */false,
+ constructInternalIoOveruseStats(/* killableOnOveruse= */false,
+ /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
+ /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+ /* totalOveruses= */2)))
+ );
+ mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+
+ ResourceOveruseStats expectedStats =
+ constructResourceOveruseStats(packageNamesByUid.keyAt(0),
+ packageNamesByUid.valueAt(0), packageIoOveruseStats.get(0).ioOveruseStats);
+
+ ResourceOveruseStats actualStats = mCarWatchdogService.getResourceOveruseStats(
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
+
+ assertWithMessage("Expected: " + expectedStats.toString() + "\nActual: "
+ + actualStats.toString())
+ .that(ResourceOveruseStatsSubject.isEquals(actualStats, expectedStats)).isTrue();
+ }
+
+ @Test
+ public void testFailsGetResourceOveruseStatsWithInvalidArgs() throws Exception {
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.getResourceOveruseStats(/* resourceOveruseFlag= */0,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.getResourceOveruseStats(
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* maxStatsPeriod= */0));
+ }
+
+ @Test
+ public void testGetAllResourceOveruseStatsWithNoMinimum() throws Exception {
+ SparseArray<String> packageNamesByUid = new SparseArray<>();
+ packageNamesByUid.put(1103456, "third_party_package");
+ packageNamesByUid.put(1201278, "vendor_package.critical");
+ injectUidToPackageNameMapping(packageNamesByUid);
+
+ List<PackageIoOveruseStats> packageIoOveruseStats = new ArrayList<>(Arrays.asList(
+ constructPackageIoOveruseStats(packageNamesByUid.keyAt(0), /* shouldNotify= */false,
+ constructInternalIoOveruseStats(/* killableOnOveruse= */true,
+ /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
+ /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+ /* totalOveruses= */2)),
+ constructPackageIoOveruseStats(packageNamesByUid.keyAt(1), /* shouldNotify= */false,
+ constructInternalIoOveruseStats(/* killableOnOveruse= */false,
+ /* remainingWriteBytes= */constructPerStateBytes(450, 120, 340),
+ /* writtenBytes= */constructPerStateBytes(5000, 6000, 9000),
+ /* totalOveruses= */2))));
+ mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+
+ List<ResourceOveruseStats> expectedStats = new ArrayList<>(Arrays.asList(
+ constructResourceOveruseStats(packageNamesByUid.keyAt(0),
+ packageNamesByUid.valueAt(0), packageIoOveruseStats.get(0).ioOveruseStats),
+ constructResourceOveruseStats(packageNamesByUid.keyAt(1),
+ packageNamesByUid.valueAt(1), packageIoOveruseStats.get(1).ioOveruseStats))
+ );
+
+ List<ResourceOveruseStats> actualStats = mCarWatchdogService.getAllResourceOveruseStats(
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* minimumStatsFlag= */0,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
+
+ ResourceOveruseStatsSubject.assertThat(actualStats)
+ .containsExactlyElementsIn(expectedStats);
+ }
+
+ @Test
+ public void testFailsGetAllResourceOveruseStatsWithInvalidArgs() throws Exception {
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.getAllResourceOveruseStats(0, /* minimumStatsFlag= */0,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.getAllResourceOveruseStats(
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+ CarWatchdogManager.FLAG_MINIMUM_STATS_IO_1_MB
+ | CarWatchdogManager.FLAG_MINIMUM_STATS_IO_100_MB,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.getAllResourceOveruseStats(
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* minimumStatsFlag= */1 << 5,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.getAllResourceOveruseStats(
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO, /* minimumStatsFlag= */0,
+ /* maxStatsPeriod= */0));
+ }
+
+ @Test
+ public void testGetAllResourceOveruseStatsWithMinimum() throws Exception {
+ SparseArray<String> packageNamesByUid = new SparseArray<>();
+ packageNamesByUid.put(1103456, "third_party_package");
+ packageNamesByUid.put(1201278, "vendor_package.critical");
+ injectUidToPackageNameMapping(packageNamesByUid);
+
+ List<PackageIoOveruseStats> packageIoOveruseStats = new ArrayList<>(Arrays.asList(
+ constructPackageIoOveruseStats(packageNamesByUid.keyAt(0), /* shouldNotify= */false,
+ constructInternalIoOveruseStats(/* killableOnOveruse= */true,
+ /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
+ /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+ /* totalOveruses= */2)),
+ constructPackageIoOveruseStats(packageNamesByUid.keyAt(1), /* shouldNotify= */false,
+ constructInternalIoOveruseStats(/* killableOnOveruse= */false,
+ /* remainingWriteBytes= */constructPerStateBytes(450, 120, 340),
+ /* writtenBytes= */constructPerStateBytes(7000000, 6000, 9000),
+ /* totalOveruses= */2))));
+ mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+
+ List<ResourceOveruseStats> expectedStats = new ArrayList<>(Arrays.asList(
+ constructResourceOveruseStats(packageNamesByUid.keyAt(1),
+ packageNamesByUid.valueAt(1), packageIoOveruseStats.get(1).ioOveruseStats))
+ );
+
+ List<ResourceOveruseStats> actualStats = mCarWatchdogService.getAllResourceOveruseStats(
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+ CarWatchdogManager.FLAG_MINIMUM_STATS_IO_1_MB,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
+
+ ResourceOveruseStatsSubject.assertThat(actualStats)
+ .containsExactlyElementsIn(expectedStats);
+ }
+
+ @Test
+ public void testGetResourceOveruseStatsForUserPackage() throws Exception {
+ SparseArray<String> packageNamesByUid = new SparseArray<>();
+ packageNamesByUid.put(1103456, "third_party_package");
+ packageNamesByUid.put(1201278, "vendor_package.critical");
+ injectUidToPackageNameMapping(packageNamesByUid);
+
+ List<PackageIoOveruseStats> packageIoOveruseStats = new ArrayList<>(Arrays.asList(
+ constructPackageIoOveruseStats(packageNamesByUid.keyAt(0), /* shouldNotify= */false,
+ constructInternalIoOveruseStats(/* killableOnOveruse= */true,
+ /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
+ /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+ /* totalOveruses= */2)),
+ constructPackageIoOveruseStats(packageNamesByUid.keyAt(1), /* shouldNotify= */false,
+ constructInternalIoOveruseStats(/* killableOnOveruse= */false,
+ /* remainingWriteBytes= */constructPerStateBytes(450, 120, 340),
+ /* writtenBytes= */constructPerStateBytes(500, 600, 900),
+ /* totalOveruses= */2))));
+ mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+
+ ResourceOveruseStats expectedStats =
+ constructResourceOveruseStats(packageNamesByUid.keyAt(1),
+ packageNamesByUid.valueAt(1), packageIoOveruseStats.get(1).ioOveruseStats);
+
+ ResourceOveruseStats actualStats =
+ mCarWatchdogService.getResourceOveruseStatsForUserPackage(
+ "vendor_package.critical", new UserHandle(12),
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY);
+
+ assertWithMessage("Expected: " + expectedStats.toString() + "\nActual: "
+ + actualStats.toString())
+ .that(ResourceOveruseStatsSubject.isEquals(actualStats, expectedStats)).isTrue();
+ }
+
+ @Test
+ public void testFailsGetResourceOveruseStatsForUserPackageWithInvalidArgs() throws Exception {
+ assertThrows(NullPointerException.class,
+ () -> mCarWatchdogService.getResourceOveruseStatsForUserPackage(
+ /* packageName= */null, new UserHandle(10),
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+ assertThrows(NullPointerException.class,
+ () -> mCarWatchdogService.getResourceOveruseStatsForUserPackage("some.package",
+ /* userHandle= */null, CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.getResourceOveruseStatsForUserPackage("some.package",
+ UserHandle.ALL, CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.getResourceOveruseStatsForUserPackage("some.package",
+ new UserHandle(10), /* resourceOveruseFlag= */0,
+ CarWatchdogManager.STATS_PERIOD_CURRENT_DAY));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.getResourceOveruseStatsForUserPackage("some.package",
+ new UserHandle(10), CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO,
+ /* maxStatsPeriod= */0));
+ }
+
+ @Test
public void testAddResourceOveruseListenerThrowsWithInvalidFlag() throws Exception {
IResourceOveruseListener mockListener = createMockResourceOveruseListener();
assertThrows(IllegalArgumentException.class, () -> {
@@ -203,8 +415,9 @@
Collections.singletonList(constructPackageIoOveruseStats(
callingUid, /* shouldNotify= */true,
constructInternalIoOveruseStats(/* killableOnOveruse= */true,
- constructPerStateBytes(20, 20, 20),
- constructPerStateBytes(100, 200, 300), /* totalOveruses= */2))));
+ /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
+ /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+ /* totalOveruses= */2))));
mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
@@ -246,8 +459,9 @@
Collections.singletonList(constructPackageIoOveruseStats(
callingUid, /* shouldNotify= */true,
constructInternalIoOveruseStats(/* killableOnOveruse= */true,
- constructPerStateBytes(20, 20, 20),
- constructPerStateBytes(100, 200, 300), /* totalOveruses= */2))));
+ /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
+ /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+ /* totalOveruses= */2))));
mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
@@ -264,6 +478,308 @@
}
@Test
+ public void testSetKillablePackageAsUserWithPackageStats() throws Exception {
+ mockUmGetAliveUsers(mMockUserManager, 11, 12);
+ injectPackageInfos(new ArrayList<>(Arrays.asList("third_party_package",
+ "vendor_package.critical")));
+
+ SparseArray<String> packageNamesByUid = new SparseArray<>();
+ packageNamesByUid.put(1103456, "third_party_package");
+ packageNamesByUid.put(1101278, "vendor_package.critical");
+ injectIoOveruseStatsForPackages(packageNamesByUid,
+ new ArraySet<>(Collections.singletonList("third_party_package")));
+
+ UserHandle userHandle = new UserHandle(11);
+
+ mCarWatchdogService.setKillablePackageAsUser("third_party_package", userHandle,
+ /* isKillable= */ true);
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+ userHandle, /* isKillable= */ true));
+
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(userHandle)).containsExactly(
+ new PackageKillableState("third_party_package", 11,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("vendor_package.critical", 11,
+ PackageKillableState.KILLABLE_STATE_NEVER));
+
+ mCarWatchdogService.setKillablePackageAsUser("third_party_package", userHandle,
+ /* isKillable= */ false);
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+ userHandle, /* isKillable= */ false));
+
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(userHandle)).containsExactly(
+ new PackageKillableState("third_party_package", 11,
+ PackageKillableState.KILLABLE_STATE_NO),
+ new PackageKillableState("vendor_package.critical", 11,
+ PackageKillableState.KILLABLE_STATE_NEVER));
+ }
+
+ @Test
+ public void testSetKillablePackageAsUserWithNoPackageStats() throws Exception {
+ mockUmGetAliveUsers(mMockUserManager, 11, 12);
+ injectPackageInfos(new ArrayList<>(Arrays.asList("third_party_package",
+ "vendor_package.critical")));
+
+ UserHandle userHandle = new UserHandle(11);
+ mCarWatchdogService.setKillablePackageAsUser("third_party_package", userHandle,
+ /* isKillable= */ true);
+ mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+ userHandle, /* isKillable= */ true);
+
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(userHandle)).containsExactly(
+ new PackageKillableState("third_party_package", 11,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("vendor_package.critical", 11,
+ PackageKillableState.KILLABLE_STATE_NEVER));
+
+ mCarWatchdogService.setKillablePackageAsUser("third_party_package", userHandle,
+ /* isKillable= */ false);
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+ userHandle, /* isKillable= */ false));
+
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(userHandle)).containsExactly(
+ new PackageKillableState("third_party_package", 11,
+ PackageKillableState.KILLABLE_STATE_NO),
+ new PackageKillableState("vendor_package.critical", 11,
+ PackageKillableState.KILLABLE_STATE_NEVER));
+ }
+
+ @Test
+ public void testSetKillablePackageAsUserForAllUsersWithPackageStats() throws Exception {
+ mockUmGetAliveUsers(mMockUserManager, 11, 12);
+ injectPackageInfos(new ArrayList<>(Arrays.asList("third_party_package",
+ "vendor_package.critical")));
+
+ SparseArray<String> packageNamesByUid = new SparseArray<>();
+ packageNamesByUid.put(1103456, "third_party_package");
+ packageNamesByUid.put(1101278, "vendor_package.critical");
+ injectIoOveruseStatsForPackages(packageNamesByUid,
+ new ArraySet<>(Collections.singletonList("third_party_package")));
+
+ mCarWatchdogService.setKillablePackageAsUser("third_party_package", UserHandle.ALL,
+ /* isKillable= */ true);
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+ UserHandle.ALL, /* isKillable= */ true));
+
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
+ new PackageKillableState("third_party_package", 11,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("vendor_package.critical", 11,
+ PackageKillableState.KILLABLE_STATE_NEVER),
+ new PackageKillableState("third_party_package", 12,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("vendor_package.critical", 12,
+ PackageKillableState.KILLABLE_STATE_NEVER));
+
+ mCarWatchdogService.setKillablePackageAsUser("third_party_package", UserHandle.ALL,
+ /* isKillable= */ false);
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+ UserHandle.ALL, /* isKillable= */ false));
+
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
+ new PackageKillableState("third_party_package", 11,
+ PackageKillableState.KILLABLE_STATE_NO),
+ new PackageKillableState("vendor_package.critical", 11,
+ PackageKillableState.KILLABLE_STATE_NEVER),
+ new PackageKillableState("third_party_package", 12,
+ PackageKillableState.KILLABLE_STATE_NO),
+ new PackageKillableState("vendor_package.critical", 12,
+ PackageKillableState.KILLABLE_STATE_NEVER));
+ }
+
+ @Test
+ public void testSetKillablePackageAsUserForAllUsersWithNoPackageStats() throws Exception {
+ mockUmGetAliveUsers(mMockUserManager, 11, 12);
+ injectPackageInfos(new ArrayList<>(Arrays.asList("third_party_package",
+ "vendor_package.critical")));
+
+ mCarWatchdogService.setKillablePackageAsUser("third_party_package", UserHandle.ALL,
+ /* isKillable= */ true);
+ mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+ UserHandle.ALL, /* isKillable= */ true);
+
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
+ new PackageKillableState("third_party_package", 11,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("vendor_package.critical", 11,
+ PackageKillableState.KILLABLE_STATE_NEVER),
+ new PackageKillableState("third_party_package", 12,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("vendor_package.critical", 12,
+ PackageKillableState.KILLABLE_STATE_NEVER));
+
+ mCarWatchdogService.setKillablePackageAsUser("third_party_package", UserHandle.ALL,
+ /* isKillable= */ false);
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
+ UserHandle.ALL, /* isKillable= */ false));
+
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
+ new PackageKillableState("third_party_package", 11,
+ PackageKillableState.KILLABLE_STATE_NO),
+ new PackageKillableState("vendor_package.critical", 11,
+ PackageKillableState.KILLABLE_STATE_NEVER),
+ new PackageKillableState("third_party_package", 12,
+ PackageKillableState.KILLABLE_STATE_NO),
+ new PackageKillableState("vendor_package.critical", 12,
+ PackageKillableState.KILLABLE_STATE_NEVER));
+ }
+
+ @Test
+ public void testGetPackageKillableStatesAsUser() throws Exception {
+ mockUmGetAliveUsers(mMockUserManager, 11, 12);
+ injectPackageInfos(new ArrayList<>(Arrays.asList("third_party_package",
+ "vendor_package.critical")));
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(new UserHandle(11)))
+ .containsExactly(
+ new PackageKillableState("third_party_package", 11,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("vendor_package.critical", 11,
+ PackageKillableState.KILLABLE_STATE_NEVER));
+ }
+
+ @Test
+ public void testGetPackageKillableStatesAsUserForAllUsers() throws Exception {
+ mockUmGetAliveUsers(mMockUserManager, 11, 12);
+ injectPackageInfos(new ArrayList<>(Arrays.asList("third_party_package",
+ "vendor_package.critical")));
+ PackageKillableStateSubject.assertThat(
+ mCarWatchdogService.getPackageKillableStatesAsUser(UserHandle.ALL)).containsExactly(
+ new PackageKillableState("third_party_package", 11,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("vendor_package.critical", 11,
+ PackageKillableState.KILLABLE_STATE_NEVER),
+ new PackageKillableState("third_party_package", 12,
+ PackageKillableState.KILLABLE_STATE_YES),
+ new PackageKillableState("vendor_package.critical", 12,
+ PackageKillableState.KILLABLE_STATE_NEVER));
+ }
+
+ @Test
+ public void testSetResourceOveruseConfigurations() throws Exception {
+ List<ResourceOveruseConfiguration> resourceOveruseConfigs = new ArrayList<>(Arrays.asList(
+ sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+ sampleIoOveruseConfigurationBuilder(ComponentType.SYSTEM).build()).build(),
+ sampleResourceOveruseConfigurationBuilder(ComponentType.VENDOR,
+ sampleIoOveruseConfigurationBuilder(ComponentType.VENDOR).build()).build(),
+ sampleResourceOveruseConfigurationBuilder(ComponentType.THIRD_PARTY,
+ sampleIoOveruseConfigurationBuilder(ComponentType.THIRD_PARTY).build())
+ .build()));
+
+ mCarWatchdogService.setResourceOveruseConfigurations(resourceOveruseConfigs,
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO);
+
+ List<android.automotive.watchdog.internal.ResourceOveruseConfiguration>
+ actualConfigs = captureOnSetResourceOveruseConfigurations();
+
+ List<android.automotive.watchdog.internal.ResourceOveruseConfiguration>
+ expectedConfigs = new ArrayList<>(Arrays.asList(
+ sampleInternalResourceOveruseConfiguration(ComponentType.SYSTEM,
+ sampleInternalIoOveruseConfiguration(ComponentType.SYSTEM)),
+ sampleInternalResourceOveruseConfiguration(ComponentType.VENDOR,
+ sampleInternalIoOveruseConfiguration(ComponentType.VENDOR)),
+ sampleInternalResourceOveruseConfiguration(ComponentType.THIRD_PARTY,
+ sampleInternalIoOveruseConfiguration(ComponentType.THIRD_PARTY))));
+
+ InternalResourceOveruseConfigurationSubject.assertThat(actualConfigs)
+ .containsExactlyElementsIn(expectedConfigs);
+ }
+
+ @Test
+ public void testFailsSetResourceOveruseConfigurationsOnInvalidArgs() throws Exception {
+ assertThrows(NullPointerException.class,
+ () -> mCarWatchdogService.setResourceOveruseConfigurations(null,
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO));
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setResourceOveruseConfigurations(new ArrayList<>(),
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO));
+
+ List<ResourceOveruseConfiguration> resourceOveruseConfigs = new ArrayList<>(
+ Collections.singletonList(
+ sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+ sampleIoOveruseConfigurationBuilder(ComponentType.SYSTEM).build())
+ .build()));
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setResourceOveruseConfigurations(resourceOveruseConfigs,
+ 0));
+ }
+
+ @Test
+ public void testFailsSetResourceOveruseConfigurationsOnDuplicateComponents() throws Exception {
+ ResourceOveruseConfiguration config =
+ sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+ sampleIoOveruseConfigurationBuilder(ComponentType.SYSTEM).build()).build();
+ List<ResourceOveruseConfiguration> resourceOveruseConfigs = new ArrayList<>(Arrays.asList(
+ config, config));
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setResourceOveruseConfigurations(resourceOveruseConfigs,
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO));
+ }
+
+ @Test
+ public void testFailsSetResourceOveruseConfigurationsOnNullIoOveruseConfiguration()
+ throws Exception {
+ List<ResourceOveruseConfiguration> resourceOveruseConfigs = new ArrayList<>(
+ Collections.singletonList(
+ sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+ null).build()));
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.setResourceOveruseConfigurations(resourceOveruseConfigs,
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO));
+ }
+
+ @Test
+ public void testGetResourceOveruseConfigurations() throws Exception {
+ List<android.automotive.watchdog.internal.ResourceOveruseConfiguration>
+ internalResourceOveruseConfigs = new ArrayList<>(Arrays.asList(
+ sampleInternalResourceOveruseConfiguration(ComponentType.SYSTEM,
+ sampleInternalIoOveruseConfiguration(ComponentType.SYSTEM)),
+ sampleInternalResourceOveruseConfiguration(ComponentType.VENDOR,
+ sampleInternalIoOveruseConfiguration(ComponentType.VENDOR)),
+ sampleInternalResourceOveruseConfiguration(ComponentType.THIRD_PARTY,
+ sampleInternalIoOveruseConfiguration(ComponentType.THIRD_PARTY))));
+ doReturn(internalResourceOveruseConfigs).when(mMockCarWatchdogDaemon)
+ .getResourceOveruseConfigurations();
+
+ List<ResourceOveruseConfiguration> actualConfigs =
+ mCarWatchdogService.getResourceOveruseConfigurations(
+ CarWatchdogManager.FLAG_RESOURCE_OVERUSE_IO);
+
+ List<ResourceOveruseConfiguration> expectedConfigs = new ArrayList<>(Arrays.asList(
+ sampleResourceOveruseConfigurationBuilder(ComponentType.SYSTEM,
+ sampleIoOveruseConfigurationBuilder(ComponentType.SYSTEM).build()).build(),
+ sampleResourceOveruseConfigurationBuilder(ComponentType.VENDOR,
+ sampleIoOveruseConfigurationBuilder(ComponentType.VENDOR).build()).build(),
+ sampleResourceOveruseConfigurationBuilder(ComponentType.THIRD_PARTY,
+ sampleIoOveruseConfigurationBuilder(ComponentType.THIRD_PARTY).build())
+ .build()));
+
+ ResourceOveruseConfigurationSubject.assertThat(actualConfigs)
+ .containsExactlyElementsIn(expectedConfigs);
+ }
+
+ @Test
+ public void testFailsGetResourceOveruseConfigurationsOnInvalidArgs() throws Exception {
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarWatchdogService.getResourceOveruseConfigurations(0));
+ }
+
+ @Test
public void testLatestIoOveruseStats() throws Exception {
int criticalSysPkgUid = Binder.getCallingUid();
int nonCriticalSysPkgUid = getUid(1056);
@@ -275,12 +791,6 @@
packageNamesByUid.put(nonCriticalSysPkgUid, "non_critical.system.package");
packageNamesByUid.put(nonCriticalVndrPkgUid, "non_critical.vendor.package");
packageNamesByUid.put(thirdPartyPkgUid, "third_party.package");
-
- SparseBooleanArray killableOnOveruseByUid = new SparseBooleanArray();
- killableOnOveruseByUid.put(criticalSysPkgUid, false);
- killableOnOveruseByUid.put(nonCriticalSysPkgUid, true);
- killableOnOveruseByUid.put(nonCriticalVndrPkgUid, true);
- killableOnOveruseByUid.put(thirdPartyPkgUid, true);
injectUidToPackageNameMapping(packageNamesByUid);
IResourceOveruseListener mockSystemListener = createMockResourceOveruseListener();
@@ -298,28 +808,28 @@
List<PackageIoOveruseStats> packageIoOveruseStats = new ArrayList<>(Arrays.asList(
/* Overuse occurred but cannot be killed/disabled. */
constructPackageIoOveruseStats(criticalSysPkgUid, /* shouldNotify= */true,
- constructInternalIoOveruseStats(
- killableOnOveruseByUid.get(criticalSysPkgUid),
- constructPerStateBytes(0, 0, 0),
- constructPerStateBytes(100, 200, 300), /* totalOveruses= */2)),
+ constructInternalIoOveruseStats(/* killableOnOveruse= */false,
+ /* remainingWriteBytes= */constructPerStateBytes(0, 0, 0),
+ /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+ /* totalOveruses= */2)),
/* No overuse occurred but should be notified. */
constructPackageIoOveruseStats(nonCriticalSysPkgUid, /* shouldNotify= */true,
- constructInternalIoOveruseStats(
- killableOnOveruseByUid.get(nonCriticalSysPkgUid),
- constructPerStateBytes(20, 30, 40),
- constructPerStateBytes(100, 200, 300), /* totalOveruses= */2)),
+ constructInternalIoOveruseStats(/* killableOnOveruse= */true,
+ /* remainingWriteBytes= */constructPerStateBytes(20, 30, 40),
+ /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+ /* totalOveruses= */2)),
/* Neither overuse occurred nor be notified. */
constructPackageIoOveruseStats(nonCriticalVndrPkgUid, /* shouldNotify= */false,
- constructInternalIoOveruseStats(
- killableOnOveruseByUid.get(nonCriticalVndrPkgUid),
- constructPerStateBytes(200, 300, 400),
- constructPerStateBytes(100, 200, 300), /* totalOveruses= */2)),
+ constructInternalIoOveruseStats(/* killableOnOveruse= */true,
+ /* remainingWriteBytes= */constructPerStateBytes(200, 300, 400),
+ /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+ /* totalOveruses= */2)),
/* Overuse occurred and can be killed/disabled. */
constructPackageIoOveruseStats(thirdPartyPkgUid, /* shouldNotify= */true,
- constructInternalIoOveruseStats(
- killableOnOveruseByUid.get(thirdPartyPkgUid),
- constructPerStateBytes(0, 0, 0),
- constructPerStateBytes(100, 200, 300), /* totalOveruses= */2))));
+ constructInternalIoOveruseStats(/* killableOnOveruse= */true,
+ /* remainingWriteBytes= */constructPerStateBytes(0, 0, 0),
+ /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+ /* totalOveruses= */2))));
mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
@@ -327,19 +837,16 @@
expectedStats.add(constructResourceOveruseStats(criticalSysPkgUid,
packageNamesByUid.get(criticalSysPkgUid),
- killableOnOveruseByUid.get(criticalSysPkgUid),
packageIoOveruseStats.get(0).ioOveruseStats));
verifyOnOveruseCalled(expectedStats, mockListener);
expectedStats.add(constructResourceOveruseStats(nonCriticalSysPkgUid,
packageNamesByUid.get(nonCriticalSysPkgUid),
- killableOnOveruseByUid.get(nonCriticalSysPkgUid),
packageIoOveruseStats.get(1).ioOveruseStats));
expectedStats.add(constructResourceOveruseStats(thirdPartyPkgUid,
packageNamesByUid.get(thirdPartyPkgUid),
- killableOnOveruseByUid.get(thirdPartyPkgUid),
packageIoOveruseStats.get(3).ioOveruseStats));
verifyOnOveruseCalled(expectedStats, mockSystemListener);
@@ -499,19 +1006,19 @@
}
private void mockWatchdogDaemon() {
- doReturn(mBinder).when(() -> ServiceManager.getService(CAR_WATCHDOG_DAEMON_INTERFACE));
- when(mBinder.queryLocalInterface(anyString())).thenReturn(mCarWatchdogDaemon);
+ doReturn(mMockBinder).when(() -> ServiceManager.getService(CAR_WATCHDOG_DAEMON_INTERFACE));
+ when(mMockBinder.queryLocalInterface(anyString())).thenReturn(mMockCarWatchdogDaemon);
}
private void setupUsers() {
- when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
- mockUmGetAllUsers(mUserManager, new UserInfo[0]);
+ when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mMockUserManager);
+ mockUmGetAllUsers(mMockUserManager, new UserInfo[0]);
}
private ICarWatchdogServiceForSystem registerCarWatchdogService() throws Exception {
ArgumentCaptor<ICarWatchdogServiceForSystem> watchdogServiceForSystemImplCaptor =
ArgumentCaptor.forClass(ICarWatchdogServiceForSystem.class);
- verify(mCarWatchdogDaemon).registerCarWatchdogService(
+ verify(mMockCarWatchdogDaemon).registerCarWatchdogService(
watchdogServiceForSystemImplCaptor.capture());
return watchdogServiceForSystemImplCaptor.getValue();
}
@@ -520,15 +1027,24 @@
mCarWatchdogService.registerClient(client, TIMEOUT_CRITICAL);
mWatchdogServiceForSystemImpl.checkIfAlive(123456, TIMEOUT_CRITICAL);
ArgumentCaptor<int[]> notRespondingClients = ArgumentCaptor.forClass(int[].class);
- verify(mCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
+ verify(mMockCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
eq(mWatchdogServiceForSystemImpl), notRespondingClients.capture(), eq(123456));
assertThat(notRespondingClients.getValue().length).isEqualTo(0);
mWatchdogServiceForSystemImpl.checkIfAlive(987654, TIMEOUT_CRITICAL);
- verify(mCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
+ verify(mMockCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).tellCarWatchdogServiceAlive(
eq(mWatchdogServiceForSystemImpl), notRespondingClients.capture(), eq(987654));
assertThat(notRespondingClients.getValue().length).isEqualTo(badClientCount);
}
+ private List<android.automotive.watchdog.internal.ResourceOveruseConfiguration>
+ captureOnSetResourceOveruseConfigurations() throws Exception {
+ ArgumentCaptor<List<android.automotive.watchdog.internal.ResourceOveruseConfiguration>>
+ resourceOveruseConfigurationsCaptor = ArgumentCaptor.forClass(List.class);
+ verify(mMockCarWatchdogDaemon).updateResourceOveruseConfigurations(
+ resourceOveruseConfigurationsCaptor.capture());
+ return resourceOveruseConfigurationsCaptor.getValue();
+ }
+
private void injectUidToPackageNameMapping(SparseArray<String> packageNamesByUid) {
doAnswer(args -> {
int[] uids = args.getArgument(0);
@@ -540,6 +1056,49 @@
}).when(mMockPackageManager).getNamesForUids(any());
}
+ private void injectIoOveruseStatsForPackages(SparseArray<String> packageNamesByUid,
+ Set<String> killablePackages) throws RemoteException {
+ injectUidToPackageNameMapping(packageNamesByUid);
+ List<PackageIoOveruseStats> packageIoOveruseStats = new ArrayList<>();
+ for (int i = 0; i < packageNamesByUid.size(); ++i) {
+ String packageName = packageNamesByUid.valueAt(i);
+ int uid = packageNamesByUid.keyAt(i);
+ packageIoOveruseStats.add(constructPackageIoOveruseStats(uid,
+ false,
+ constructInternalIoOveruseStats(killablePackages.contains(packageName),
+ /* remainingWriteBytes= */constructPerStateBytes(20, 20, 20),
+ /* writtenBytes= */constructPerStateBytes(100, 200, 300),
+ /* totalOveruses= */2)));
+ }
+ mWatchdogServiceForSystemImpl.latestIoOveruseStats(packageIoOveruseStats);
+ }
+
+ private void injectPackageInfos(List<String> packageNames) {
+ List<android.content.pm.PackageInfo> packageInfos = new ArrayList<>();
+ TriConsumer<String, Integer, Integer> addPackageInfo =
+ (packageName, flags, privateFlags) -> {
+ android.content.pm.PackageInfo packageInfo =
+ new android.content.pm.PackageInfo();
+ packageInfo.packageName = packageName;
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.flags = flags;
+ packageInfo.applicationInfo.privateFlags = privateFlags;
+ packageInfos.add(packageInfo);
+ };
+ for (String packageName : packageNames) {
+ if (packageName.startsWith("system")) {
+ addPackageInfo.accept(packageName, ApplicationInfo.FLAG_SYSTEM, 0);
+ } else if (packageName.startsWith("vendor")) {
+ addPackageInfo.accept(packageName, ApplicationInfo.FLAG_SYSTEM,
+ ApplicationInfo.PRIVATE_FLAG_OEM);
+ } else {
+ addPackageInfo.accept(packageName, 0, 0);
+ }
+ }
+ doReturn(packageInfos).when(mMockPackageManager).getInstalledPackagesAsUser(
+ eq(0), anyInt());
+ }
+
private void mockApplicationEnabledSettingAccessors(IPackageManager pm) throws Exception {
doReturn(COMPONENT_ENABLED_STATE_ENABLED).when(pm)
.getApplicationEnabledSetting(anyString(), eq(UserHandle.myUserId()));
@@ -553,7 +1112,7 @@
ArgumentCaptor<List<PackageResourceOveruseAction>> resourceOveruseActionsCaptor =
ArgumentCaptor.forClass((Class) List.class);
- verify(mCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).actionTakenOnResourceOveruse(
+ verify(mMockCarWatchdogDaemon, timeout(MAX_WAIT_TIME_MS)).actionTakenOnResourceOveruse(
resourceOveruseActionsCaptor.capture());
List<PackageResourceOveruseAction> actual = resourceOveruseActionsCaptor.getValue();
@@ -586,6 +1145,118 @@
return UserHandle.getUid(UserHandle.myUserId(), appId);
}
+ private static ResourceOveruseConfiguration.Builder sampleResourceOveruseConfigurationBuilder(
+ int componentType, IoOveruseConfiguration ioOveruseConfig) {
+ String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType);
+ List<String> safeToKill = new ArrayList<>(Arrays.asList(
+ prefix + "_package.A", prefix + "_pkg.B"));
+ List<String> vendorPrefixes = new ArrayList<>(Arrays.asList(
+ prefix + "_package", prefix + "_pkg"));
+ Map<String, String> pkgToAppCategory = new ArrayMap<>();
+ pkgToAppCategory.put(prefix + "_package.A", "android.car.watchdog.app.category.MEDIA");
+ ResourceOveruseConfiguration.Builder configBuilder =
+ new ResourceOveruseConfiguration.Builder(componentType, safeToKill,
+ vendorPrefixes, pkgToAppCategory);
+ configBuilder.setIoOveruseConfiguration(ioOveruseConfig);
+ return configBuilder;
+ }
+
+ private static IoOveruseConfiguration.Builder sampleIoOveruseConfigurationBuilder(
+ int componentType) {
+ String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType);
+ PerStateBytes componentLevelThresholds = new PerStateBytes(
+ /* foregroundModeBytes= */10, /* backgroundModeBytes= */20,
+ /* garageModeBytes= */30);
+ Map<String, PerStateBytes> packageSpecificThresholds = new ArrayMap<>();
+ packageSpecificThresholds.put(prefix + "_package.A", new PerStateBytes(
+ /* foregroundModeBytes= */40, /* backgroundModeBytes= */50,
+ /* garageModeBytes= */60));
+
+ Map<String, PerStateBytes> appCategorySpecificThresholds = new ArrayMap<>();
+ appCategorySpecificThresholds.put(
+ ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MEDIA,
+ new PerStateBytes(/* foregroundModeBytes= */100, /* backgroundModeBytes= */200,
+ /* garageModeBytes= */300));
+ appCategorySpecificThresholds.put(
+ ResourceOveruseConfiguration.APPLICATION_CATEGORY_TYPE_MAPS,
+ new PerStateBytes(/* foregroundModeBytes= */1100, /* backgroundModeBytes= */2200,
+ /* garageModeBytes= */3300));
+
+ List<IoOveruseAlertThreshold> systemWideThresholds = new ArrayList<>(
+ Collections.singletonList(new IoOveruseAlertThreshold(/* durationInSeconds= */10,
+ /* writtenBytesPerSecond= */200)));
+
+ return new IoOveruseConfiguration.Builder(componentLevelThresholds,
+ packageSpecificThresholds, appCategorySpecificThresholds, systemWideThresholds);
+ }
+
+ private static android.automotive.watchdog.internal.ResourceOveruseConfiguration
+ sampleInternalResourceOveruseConfiguration(int componentType,
+ android.automotive.watchdog.internal.IoOveruseConfiguration ioOveruseConfig) {
+ String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType);
+ android.automotive.watchdog.internal.ResourceOveruseConfiguration config =
+ new android.automotive.watchdog.internal.ResourceOveruseConfiguration();
+ config.componentType = componentType;
+ config.safeToKillPackages = new ArrayList<>(Arrays.asList(
+ prefix + "_package.A", prefix + "_pkg.B"));
+ config.vendorPackagePrefixes = new ArrayList<>(Arrays.asList(
+ prefix + "_package", prefix + "_pkg"));
+
+ PackageMetadata metadata = new PackageMetadata();
+ metadata.packageName = prefix + "_package.A";
+ metadata.appCategoryType = ApplicationCategoryType.MEDIA;
+ config.packageMetadata = new ArrayList<>(Collections.singletonList(metadata));
+
+ ResourceSpecificConfiguration resourceSpecificConfig = new ResourceSpecificConfiguration();
+ resourceSpecificConfig.setIoOveruseConfiguration(ioOveruseConfig);
+ config.resourceSpecificConfigurations = new ArrayList<>(
+ Collections.singletonList(resourceSpecificConfig));
+
+ return config;
+ }
+
+ private static android.automotive.watchdog.internal.IoOveruseConfiguration
+ sampleInternalIoOveruseConfiguration(int componentType) {
+ String prefix = WatchdogPerfHandler.toComponentTypeStr(componentType);
+ android.automotive.watchdog.internal.IoOveruseConfiguration config =
+ new android.automotive.watchdog.internal.IoOveruseConfiguration();
+ config.componentLevelThresholds = constructPerStateIoOveruseThreshold(prefix,
+ /* fgBytes= */10, /* bgBytes= */20, /* gmBytes= */30);
+ config.packageSpecificThresholds = new ArrayList<>(Collections.singletonList(
+ constructPerStateIoOveruseThreshold(prefix + "_package.A", /* fgBytes= */40,
+ /* bgBytes= */50, /* gmBytes= */60)));
+ config.categorySpecificThresholds = new ArrayList<>(Arrays.asList(
+ constructPerStateIoOveruseThreshold(
+ WatchdogPerfHandler.INTERNAL_APPLICATION_CATEGORY_TYPE_MEDIA,
+ /* fgBytes= */100, /* bgBytes= */200, /* gmBytes= */300),
+ constructPerStateIoOveruseThreshold(
+ WatchdogPerfHandler.INTERNAL_APPLICATION_CATEGORY_TYPE_MAPS,
+ /* fgBytes= */1100, /* bgBytes= */2200, /* gmBytes= */3300)));
+ config.systemWideThresholds = new ArrayList<>(Collections.singletonList(
+ constructInternalIoOveruseAlertThreshold(/* duration= */10, /* writeBPS= */200)));
+ return config;
+ }
+
+ private static PerStateIoOveruseThreshold constructPerStateIoOveruseThreshold(String name,
+ long fgBytes, long bgBytes, long gmBytes) {
+ PerStateIoOveruseThreshold threshold = new PerStateIoOveruseThreshold();
+ threshold.name = name;
+ threshold.perStateWriteBytes = new android.automotive.watchdog.PerStateBytes();
+ threshold.perStateWriteBytes.foregroundBytes = fgBytes;
+ threshold.perStateWriteBytes.backgroundBytes = bgBytes;
+ threshold.perStateWriteBytes.garageModeBytes = gmBytes;
+ return threshold;
+ }
+
+ private static android.automotive.watchdog.internal.IoOveruseAlertThreshold
+ constructInternalIoOveruseAlertThreshold(long duration, long writeBPS) {
+ android.automotive.watchdog.internal.IoOveruseAlertThreshold threshold =
+ new android.automotive.watchdog.internal.IoOveruseAlertThreshold();
+ threshold.durationInSeconds = duration;
+ threshold.writtenBytesPerSecond = writeBPS;
+ return threshold;
+ }
+
private static PackageIoOveruseStats constructPackageIoOveruseStats(int uid,
boolean shouldNotify, android.automotive.watchdog.IoOveruseStats ioOveruseStats) {
PackageIoOveruseStats stats = new PackageIoOveruseStats();
@@ -596,11 +1267,10 @@
}
private static ResourceOveruseStats constructResourceOveruseStats(int uid, String packageName,
- boolean killableOnOveruse,
android.automotive.watchdog.IoOveruseStats internalIoOveruseStats) {
IoOveruseStats ioOveruseStats =
WatchdogPerfHandler.toIoOveruseStatsBuilder(internalIoOveruseStats)
- .setKillableOnOveruse(killableOnOveruse).build();
+ .setKillableOnOveruse(internalIoOveruseStats.killableOnOveruse).build();
return new ResourceOveruseStats.Builder(packageName, UserHandle.getUserHandleForUid(uid))
.setIoOveruseStats(ioOveruseStats).build();
@@ -629,7 +1299,7 @@
return perStateBytes;
}
- public static PackageResourceOveruseAction constructPackageResourceOveruseAction(
+ private static PackageResourceOveruseAction constructPackageResourceOveruseAction(
String packageName, int uid, int[] resourceTypes, int resourceOveruseActionType) {
PackageResourceOveruseAction action = new PackageResourceOveruseAction();
action.packageIdentifier = new PackageIdentifier();
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/InternalIoOveruseConfigurationSubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/InternalIoOveruseConfigurationSubject.java
new file mode 100644
index 0000000..47ad645
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/InternalIoOveruseConfigurationSubject.java
@@ -0,0 +1,161 @@
+/*
+ * 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.watchdog;
+
+import static com.google.common.truth.Truth.assertAbout;
+
+import android.annotation.Nullable;
+import android.automotive.watchdog.PerStateBytes;
+import android.automotive.watchdog.internal.IoOveruseAlertThreshold;
+import android.automotive.watchdog.internal.IoOveruseConfiguration;
+import android.automotive.watchdog.internal.PerStateIoOveruseThreshold;
+
+import com.google.common.truth.Correspondence;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public final class InternalIoOveruseConfigurationSubject extends Subject {
+ // Boiler-plate Subject.Factory for InternalIoOveruseConfigurationSubject
+ private static final Subject.Factory<
+ com.android.car.watchdog.InternalIoOveruseConfigurationSubject,
+ Iterable<IoOveruseConfiguration>> Io_OVERUSE_CONFIG_SUBJECT_FACTORY =
+ com.android.car.watchdog.InternalIoOveruseConfigurationSubject::new;
+
+ private final Iterable<IoOveruseConfiguration> mActual;
+
+ // User-defined entry point
+ public static InternalIoOveruseConfigurationSubject assertThat(
+ @Nullable Iterable<IoOveruseConfiguration> stats) {
+ return assertAbout(Io_OVERUSE_CONFIG_SUBJECT_FACTORY).that(stats);
+ }
+
+ public static Subject.Factory<InternalIoOveruseConfigurationSubject,
+ Iterable<IoOveruseConfiguration>> resourceOveruseStats() {
+ return Io_OVERUSE_CONFIG_SUBJECT_FACTORY;
+ }
+
+ public void containsExactly(IoOveruseConfiguration... stats) {
+ containsExactlyElementsIn(Arrays.asList(stats));
+ }
+
+ public void containsExactlyElementsIn(Iterable<IoOveruseConfiguration> expected) {
+ Truth.assertWithMessage("Expected: " + expected.toString() + "\nActual: "
+ + mActual.toString()).that(mActual)
+ .comparingElementsUsing(Correspondence.from(
+ InternalIoOveruseConfigurationSubject::isEquals, "is equal to"))
+ .containsExactlyElementsIn(expected);
+ }
+
+ public static boolean isEquals(IoOveruseConfiguration actual,
+ IoOveruseConfiguration expected) {
+ if (actual == null || expected == null) {
+ return (actual == null) && (expected == null);
+ }
+ return actual.componentLevelThresholds.name == expected.componentLevelThresholds.name
+ && isPerStateBytesEquals(actual.componentLevelThresholds.perStateWriteBytes,
+ expected.componentLevelThresholds.perStateWriteBytes)
+ && isPerStateThresholdEquals(actual.packageSpecificThresholds,
+ expected.packageSpecificThresholds)
+ && isPerStateThresholdEquals(actual.categorySpecificThresholds,
+ expected.categorySpecificThresholds)
+ && isAlertThresholdEquals(actual.systemWideThresholds,
+ expected.systemWideThresholds);
+ }
+
+ public static StringBuilder toStringBuilder(StringBuilder builder,
+ IoOveruseConfiguration config) {
+ builder.append("{Component-level thresholds: ")
+ .append(toString(config.componentLevelThresholds))
+ .append(", Package specific thresholds: [")
+ .append(config.packageSpecificThresholds.stream()
+ .map(InternalIoOveruseConfigurationSubject::toString)
+ .collect(Collectors.joining(", ")))
+ .append("], Category specific thresholds: [")
+ .append(config.categorySpecificThresholds.stream()
+ .map(InternalIoOveruseConfigurationSubject::toString)
+ .collect(Collectors.joining(", ")))
+ .append("], System wide thresholds: [")
+ .append(config.systemWideThresholds.stream()
+ .map(InternalIoOveruseConfigurationSubject::toString)
+ .collect(Collectors.joining(", ")))
+ .append("]}");
+ return builder;
+ }
+
+ public static String toString(PerStateIoOveruseThreshold threshold) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("{Name: ").append(threshold.name).append(", WriteBytes: {fgBytes: ")
+ .append(threshold.perStateWriteBytes.foregroundBytes).append(", bgBytes: ")
+ .append(threshold.perStateWriteBytes.backgroundBytes).append(", gmBytes: ")
+ .append(threshold.perStateWriteBytes.garageModeBytes).append("}}");
+ return builder.toString();
+ }
+
+ public static String toString(IoOveruseAlertThreshold threshold) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("{durationInSeconds: ").append(threshold.durationInSeconds)
+ .append(", writtenBytesPerSecond: ").append(threshold.writtenBytesPerSecond)
+ .append("}");
+ return builder.toString();
+ }
+
+ private InternalIoOveruseConfigurationSubject(FailureMetadata failureMetadata,
+ @Nullable Iterable<IoOveruseConfiguration> iterableSubject) {
+ super(failureMetadata, iterableSubject);
+ this.mActual = iterableSubject;
+ }
+
+ private static boolean isPerStateThresholdEquals(List<PerStateIoOveruseThreshold> actual,
+ List<PerStateIoOveruseThreshold> expected) {
+ Set<String> actualStr = toPerStateThresholdStrings(actual);
+ Set<String> expectedStr = toPerStateThresholdStrings(expected);
+ return actualStr.equals(expectedStr);
+ }
+
+ private static boolean isAlertThresholdEquals(List<IoOveruseAlertThreshold> actual,
+ List<IoOveruseAlertThreshold> expected) {
+ Set<String> actualStr = toAlertThresholdStrings(actual);
+ Set<String> expectedStr = toAlertThresholdStrings(expected);
+ return actualStr.equals(expectedStr);
+ }
+
+ private static boolean isPerStateBytesEquals(PerStateBytes acutal, PerStateBytes expected) {
+ return acutal.foregroundBytes == expected.foregroundBytes
+ && acutal.backgroundBytes == expected.backgroundBytes
+ && acutal.garageModeBytes == expected.garageModeBytes;
+ }
+
+ private static Set<String> toPerStateThresholdStrings(
+ List<PerStateIoOveruseThreshold> thresholds) {
+ return thresholds.stream().map(x -> String.format("%s:{%d,%d,%d}", x.name,
+ x.perStateWriteBytes.foregroundBytes, x.perStateWriteBytes.backgroundBytes,
+ x.perStateWriteBytes.garageModeBytes))
+ .collect(Collectors.toSet());
+ }
+
+ private static Set<String> toAlertThresholdStrings(
+ List<IoOveruseAlertThreshold> thresholds) {
+ return thresholds.stream().map(x -> String.format("%d:%d", x.durationInSeconds,
+ x.writtenBytesPerSecond)).collect(Collectors.toSet());
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/InternalResourceOveruseConfigurationSubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/InternalResourceOveruseConfigurationSubject.java
new file mode 100644
index 0000000..d324efe
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/InternalResourceOveruseConfigurationSubject.java
@@ -0,0 +1,191 @@
+/*
+ * 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.watchdog;
+
+import static com.google.common.truth.Truth.assertAbout;
+
+import android.annotation.Nullable;
+import android.automotive.watchdog.internal.PackageMetadata;
+import android.automotive.watchdog.internal.ResourceOveruseConfiguration;
+import android.automotive.watchdog.internal.ResourceSpecificConfiguration;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.truth.Correspondence;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public final class InternalResourceOveruseConfigurationSubject extends Subject {
+ // Boiler-plate Subject.Factory for InternalResourceOveruseConfigurationSubject
+ private static final Subject.Factory<
+ com.android.car.watchdog.InternalResourceOveruseConfigurationSubject,
+ Iterable<ResourceOveruseConfiguration>> RESOURCE_OVERUSE_CONFIG_SUBJECT_FACTORY =
+ com.android.car.watchdog.InternalResourceOveruseConfigurationSubject::new;
+
+ private static final Correspondence<List<PackageMetadata>, List<PackageMetadata>>
+ METADATA_LIST_CORRESPONDENCE = Correspondence.from(
+ InternalResourceOveruseConfigurationSubject::isPackageMetadataEquals,
+ "is equal to");
+
+ private static final Correspondence<List<ResourceSpecificConfiguration>,
+ List<ResourceSpecificConfiguration>>
+ RESOURCE_SPECIFIC_CONFIG_LIST_CORRESPONDENCE = Correspondence.from(
+ InternalResourceOveruseConfigurationSubject::isResourceSpecificConfigEquals,
+ "is equal to");
+
+ private final Iterable<ResourceOveruseConfiguration> mActual;
+
+ // User-defined entry point
+ public static InternalResourceOveruseConfigurationSubject assertThat(
+ @Nullable Iterable<ResourceOveruseConfiguration> stats) {
+ return assertAbout(RESOURCE_OVERUSE_CONFIG_SUBJECT_FACTORY).that(stats);
+ }
+
+ public static Subject.Factory<InternalResourceOveruseConfigurationSubject,
+ Iterable<ResourceOveruseConfiguration>> resourceOveruseStats() {
+ return RESOURCE_OVERUSE_CONFIG_SUBJECT_FACTORY;
+ }
+
+ public void containsExactly(ResourceOveruseConfiguration... stats) {
+ containsExactlyElementsIn(Arrays.asList(stats));
+ }
+
+ public void containsExactlyElementsIn(Iterable<ResourceOveruseConfiguration> expected) {
+ Truth.assertWithMessage("Expected: " + toString(expected) + "\nActual: "
+ + toString(mActual)).that(mActual)
+ .comparingElementsUsing(Correspondence.from(
+ InternalResourceOveruseConfigurationSubject::isEquals, "is equal to"))
+ .containsExactlyElementsIn(expected);
+ }
+
+ public static boolean isPackageMetadataEquals(List<PackageMetadata> actual,
+ List<PackageMetadata> expected) {
+ Set<String> actualStr = toMetadataStrings(actual);
+ Set<String> expectedStr = toMetadataStrings(expected);
+ return actualStr.equals(expectedStr);
+ }
+
+ public static boolean isResourceSpecificConfigEquals(List<ResourceSpecificConfiguration> actual,
+ List<ResourceSpecificConfiguration> expected) {
+ if (actual.size() != expected.size()) {
+ return false;
+ }
+ if (actual.size() == 0) {
+ return true;
+ }
+ /*
+ * When more resource types are added make this comparison more generic. Because the
+ * resource overuse configuration should contain only one I/O overuse configuration, the
+ * comparison only checks for first I/O overuse configuration.
+ */
+ ResourceSpecificConfiguration actualElement = actual.get(0);
+ ResourceSpecificConfiguration expectedElement = expected.get(0);
+ if (actualElement.getTag() != expectedElement.getTag()) {
+ return false;
+ }
+ if (actualElement.getTag() != ResourceSpecificConfiguration.ioOveruseConfiguration) {
+ return false;
+ }
+ return InternalIoOveruseConfigurationSubject.isEquals(
+ actualElement.getIoOveruseConfiguration(),
+ expectedElement.getIoOveruseConfiguration());
+ }
+
+ public static boolean isEquals(ResourceOveruseConfiguration actual,
+ ResourceOveruseConfiguration expected) {
+ if (actual == null || expected == null) {
+ return (actual == null) && (expected == null);
+ }
+ return actual.componentType == expected.componentType
+ && ImmutableSet.copyOf(actual.safeToKillPackages).equals(
+ ImmutableSet.copyOf(expected.safeToKillPackages))
+ && ImmutableSet.copyOf(actual.vendorPackagePrefixes).equals(
+ ImmutableSet.copyOf(expected.vendorPackagePrefixes))
+ && METADATA_LIST_CORRESPONDENCE.compare(actual.packageMetadata,
+ expected.packageMetadata)
+ && RESOURCE_SPECIFIC_CONFIG_LIST_CORRESPONDENCE.compare(
+ actual.resourceSpecificConfigurations,
+ expected.resourceSpecificConfigurations);
+ }
+
+ private InternalResourceOveruseConfigurationSubject(FailureMetadata failureMetadata,
+ @Nullable Iterable<ResourceOveruseConfiguration> iterableSubject) {
+ super(failureMetadata, iterableSubject);
+ this.mActual = iterableSubject;
+ }
+
+ private static Set<String> toMetadataStrings(List<PackageMetadata> metadata) {
+ return metadata.stream().map(x -> String.format("%s:%d", x.packageName, x.appCategoryType))
+ .collect(Collectors.toSet());
+ }
+
+ private static String toString(
+ Iterable<ResourceOveruseConfiguration> configs) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("[");
+ for (ResourceOveruseConfiguration config : configs) {
+ toStringBuilder(builder, config).append(", ");
+ }
+ builder.append("]");
+ return builder.toString();
+ }
+
+ private static StringBuilder toStringBuilder(StringBuilder builder,
+ ResourceOveruseConfiguration config) {
+ builder.append("{Component type: ").append(config.componentType)
+ .append(", Safe-to-kill packages: [")
+ .append(config.safeToKillPackages.stream().map(Object::toString)
+ .collect(Collectors.joining(", ")))
+ .append("], Vendor package prefixes: [")
+ .append(config.vendorPackagePrefixes.stream().map(Object::toString)
+ .collect(Collectors.joining(", ")))
+ .append("], Package Metadata: [")
+ .append(config.packageMetadata.stream()
+ .map(InternalResourceOveruseConfigurationSubject::toPackageMetadataString)
+ .collect(Collectors.joining(", ")))
+ .append("], Resource specific configurations: [")
+ .append(config.resourceSpecificConfigurations.stream()
+ .map(InternalResourceOveruseConfigurationSubject
+ ::toResourceOveruseConfigString).collect(Collectors.joining(", ")))
+ .append("]}");
+ return builder;
+ }
+
+ private static String toResourceOveruseConfigString(ResourceSpecificConfiguration config) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("{Tag: ").append(config.getTag()).append(", Value: ");
+ if (config.getTag() == ResourceSpecificConfiguration.ioOveruseConfiguration) {
+ InternalIoOveruseConfigurationSubject.toStringBuilder(builder,
+ config.getIoOveruseConfiguration());
+ } else {
+ builder.append("UNKNOWN");
+ }
+ return builder.toString();
+ }
+
+ private static String toPackageMetadataString(PackageMetadata metadata) {
+ StringBuilder builder = new StringBuilder();
+ builder.append("{Name: ").append(metadata.packageName).append(", App category type: ")
+ .append(metadata.appCategoryType).append("}");
+ return builder.toString();
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/IoOveruseConfigurationSubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/IoOveruseConfigurationSubject.java
new file mode 100644
index 0000000..3629898
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/IoOveruseConfigurationSubject.java
@@ -0,0 +1,123 @@
+/*
+ * 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.watchdog;
+
+import static com.google.common.truth.Truth.assertAbout;
+
+import android.annotation.Nullable;
+import android.car.watchdog.IoOveruseAlertThreshold;
+import android.car.watchdog.IoOveruseConfiguration;
+import android.car.watchdog.PerStateBytes;
+
+import com.google.common.base.Equivalence;
+import com.google.common.collect.Maps;
+import com.google.common.truth.Correspondence;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public final class IoOveruseConfigurationSubject extends Subject {
+ // Boiler-plate Subject.Factory for IoOveruseConfigurationSubject
+ private static final Subject.Factory<
+ com.android.car.watchdog.IoOveruseConfigurationSubject,
+ Iterable<IoOveruseConfiguration>> Io_OVERUSE_CONFIG_SUBJECT_FACTORY =
+ com.android.car.watchdog.IoOveruseConfigurationSubject::new;
+
+ private static final Equivalence<PerStateBytes> PER_STATE_BYTES_EQUIVALENCE =
+ new Equivalence<PerStateBytes>() {
+ @Override
+ protected boolean doEquivalent(PerStateBytes actual, PerStateBytes expected) {
+ return isPerStateBytesEquals(actual, expected);
+ }
+
+ @Override
+ protected int doHash(PerStateBytes perStateBytes) {
+ return (int) ((perStateBytes.getForegroundModeBytes() * 10 ^ 4)
+ + (perStateBytes.getBackgroundModeBytes() * 10 ^ 3)
+ + perStateBytes.getGarageModeBytes());
+ }
+ };
+
+ private final Iterable<IoOveruseConfiguration> mActual;
+
+ // User-defined entry point
+ public static IoOveruseConfigurationSubject assertThat(
+ @Nullable Iterable<IoOveruseConfiguration> stats) {
+ return assertAbout(Io_OVERUSE_CONFIG_SUBJECT_FACTORY).that(stats);
+ }
+
+ public static Subject.Factory<IoOveruseConfigurationSubject,
+ Iterable<IoOveruseConfiguration>> resourceOveruseStats() {
+ return Io_OVERUSE_CONFIG_SUBJECT_FACTORY;
+ }
+
+ public void containsExactly(IoOveruseConfiguration... stats) {
+ containsExactlyElementsIn(Arrays.asList(stats));
+ }
+
+ public void containsExactlyElementsIn(Iterable<IoOveruseConfiguration> expected) {
+ Truth.assertThat(mActual)
+ .comparingElementsUsing(Correspondence.from(
+ IoOveruseConfigurationSubject::isEquals, "is equal to"))
+ .containsExactlyElementsIn(expected);
+ }
+
+ public static boolean isEquals(IoOveruseConfiguration actual,
+ IoOveruseConfiguration expected) {
+ if (actual == null || expected == null) {
+ return (actual == null) && (expected == null);
+ }
+
+ return isPerStateBytesEquals(actual.getComponentLevelThresholds(),
+ expected.getComponentLevelThresholds())
+ && Maps.difference(actual.getPackageSpecificThresholds(),
+ expected.getPackageSpecificThresholds(), PER_STATE_BYTES_EQUIVALENCE).areEqual()
+ && Maps.difference(actual.getAppCategorySpecificThresholds(),
+ expected.getAppCategorySpecificThresholds(), PER_STATE_BYTES_EQUIVALENCE).areEqual()
+ && isAlertThresholdEquals(actual.getSystemWideThresholds(),
+ expected.getSystemWideThresholds());
+ }
+
+ private IoOveruseConfigurationSubject(FailureMetadata failureMetadata,
+ @Nullable Iterable<IoOveruseConfiguration> iterableSubject) {
+ super(failureMetadata, iterableSubject);
+ this.mActual = iterableSubject;
+ }
+
+ private static boolean isPerStateBytesEquals(PerStateBytes actual, PerStateBytes expected) {
+ return actual.getForegroundModeBytes() == expected.getForegroundModeBytes()
+ && actual.getBackgroundModeBytes() == expected.getBackgroundModeBytes()
+ && actual.getGarageModeBytes() == expected.getGarageModeBytes();
+ }
+
+ private static boolean isAlertThresholdEquals(List<IoOveruseAlertThreshold> actual,
+ List<IoOveruseAlertThreshold> expected) {
+ Set<String> actualStr = toAlertThresholdStrings(actual);
+ Set<String> expectedStr = toAlertThresholdStrings(expected);
+ return actualStr.equals(expectedStr);
+ }
+
+ private static Set<String> toAlertThresholdStrings(List<IoOveruseAlertThreshold> thresholds) {
+ return thresholds.stream().map(x -> String.format("%d:%d", x.getDurationInSeconds(),
+ x.getWrittenBytesPerSecond())).collect(Collectors.toSet());
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/PackageKillableStateSubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/PackageKillableStateSubject.java
new file mode 100644
index 0000000..953d1df
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/PackageKillableStateSubject.java
@@ -0,0 +1,77 @@
+/*
+ * 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.watchdog;
+
+import static com.google.common.truth.Truth.assertAbout;
+
+import android.annotation.Nullable;
+import android.car.watchdog.PackageKillableState;
+
+import com.google.common.truth.Correspondence;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
+
+import java.util.Arrays;
+
+public final class PackageKillableStateSubject extends Subject {
+ // Boiler-plate Subject.Factory for PackageKillableStateSubject
+ private static final Subject.Factory<
+ com.android.car.watchdog.PackageKillableStateSubject,
+ Iterable<PackageKillableState>> PACKAGE_KILLABLE_STATE_SUBJECT_FACTORY =
+ com.android.car.watchdog.PackageKillableStateSubject::new;
+
+ private final Iterable<PackageKillableState> mActual;
+
+ // User-defined entry point
+ public static PackageKillableStateSubject assertThat(
+ @Nullable Iterable<PackageKillableState> stats) {
+ return assertAbout(PACKAGE_KILLABLE_STATE_SUBJECT_FACTORY).that(stats);
+ }
+
+ public static Subject.Factory<PackageKillableStateSubject,
+ Iterable<PackageKillableState>> resourceOveruseStats() {
+ return PACKAGE_KILLABLE_STATE_SUBJECT_FACTORY;
+ }
+
+ public void containsExactly(PackageKillableState... stats) {
+ containsExactlyElementsIn(Arrays.asList(stats));
+ }
+
+ public void containsExactlyElementsIn(Iterable<PackageKillableState> expected) {
+ Truth.assertThat(mActual)
+ .comparingElementsUsing(Correspondence.from(
+ PackageKillableStateSubject::isEquals, "is equal to"))
+ .containsExactlyElementsIn(expected);
+ }
+
+ public static boolean isEquals(PackageKillableState actual,
+ PackageKillableState expected) {
+ if (actual == null || expected == null) {
+ return (actual == null) && (expected == null);
+ }
+ return actual.getPackageName().equals(expected.getPackageName())
+ && actual.getUserId() == expected.getUserId()
+ && actual.getKillableState() == expected.getKillableState();
+ }
+
+ private PackageKillableStateSubject(FailureMetadata failureMetadata,
+ @Nullable Iterable<PackageKillableState> iterableSubject) {
+ super(failureMetadata, iterableSubject);
+ this.mActual = iterableSubject;
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/ResourceOveruseConfigurationSubject.java b/tests/carservice_unit_test/src/com/android/car/watchdog/ResourceOveruseConfigurationSubject.java
new file mode 100644
index 0000000..5bfdf17
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/ResourceOveruseConfigurationSubject.java
@@ -0,0 +1,85 @@
+/*
+ * 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.watchdog;
+
+import static com.google.common.truth.Truth.assertAbout;
+
+import android.annotation.Nullable;
+import android.car.watchdog.ResourceOveruseConfiguration;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
+import com.google.common.truth.Correspondence;
+import com.google.common.truth.FailureMetadata;
+import com.google.common.truth.Subject;
+import com.google.common.truth.Truth;
+
+import java.util.Arrays;
+
+public final class ResourceOveruseConfigurationSubject extends Subject {
+ // Boiler-plate Subject.Factory for ResourceOveruseConfigurationSubject
+ private static final Subject.Factory<
+ com.android.car.watchdog.ResourceOveruseConfigurationSubject,
+ Iterable<ResourceOveruseConfiguration>> RESOURCE_OVERUSE_CONFIG_SUBJECT_FACTORY =
+ com.android.car.watchdog.ResourceOveruseConfigurationSubject::new;
+
+ private final Iterable<ResourceOveruseConfiguration> mActual;
+
+ // User-defined entry point
+ public static ResourceOveruseConfigurationSubject assertThat(
+ @Nullable Iterable<ResourceOveruseConfiguration> stats) {
+ return assertAbout(RESOURCE_OVERUSE_CONFIG_SUBJECT_FACTORY).that(stats);
+ }
+
+ public static Subject.Factory<ResourceOveruseConfigurationSubject,
+ Iterable<ResourceOveruseConfiguration>> resourceOveruseStats() {
+ return RESOURCE_OVERUSE_CONFIG_SUBJECT_FACTORY;
+ }
+
+ public void containsExactly(ResourceOveruseConfiguration... stats) {
+ containsExactlyElementsIn(Arrays.asList(stats));
+ }
+
+ public void containsExactlyElementsIn(Iterable<ResourceOveruseConfiguration> expected) {
+ Truth.assertThat(mActual)
+ .comparingElementsUsing(Correspondence.from(
+ ResourceOveruseConfigurationSubject::isEquals, "is equal to"))
+ .containsExactlyElementsIn(expected);
+ }
+
+ public static boolean isEquals(ResourceOveruseConfiguration actual,
+ ResourceOveruseConfiguration expected) {
+ if (actual == null || expected == null) {
+ return (actual == null) && (expected == null);
+ }
+ return actual.getComponentType() == expected.getComponentType()
+ && ImmutableSet.copyOf(actual.getSafeToKillPackages()).equals(
+ ImmutableSet.copyOf(expected.getSafeToKillPackages()))
+ && ImmutableSet.copyOf(actual.getVendorPackagePrefixes()).equals(
+ ImmutableSet.copyOf(expected.getVendorPackagePrefixes()))
+ && Maps.difference(actual.getPackagesToAppCategoryTypes(),
+ expected.getPackagesToAppCategoryTypes()).areEqual()
+ && IoOveruseConfigurationSubject.isEquals(actual.getIoOveruseConfiguration(),
+ expected.getIoOveruseConfiguration());
+ }
+
+ private ResourceOveruseConfigurationSubject(FailureMetadata failureMetadata,
+ @Nullable Iterable<ResourceOveruseConfiguration> iterableSubject) {
+ super(failureMetadata, iterableSubject);
+ this.mActual = iterableSubject;
+ }
+}