Merge "Create a new car service lib just for the test lib"
diff --git a/car-lib/src/android/car/app/CarActivityView.java b/car-lib/src/android/car/app/CarActivityView.java
new file mode 100644
index 0000000..efefbe1
--- /dev/null
+++ b/car-lib/src/android/car/app/CarActivityView.java
@@ -0,0 +1,156 @@
+/*
+ * 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 android.car.app;
+
+import android.annotation.Nullable;
+import android.app.ActivityView;
+import android.car.Car;
+import android.car.drivingstate.CarUxRestrictionsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Display;
+
+/**
+ * CarActivityView is a special kind of ActivityView that can track which display the ActivityView
+ * is placed. This information can be used to enforce the driving safety.
+ *
+ * @hide
+ */
+public final class CarActivityView extends ActivityView {
+
+ private static final String TAG = CarActivityView.class.getSimpleName();
+
+ // volatile, since mUserActivityViewCallback can be accessed from Main and Binder thread.
+ @Nullable private volatile StateCallback mUserActivityViewCallback;
+
+ @Nullable private CarUxRestrictionsManager mUxRestrictionsManager;
+
+ private int mVirtualDisplayId = Display.INVALID_DISPLAY;
+
+ public CarActivityView(Context context) {
+ this(context, /*attrs=*/ null);
+ }
+
+ public CarActivityView(Context context, AttributeSet attrs) {
+ this(context, attrs, /*defStyle=*/ 0);
+ }
+
+ public CarActivityView(Context context, AttributeSet attrs, int defStyle) {
+ this(context, attrs, defStyle, /*singleTaskInstance=*/ false);
+ }
+
+ public CarActivityView(
+ Context context, AttributeSet attrs, int defStyle, boolean singleTaskInstance) {
+ super(context, attrs, defStyle, singleTaskInstance);
+ super.setCallback(new CarActivityViewCallback());
+ Car.createCar(mContext, /*handler=*/ null,
+ Car.CAR_WAIT_TIMEOUT_DO_NOT_WAIT,
+ (car, ready) -> {
+ // Expect to be called in the main thread, since passed a 'null' handler
+ // in Car.createCar().
+ if (!ready) return;
+ mUxRestrictionsManager = (CarUxRestrictionsManager) car.getCarManager(
+ Car.CAR_UX_RESTRICTION_SERVICE);
+ if (mVirtualDisplayId != Display.INVALID_DISPLAY) {
+ // When the CarService is reconnected, we'd like to report the physical
+ // display id again, since the previously reported mapping could be gone.
+ reportPhysicalDisplayId(
+ mUxRestrictionsManager, mVirtualDisplayId, mContext.getDisplayId());
+ }
+ });
+ }
+
+ @Override
+ public void setCallback(StateCallback callback) {
+ mUserActivityViewCallback = callback;
+ if (getVirtualDisplayId() != Display.INVALID_DISPLAY && callback != null) {
+ callback.onActivityViewReady(this);
+ }
+ }
+
+ private static void reportPhysicalDisplayId(CarUxRestrictionsManager manager,
+ int virtualDisplayId, int physicalDisplayId) {
+ Log.d(TAG, "reportPhysicalDisplayId: virtualDisplayId=" + virtualDisplayId
+ + ", physicalDisplayId=" + physicalDisplayId);
+ if (virtualDisplayId == Display.INVALID_DISPLAY) {
+ throw new RuntimeException("Has no virtual display to report.");
+ }
+ if (manager == null) {
+ Log.w(TAG, "CarUxRestrictionsManager is not ready yet");
+ return;
+ }
+ manager.reportVirtualDisplayToPhysicalDisplay(virtualDisplayId, physicalDisplayId);
+ }
+
+ // Intercepts ActivityViewCallback and reports it's display-id changes.
+ private class CarActivityViewCallback extends StateCallback {
+ // onActivityViewReady() and onActivityViewDestroyed() are called in the main thread.
+ @Override
+ public void onActivityViewReady(ActivityView activityView) {
+ // Stores the virtual display id to use it onActivityViewDestroyed().
+ mVirtualDisplayId = getVirtualDisplayId();
+ reportPhysicalDisplayId(
+ mUxRestrictionsManager, mVirtualDisplayId, mContext.getDisplayId());
+
+ StateCallback stateCallback = mUserActivityViewCallback;
+ if (stateCallback != null) {
+ stateCallback.onActivityViewReady(activityView);
+ }
+ }
+
+ @Override
+ public void onActivityViewDestroyed(ActivityView activityView) {
+ // getVirtualDisplayId() will return INVALID_DISPLAY inside onActivityViewDestroyed(),
+ // because AV.mVirtualDisplay was already released during AV.performRelease().
+ int virtualDisplayId = mVirtualDisplayId;
+ mVirtualDisplayId = Display.INVALID_DISPLAY;
+ reportPhysicalDisplayId(
+ mUxRestrictionsManager, virtualDisplayId, Display.INVALID_DISPLAY);
+
+ StateCallback stateCallback = mUserActivityViewCallback;
+ if (stateCallback != null) {
+ stateCallback.onActivityViewDestroyed(activityView);
+ }
+ }
+
+ @Override
+ public void onTaskCreated(int taskId, ComponentName componentName) {
+ StateCallback stateCallback = mUserActivityViewCallback;
+ if (stateCallback != null) {
+ stateCallback.onTaskCreated(taskId, componentName);
+ }
+ }
+
+ @Override
+ public void onTaskMovedToFront(int taskId) {
+ StateCallback stateCallback = mUserActivityViewCallback;
+ if (stateCallback != null) {
+ stateCallback.onTaskMovedToFront(taskId);
+ }
+ }
+
+ @Override
+ public void onTaskRemovalStarted(int taskId) {
+ StateCallback stateCallback = mUserActivityViewCallback;
+ if (stateCallback != null) {
+ stateCallback.onTaskRemovalStarted(taskId);
+ }
+ }
+ }
+}
diff --git a/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java b/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java
index be194b8..9533322 100644
--- a/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java
+++ b/car-lib/src/android/car/drivingstate/CarUxRestrictionsManager.java
@@ -22,8 +22,10 @@
import android.annotation.RequiresPermission;
import android.car.Car;
import android.car.CarManagerBase;
+import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
+import android.os.IRemoteCallback;
import android.os.Looper;
import android.os.Message;
import android.os.RemoteException;
@@ -408,4 +410,44 @@
return mDisplayId;
}
+
+ // Dummy Callback to identify the requester of reportVirtualDisplayToPhysicalDisplay() and
+ // to clean up the internal data when the requester is crashed.
+ private final IRemoteCallback mRequester = new IRemoteCallback.Stub() {
+ @Override public void sendResult(Bundle data) {} // Unused
+ };
+
+ /**
+ * Reports the mapping the virtual display to the physical display.
+ *
+ * @param virtualDisplayId the display id of the embedded virtual display.
+ * @parom physicalDisplayId the display id where the ActivityView is placed in.
+ * @hide
+ */
+ public void reportVirtualDisplayToPhysicalDisplay(int virtualDisplayId, int physicalDisplayId) {
+ try {
+ mUxRService.reportVirtualDisplayToPhysicalDisplay(mRequester,
+ virtualDisplayId, physicalDisplayId);
+ } catch (RemoteException e) {
+ handleRemoteExceptionFromCarService(e);
+ }
+ }
+
+ /**
+ * Finds out the physical display id where ActivityView is actually located in.
+ * If the given ActivityView is placed inside of another ActivityView, then it will return
+ * the display id where the parent ActivityView is located in.
+ *
+ * @param displayId the display id of the embedded virtual display of ActivityView.
+ * @return the physical display id where ActivityView is actually located in.
+ * @hide
+ */
+ public int getMappedPhysicalDisplayOfVirtualDisplay(int displayId) {
+ try {
+ return mUxRService.getMappedPhysicalDisplayOfVirtualDisplay(displayId);
+ } catch (RemoteException e) {
+ // When CarService isn't ready, we'll return DEFAULT_DISPLAY defensively.
+ return handleRemoteExceptionFromCarService(e, Display.DEFAULT_DISPLAY);
+ }
+ }
}
diff --git a/car-lib/src/android/car/drivingstate/ICarUxRestrictionsManager.aidl b/car-lib/src/android/car/drivingstate/ICarUxRestrictionsManager.aidl
index 7f3a6e9..64daa57 100644
--- a/car-lib/src/android/car/drivingstate/ICarUxRestrictionsManager.aidl
+++ b/car-lib/src/android/car/drivingstate/ICarUxRestrictionsManager.aidl
@@ -19,6 +19,7 @@
import android.car.drivingstate.CarUxRestrictions;
import android.car.drivingstate.CarUxRestrictionsConfiguration;
import android.car.drivingstate.ICarUxRestrictionsChangeListener;
+import android.os.IRemoteCallback;
/**
* Binder interface for {@link android.car.drivingstate.CarUxRestrictionsManager}.
@@ -36,4 +37,6 @@
List<CarUxRestrictionsConfiguration> getConfigs() = 5;
boolean setRestrictionMode(int mode) = 6;
int getRestrictionMode() = 7;
+ void reportVirtualDisplayToPhysicalDisplay(in IRemoteCallback binder, int virtualDisplayId, int physicalDisplayId) = 8;
+ int getMappedPhysicalDisplayOfVirtualDisplay(int displayId) = 9;
}
diff --git a/service/src/com/android/car/CarServiceBase.java b/service/src/com/android/car/CarServiceBase.java
index e014cf0..c4b578d 100644
--- a/service/src/com/android/car/CarServiceBase.java
+++ b/service/src/com/android/car/CarServiceBase.java
@@ -36,7 +36,4 @@
default void vehicleHalReconnected() {}
void dump(PrintWriter writer);
-
- /** Called when we only want to dump metrics instead of everything else. */
- default void dumpMetrics(PrintWriter writer) {};
}
diff --git a/service/src/com/android/car/CarUxRestrictionsManagerService.java b/service/src/com/android/car/CarUxRestrictionsManagerService.java
index bb49ff2..c7de564 100644
--- a/service/src/com/android/car/CarUxRestrictionsManagerService.java
+++ b/service/src/com/android/car/CarUxRestrictionsManagerService.java
@@ -45,7 +45,9 @@
import android.os.Binder;
import android.os.Build;
import android.os.IBinder;
+import android.os.IRemoteCallback;
import android.os.Process;
+import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.os.SystemClock;
import android.util.ArraySet;
@@ -54,6 +56,7 @@
import android.util.JsonWriter;
import android.util.Log;
import android.util.Slog;
+import android.util.SparseArray;
import android.view.Display;
import android.view.DisplayAddress;
@@ -303,6 +306,9 @@
mUxRClients.clear();
mDrivingStateService.unregisterDrivingStateChangeListener(
mICarDrivingStateChangeEventListener);
+ synchronized (mMapLock) {
+ mActivityViewDisplayInfoMap.clear();
+ }
}
// Binder methods
@@ -626,6 +632,12 @@
for (Utils.TransitionLog tlog : mTransitionLogs) {
writer.println(tlog);
}
+ writer.println("UX Restriction display info:");
+ for (int i = mActivityViewDisplayInfoMap.size() - 1; i >= 0; --i) {
+ DisplayInfo info = mActivityViewDisplayInfoMap.valueAt(i);
+ writer.printf("Display%d: physicalDisplayId=%d, owner=%s",
+ mActivityViewDisplayInfoMap.keyAt(i), info.mPhysicalDisplayId, info.mOwner);
+ }
}
/**
@@ -969,4 +981,93 @@
Slog.d(TAG, msg);
}
}
+
+ private final Object mMapLock = new Object();
+
+ private static final class DisplayInfo {
+ final IRemoteCallback mOwner;
+ final int mPhysicalDisplayId;
+ DisplayInfo(IRemoteCallback owner, int physicalDisplayId) {
+ mOwner = owner;
+ mPhysicalDisplayId = physicalDisplayId;
+ }
+ };
+
+ @GuardedBy("mMapLock")
+ private final SparseArray<DisplayInfo> mActivityViewDisplayInfoMap = new SparseArray<>();
+
+ @GuardedBy("mMapLock")
+ private final RemoteCallbackList<IRemoteCallback> mRemoteCallbackList =
+ new RemoteCallbackList<>() {
+ @Override
+ public void onCallbackDied(IRemoteCallback callback) {
+ synchronized (mMapLock) {
+ // Descending order to delete items safely from SpareArray.gc().
+ for (int i = mActivityViewDisplayInfoMap.size() - 1; i >= 0; --i) {
+ DisplayInfo info = mActivityViewDisplayInfoMap.valueAt(i);
+ if (info.mOwner == callback) {
+ logd("onCallbackDied: clean up callback=" + callback);
+ mActivityViewDisplayInfoMap.removeAt(i);
+ }
+ }
+ }
+ }
+ };
+
+ @Override
+ public void reportVirtualDisplayToPhysicalDisplay(IRemoteCallback callback,
+ int virtualDisplayId, int physicalDisplayId) {
+ logd("reportVirtualDisplayToPhysicalDisplay: callback=" + callback
+ + ", virtualDisplayId=" + virtualDisplayId
+ + ", physicalDisplayId=" + physicalDisplayId);
+ boolean release = physicalDisplayId == Display.INVALID_DISPLAY;
+ checkCallerOwnsDisplay(virtualDisplayId, release);
+ synchronized (mMapLock) {
+ if (release) {
+ mRemoteCallbackList.unregister(callback);
+ mActivityViewDisplayInfoMap.delete(virtualDisplayId);
+ return;
+ }
+ mRemoteCallbackList.register(callback);
+ mActivityViewDisplayInfoMap.put(virtualDisplayId,
+ new DisplayInfo(callback, physicalDisplayId));
+ }
+ }
+
+ @Override
+ public int getMappedPhysicalDisplayOfVirtualDisplay(int displayId) {
+ logd("getMappedPhysicalDisplayOfVirtualDisplay: displayId=" + displayId);
+ synchronized (mMapLock) {
+ DisplayInfo foundInfo = mActivityViewDisplayInfoMap.get(displayId);
+ if (foundInfo == null) {
+ return Display.INVALID_DISPLAY;
+ }
+ // ActivityView can be placed in another ActivityView, so we should repeat the process
+ // until no parent is found (reached to the physical display).
+ while (foundInfo != null) {
+ displayId = foundInfo.mPhysicalDisplayId;
+ foundInfo = mActivityViewDisplayInfoMap.get(displayId);
+ }
+ }
+ return displayId;
+ }
+
+ private void checkCallerOwnsDisplay(int displayId, boolean release) {
+ Display display = mDisplayManager.getDisplay(displayId);
+ if (display == null) {
+ // Bypasses the permission check for non-existing display when releasing it, since
+ // reportVirtualDisplayToPhysicalDisplay() and releasing display happens simultaneously
+ // and it's no harm to release the information on the non-existing display.
+ if (release) return;
+ throw new IllegalArgumentException(
+ "Cannot find display for non-existent displayId: " + displayId);
+ }
+
+ int callingUid = Binder.getCallingUid();
+ int displayOwnerUid = display.getOwnerUid();
+ if (callingUid != displayOwnerUid) {
+ throw new SecurityException("The caller doesn't own the display: callingUid="
+ + callingUid + ", displayOwnerUid=" + displayOwnerUid);
+ }
+ }
}
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index dfdee04..2de5d5c 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -19,12 +19,15 @@
import android.annotation.MainThread;
import android.annotation.Nullable;
import android.app.ActivityManager;
+import android.app.ActivityOptions;
import android.app.UiModeManager;
import android.car.Car;
import android.car.ICar;
import android.car.cluster.renderer.IInstrumentClusterNavigation;
import android.car.userlib.CarUserManagerHelper;
+import android.content.ComponentName;
import android.content.Context;
+import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.hardware.automotive.vehicle.V2_0.IVehicle;
@@ -50,6 +53,7 @@
import com.android.car.hal.VehicleHal;
import com.android.car.internal.FeatureConfiguration;
import com.android.car.pm.CarPackageManagerService;
+import com.android.car.stats.CarStatsService;
import com.android.car.systeminterface.SystemInterface;
import com.android.car.trust.CarTrustedDeviceService;
import com.android.car.user.CarUserNoticeService;
@@ -110,6 +114,7 @@
private final VmsSubscriberService mVmsSubscriberService;
private final VmsPublisherService mVmsPublisherService;
private final CarBugreportManagerService mCarBugreportManagerService;
+ private final CarStatsService mCarStatsService;
private final CarServiceBase[] mAllServices;
@@ -167,13 +172,16 @@
mAppFocusService, mCarInputService);
mSystemStateControllerService = new SystemStateControllerService(
serviceContext, mCarAudioService, this);
+ mCarStatsService = new CarStatsService(serviceContext);
mVmsBrokerService = new VmsBrokerService();
mVmsClientManager = new VmsClientManager(
- serviceContext, mVmsBrokerService, mCarUserService, mHal.getVmsHal());
+ // CarStatsService needs to be passed to the constructor due to HAL init order
+ serviceContext, mCarStatsService, mCarUserService, mVmsBrokerService,
+ mHal.getVmsHal());
mVmsSubscriberService = new VmsSubscriberService(
serviceContext, mVmsBrokerService, mVmsClientManager, mHal.getVmsHal());
mVmsPublisherService = new VmsPublisherService(
- serviceContext, mVmsBrokerService, mVmsClientManager);
+ serviceContext, mCarStatsService, mVmsBrokerService, mVmsClientManager);
mCarDiagnosticService = new CarDiagnosticService(serviceContext, mHal.getDiagnosticHal());
mCarStorageMonitoringService = new CarStorageMonitoringService(serviceContext,
systemInterface);
@@ -486,7 +494,7 @@
writer.println("*FutureConfig, DEFAULT:" + FeatureConfiguration.DEFAULT);
writer.println("*Dump all services*");
- dumpAllServices(writer, false);
+ dumpAllServices(writer);
writer.println("*Dump Vehicle HAL*");
writer.println("Vehicle HAL Interface: " + mVehicleInterfaceName);
@@ -511,8 +519,8 @@
dumpIndividualServices(writer, services);
return;
} else if ("--metrics".equals(args[0])) {
- writer.println("*Dump car service metrics*");
- dumpAllServices(writer, true);
+ // Strip the --metrics flag when passing dumpsys arguments to CarStatsService
+ mCarStatsService.dump(fd, writer, Arrays.copyOfRange(args, 1, args.length));
} else if ("--vms-hal".equals(args[0])) {
mHal.getVmsHal().dumpMetrics(fd);
} else if (Build.IS_USERDEBUG || Build.IS_ENG) {
@@ -535,12 +543,12 @@
}
}
- private void dumpAllServices(PrintWriter writer, boolean dumpMetricsOnly) {
+ private void dumpAllServices(PrintWriter writer) {
for (CarServiceBase service : mAllServices) {
- dumpService(service, writer, dumpMetricsOnly);
+ dumpService(service, writer);
}
if (mCarTestService != null) {
- dumpService(mCarTestService, writer, dumpMetricsOnly);
+ dumpService(mCarTestService, writer);
}
}
@@ -551,7 +559,7 @@
if (service == null) {
writer.println("No such service!");
} else {
- dumpService(service, writer, /* dumpMetricsOnly= */ false);
+ dumpService(service, writer);
}
writer.println();
}
@@ -564,13 +572,9 @@
.findFirst().orElse(null);
}
- private void dumpService(CarServiceBase service, PrintWriter writer, boolean dumpMetricsOnly) {
+ private void dumpService(CarServiceBase service, PrintWriter writer) {
try {
- if (dumpMetricsOnly) {
- service.dumpMetrics(writer);
- } else {
- service.dump(writer);
- }
+ service.dump(writer);
} catch (Exception e) {
writer.println("Failed dumping: " + service.getClass().getName());
e.printStackTrace(writer);
@@ -609,6 +613,8 @@
private static final String COMMAND_ENABLE_TRUSTED_DEVICE = "enable-trusted-device";
private static final String COMMAND_REMOVE_TRUSTED_DEVICES = "remove-trusted-devices";
private static final String COMMAND_SET_UID_TO_ZONE = "set-zoneid-for-uid";
+ private static final String COMMAND_START_FIXED_ACTIVITY_MODE = "start-fixed-activity-mode";
+ private static final String COMMAND_STOP_FIXED_ACTIVITY_MODE = "stop-fixed-activity-mode";
private static final String PARAM_DAY_MODE = "day";
private static final String PARAM_NIGHT_MODE = "night";
@@ -683,6 +689,11 @@
pw.println("\t When used with dumpsys, only metrics will be in the dumpsys output.");
pw.println("\tset-zoneid-for-uid [zoneid] [uid]");
pw.println("\t Maps the audio zoneid to uid.");
+ pw.println("\tstart-fixed-activity displayId packageName activityName");
+ pw.println("\t Start an Activity the specified display as fixed mode");
+ pw.println("\tstop-fixed-mode displayId");
+ pw.println("\t Stop fixed Activity mode for the given display. "
+ + "The Activity will not be restarted upon crash.");
}
private int dumpInvalidArguments(PrintWriter pw) {
@@ -820,6 +831,12 @@
dumpHelp(writer);
}
break;
+ case COMMAND_START_FIXED_ACTIVITY_MODE:
+ handleStartFixedActivity(args, writer);
+ break;
+ case COMMAND_STOP_FIXED_ACTIVITY_MODE:
+ handleStopFixedMode(args, writer);
+ break;
default:
writer.println("Unknown command: \"" + arg + "\"");
dumpHelp(writer);
@@ -828,6 +845,50 @@
return RESULT_OK;
}
+ private void handleStartFixedActivity(String[] args, PrintWriter writer) {
+ if (args.length != 4) {
+ writer.println("Incorrect number of arguments");
+ dumpHelp(writer);
+ return;
+ }
+ int displayId;
+ try {
+ displayId = Integer.parseInt(args[1]);
+ } catch (NumberFormatException e) {
+ writer.println("Wrong display id:" + args[1]);
+ return;
+ }
+ String packageName = args[2];
+ String activityName = args[3];
+ Intent intent = new Intent();
+ intent.setComponent(new ComponentName(packageName, activityName));
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ ActivityOptions options = ActivityOptions.makeBasic();
+ options.setLaunchDisplayId(displayId);
+ if (!mFixedActivityService.startFixedActivityModeForDisplayAndUser(intent, options,
+ displayId, ActivityManager.getCurrentUser())) {
+ writer.println("Failed to start");
+ return;
+ }
+ writer.println("Succeeded");
+ }
+
+ private void handleStopFixedMode(String[] args, PrintWriter writer) {
+ if (args.length != 2) {
+ writer.println("Incorrect number of arguments");
+ dumpHelp(writer);
+ return;
+ }
+ int displayId;
+ try {
+ displayId = Integer.parseInt(args[1]);
+ } catch (NumberFormatException e) {
+ writer.println("Wrong display id:" + args[1]);
+ return;
+ }
+ mFixedActivityService.stopFixedActivityMode(displayId);
+ }
+
private void forceDayNightMode(String arg, PrintWriter writer) {
int mode;
switch (arg) {
diff --git a/service/src/com/android/car/VmsPublisherService.java b/service/src/com/android/car/VmsPublisherService.java
index 3029a9e..da0b8b7 100644
--- a/service/src/com/android/car/VmsPublisherService.java
+++ b/service/src/com/android/car/VmsPublisherService.java
@@ -29,16 +29,16 @@
import android.util.ArrayMap;
import android.util.Log;
+import com.android.car.stats.CarStatsService;
import com.android.car.vms.VmsBrokerService;
import com.android.car.vms.VmsClientManager;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.io.PrintWriter;
import java.util.Collections;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
+import java.util.function.IntSupplier;
/**
@@ -50,75 +50,35 @@
private static final boolean DBG = false;
private static final String TAG = "VmsPublisherService";
- @VisibleForTesting
- static final String PACKET_COUNT_FORMAT = "Packet count for layer %s: %d\n";
-
- @VisibleForTesting
- static final String PACKET_SIZE_FORMAT = "Total packet size for layer %s: %d (bytes)\n";
-
- @VisibleForTesting
- static final String PACKET_FAILURE_COUNT_FORMAT =
- "Total packet failure count for layer %s from %s to %s: %d\n";
-
- @VisibleForTesting
- static final String PACKET_FAILURE_SIZE_FORMAT =
- "Total packet failure size for layer %s from %s to %s: %d (bytes)\n";
-
private final Context mContext;
+ private final CarStatsService mStatsService;
private final VmsBrokerService mBrokerService;
private final VmsClientManager mClientManager;
+ private final IntSupplier mGetCallingUid;
private final Map<String, PublisherProxy> mPublisherProxies = Collections.synchronizedMap(
new ArrayMap<>());
- @GuardedBy("mPacketCounts")
- private final Map<VmsLayer, PacketCountAndSize> mPacketCounts = new ArrayMap<>();
- @GuardedBy("mPacketFailureCounts")
- private final Map<PacketFailureKey, PacketCountAndSize> mPacketFailureCounts = new ArrayMap<>();
-
- // PacketCountAndSize keeps track of the cumulative size and number of packets of a specific
- // VmsLayer that we have seen.
- private class PacketCountAndSize {
- long mCount;
- long mSize;
- }
-
- // PacketFailureKey is a triple of the VmsLayer, the publisher and subscriber for which a packet
- // failed to be sent.
- private class PacketFailureKey {
- VmsLayer mVmsLayer;
- String mPublisher;
- String mSubscriber;
-
- PacketFailureKey(VmsLayer vmsLayer, String publisher, String subscriber) {
- mVmsLayer = vmsLayer;
- mPublisher = publisher;
- mSubscriber = subscriber;
- }
-
- @Override
- public boolean equals(Object o) {
- if (!(o instanceof PacketFailureKey)) {
- return false;
- }
-
- PacketFailureKey otherKey = (PacketFailureKey) o;
- return Objects.equals(mVmsLayer, otherKey.mVmsLayer) && Objects.equals(mPublisher,
- otherKey.mPublisher) && Objects.equals(mSubscriber, otherKey.mSubscriber);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(mVmsLayer, mPublisher, mSubscriber);
- }
- }
-
- public VmsPublisherService(
+ VmsPublisherService(
Context context,
+ CarStatsService statsService,
VmsBrokerService brokerService,
VmsClientManager clientManager) {
+ this(context, statsService, brokerService, clientManager, Binder::getCallingUid);
+ }
+
+ @VisibleForTesting
+ VmsPublisherService(
+ Context context,
+ CarStatsService statsService,
+ VmsBrokerService brokerService,
+ VmsClientManager clientManager,
+ IntSupplier getCallingUid) {
mContext = context;
+ mStatsService = statsService;
mBrokerService = brokerService;
mClientManager = clientManager;
+ mGetCallingUid = getCallingUid;
+
mClientManager.setPublisherService(this);
}
@@ -133,35 +93,8 @@
@Override
public void dump(PrintWriter writer) {
- dumpMetrics(writer);
- }
-
- @Override
- public void dumpMetrics(PrintWriter writer) {
writer.println("*" + getClass().getSimpleName() + "*");
writer.println("mPublisherProxies: " + mPublisherProxies.size());
- synchronized (mPacketCounts) {
- for (Map.Entry<VmsLayer, PacketCountAndSize> entry : mPacketCounts.entrySet()) {
- VmsLayer layer = entry.getKey();
- PacketCountAndSize countAndSize = entry.getValue();
- writer.format(PACKET_COUNT_FORMAT, layer, countAndSize.mCount);
- writer.format(PACKET_SIZE_FORMAT, layer, countAndSize.mSize);
- }
- }
- synchronized (mPacketFailureCounts) {
- for (Map.Entry<PacketFailureKey, PacketCountAndSize> entry :
- mPacketFailureCounts.entrySet()) {
- PacketFailureKey key = entry.getKey();
- PacketCountAndSize countAndSize = entry.getValue();
- VmsLayer layer = key.mVmsLayer;
- String publisher = key.mPublisher;
- String subscriber = key.mSubscriber;
- writer.format(PACKET_FAILURE_COUNT_FORMAT, layer, publisher, subscriber,
- countAndSize.mCount);
- writer.format(PACKET_FAILURE_SIZE_FORMAT, layer, publisher, subscriber,
- countAndSize.mSize);
- }
- }
}
/**
@@ -236,26 +169,6 @@
mBrokerService.setPublisherLayersOffering(token, offering);
}
- private void incrementPacketCount(VmsLayer layer, long size) {
- synchronized (mPacketCounts) {
- PacketCountAndSize countAndSize = mPacketCounts.computeIfAbsent(layer,
- i -> new PacketCountAndSize());
- countAndSize.mCount++;
- countAndSize.mSize += size;
- }
- }
-
- private void incrementPacketFailure(VmsLayer layer, String publisher, String subscriber,
- long size) {
- synchronized (mPacketFailureCounts) {
- PacketFailureKey key = new PacketFailureKey(layer, publisher, subscriber);
- PacketCountAndSize countAndSize = mPacketFailureCounts.computeIfAbsent(key,
- i -> new PacketCountAndSize());
- countAndSize.mCount++;
- countAndSize.mSize += size;
- }
- }
-
@Override
public void publish(IBinder token, VmsLayer layer, int publisherId, byte[] payload) {
assertPermission(token);
@@ -268,7 +181,8 @@
}
int payloadLength = payload != null ? payload.length : 0;
- incrementPacketCount(layer, payloadLength);
+ mStatsService.getVmsClientLog(mGetCallingUid.getAsInt())
+ .logPacketSent(layer, payloadLength);
// Send the message to subscribers
Set<IVmsSubscriberClient> listeners =
@@ -277,17 +191,21 @@
if (DBG) Log.d(TAG, String.format("Number of subscribers: %d", listeners.size()));
if (listeners.size() == 0) {
- // An empty string for the last argument is a special value signalizing zero
- // subscribers for the VMS_PACKET_FAILURE_REPORTED atom.
- incrementPacketFailure(layer, mName, "", payloadLength);
+ // A negative UID signals that the packet had zero subscribers
+ mStatsService.getVmsClientLog(-1)
+ .logPacketDropped(layer, payloadLength);
}
for (IVmsSubscriberClient listener : listeners) {
+ int subscriberUid = mClientManager.getSubscriberUid(listener);
try {
listener.onVmsMessageReceived(layer, payload);
+ mStatsService.getVmsClientLog(subscriberUid)
+ .logPacketReceived(layer, payloadLength);
} catch (RemoteException ex) {
+ mStatsService.getVmsClientLog(subscriberUid)
+ .logPacketDropped(layer, payloadLength);
String subscriberName = mClientManager.getPackageName(listener);
- incrementPacketFailure(layer, mName, subscriberName, payloadLength);
Log.e(TAG, String.format("Unable to publish to listener: %s", subscriberName));
}
}
diff --git a/service/src/com/android/car/am/FixedActivityService.java b/service/src/com/android/car/am/FixedActivityService.java
index b8f8608..20885b8 100644
--- a/service/src/com/android/car/am/FixedActivityService.java
+++ b/service/src/com/android/car/am/FixedActivityService.java
@@ -15,6 +15,9 @@
*/
package com.android.car.am;
+import static android.app.ActivityTaskManager.INVALID_TASK_ID;
+import static android.os.Process.INVALID_UID;
+
import static com.android.car.CarLog.TAG_AM;
import android.annotation.NonNull;
@@ -26,6 +29,7 @@
import android.app.IActivityManager;
import android.app.IProcessObserver;
import android.app.TaskStackListener;
+import android.car.hardware.power.CarPowerManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -34,7 +38,10 @@
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.HandlerThread;
import android.os.RemoteException;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
@@ -60,6 +67,11 @@
private static final boolean DBG = false;
+ private static final long RECHECK_INTERVAL_MS = 500;
+ private static final int MAX_NUMBER_OF_CONSECUTIVE_CRASH_RETRY = 5;
+ // If process keep running without crashing, will reset consecutive crash counts.
+ private static final long CRASH_FORGET_INTERVAL_MS = 2 * 60 * 1000; // 2 mins
+
private static class RunningActivityInfo {
@NonNull
public final Intent intent;
@@ -70,9 +82,20 @@
@UserIdInt
public final int userId;
- // Only used in a method for local book-keeping. So do not need a lock.
- // This does not represent the current visibility.
+ @GuardedBy("mLock")
public boolean isVisible;
+ @GuardedBy("mLock")
+ public long lastLaunchTimeMs = 0;
+ @GuardedBy("mLock")
+ public int consecutiveRetries = 0;
+ @GuardedBy("mLock")
+ public int taskId = INVALID_TASK_ID;
+ @GuardedBy("mLock")
+ public int previousTaskId = INVALID_TASK_ID;
+ @GuardedBy("mLock")
+ public boolean inBackground;
+ @GuardedBy("mLock")
+ public boolean failureLogged;
RunningActivityInfo(@NonNull Intent intent, @NonNull ActivityOptions activityOptions,
@UserIdInt int userId) {
@@ -81,10 +104,17 @@
this.userId = userId;
}
+ private void resetCrashCounterLocked() {
+ consecutiveRetries = 0;
+ failureLogged = false;
+ }
+
@Override
public String toString() {
return "RunningActivityInfo{intent:" + intent + ",activityOptions:" + activityOptions
- + ",userId:" + userId + "}";
+ + ",userId:" + userId + ",isVisible:" + isVisible
+ + ",lastLaunchTimeMs:" + lastLaunchTimeMs
+ + ",consecutiveRetries:" + consecutiveRetries + ",taskId:" + taskId + "}";
}
}
@@ -111,11 +141,41 @@
private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- final String action = intent.getAction();
+ String action = intent.getAction();
if (Intent.ACTION_PACKAGE_CHANGED.equals(action)
|| Intent.ACTION_PACKAGE_REPLACED.equals(
action)) {
- launchIfNecessary();
+ Uri packageData = intent.getData();
+ if (packageData == null) {
+ Log.w(TAG_AM, "null packageData");
+ return;
+ }
+ String packageName = packageData.getSchemeSpecificPart();
+ if (packageName == null) {
+ Log.w(TAG_AM, "null packageName");
+ return;
+ }
+ int uid = intent.getIntExtra(Intent.EXTRA_UID, INVALID_UID);
+ int userId = UserHandle.getUserId(uid);
+ boolean tryLaunch = false;
+ synchronized (mLock) {
+ for (int i = 0; i < mRunningActivities.size(); i++) {
+ RunningActivityInfo info = mRunningActivities.valueAt(i);
+ ComponentName component = info.intent.getComponent();
+ // should do this for all activities as the same package can cover multiple
+ // displays.
+ if (packageName.equals(component.getPackageName())
+ && info.userId == userId) {
+ Log.i(TAG_AM, "Package updated:" + packageName
+ + ",user:" + userId);
+ info.resetCrashCounterLocked();
+ tryLaunch = true;
+ }
+ }
+ }
+ if (tryLaunch) {
+ launchIfNecessary();
+ }
}
}
};
@@ -126,6 +186,26 @@
public void onTaskStackChanged() {
launchIfNecessary();
}
+
+ @Override
+ public void onTaskCreated(int taskId, ComponentName componentName) {
+ launchIfNecessary();
+ }
+
+ @Override
+ public void onTaskRemoved(int taskId) {
+ launchIfNecessary();
+ }
+
+ @Override
+ public void onTaskMovedToFront(int taskId) {
+ launchIfNecessary();
+ }
+
+ @Override
+ public void onTaskRemovalStarted(int taskId) {
+ launchIfNecessary();
+ }
};
private final IProcessObserver mProcessObserver = new IProcessObserver.Stub() {
@@ -145,6 +225,13 @@
}
};
+ private final HandlerThread mHandlerThread = new HandlerThread(
+ FixedActivityService.class.getSimpleName());
+
+ private final Runnable mActivityCheckRunnable = () -> {
+ launchIfNecessary();
+ };
+
private final Object mLock = new Object();
// key: displayId
@@ -155,13 +242,29 @@
@GuardedBy("mLock")
private boolean mEventMonitoringActive;
+ @GuardedBy("mLock")
+ private CarPowerManager mCarPowerManager;
+
+ private final CarPowerManager.CarPowerStateListener mCarPowerStateListener = (state) -> {
+ if (state != CarPowerManager.CarPowerStateListener.ON) {
+ return;
+ }
+ synchronized (mLock) {
+ for (int i = 0; i < mRunningActivities.size(); i++) {
+ RunningActivityInfo info = mRunningActivities.valueAt(i);
+ info.resetCrashCounterLocked();
+ }
+ }
+ launchIfNecessary();
+ };
+
public FixedActivityService(Context context) {
mContext = context;
mAm = ActivityManager.getService();
mUm = context.getSystemService(UserManager.class);
+ mHandlerThread.start();
}
-
@Override
public void init() {
// nothing to do
@@ -181,18 +284,26 @@
}
}
+ private void postRecheck(long delayMs) {
+ mHandlerThread.getThreadHandler().postDelayed(mActivityCheckRunnable, delayMs);
+ }
+
private void startMonitoringEvents() {
+ CarPowerManager carPowerManager;
synchronized (mLock) {
if (mEventMonitoringActive) {
return;
}
mEventMonitoringActive = true;
+ carPowerManager = CarLocalServices.createCarPowerManager(mContext);
+ mCarPowerManager = carPowerManager;
}
CarUserService userService = CarLocalServices.getService(CarUserService.class);
userService.addUserCallback(mUserCallback);
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
+ filter.addDataScheme("package");
mContext.registerReceiverAsUser(mBroadcastReceiver, UserHandle.ALL, filter,
/* broadcastPermission= */ null, /* scheduler= */ null);
try {
@@ -201,15 +312,28 @@
} catch (RemoteException e) {
Log.e(TAG_AM, "remote exception from AM", e);
}
+ try {
+ carPowerManager.setListener(mCarPowerStateListener);
+ } catch (Exception e) {
+ // should not happen
+ Log.e(TAG_AM, "Got exception from CarPowerManager", e);
+ }
}
private void stopMonitoringEvents() {
+ CarPowerManager carPowerManager;
synchronized (mLock) {
if (!mEventMonitoringActive) {
return;
}
mEventMonitoringActive = false;
+ carPowerManager = mCarPowerManager;
+ mCarPowerManager = null;
}
+ if (carPowerManager != null) {
+ carPowerManager.clearListener();
+ }
+ mHandlerThread.getThreadHandler().removeCallbacks(mActivityCheckRunnable);
CarUserService userService = CarLocalServices.getService(CarUserService.class);
userService.removeUserCallback(mUserCallback);
try {
@@ -243,6 +367,7 @@
Log.e(TAG_AM, "cannot get StackInfo from AM");
return false;
}
+ long now = SystemClock.elapsedRealtime();
synchronized (mLock) {
if (mRunningActivities.size() == 0) {
// it must have been stopped.
@@ -264,16 +389,30 @@
&& activityInfo.userId == topUserId && stackInfo.visible) {
// top one is matching.
activityInfo.isVisible = true;
+ activityInfo.taskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
continue;
}
- if (DBG) {
- Log.i(TAG_AM, "Unmatched top activity:" + stackInfo.topActivity
- + " user:" + topUserId + " display:" + stackInfo.displayId);
+ activityInfo.previousTaskId = stackInfo.taskIds[stackInfo.taskIds.length - 1];
+ Log.i(TAG_AM, "Unmatched top activity will be removed:"
+ + stackInfo.topActivity + " top task id:" + activityInfo.previousTaskId
+ + " user:" + topUserId + " display:" + stackInfo.displayId);
+ activityInfo.inBackground = false;
+ for (int i = 0; i < stackInfo.taskIds.length - 1; i++) {
+ if (activityInfo.taskId == stackInfo.taskIds[i]) {
+ activityInfo.inBackground = true;
+ }
+ }
+ if (!activityInfo.inBackground) {
+ activityInfo.taskId = INVALID_TASK_ID;
}
}
for (int i = 0; i < mRunningActivities.size(); i++) {
RunningActivityInfo activityInfo = mRunningActivities.valueAt(i);
+ long timeSinceLastLaunchMs = now - activityInfo.lastLaunchTimeMs;
if (activityInfo.isVisible) {
+ if (timeSinceLastLaunchMs >= CRASH_FORGET_INTERVAL_MS) {
+ activityInfo.consecutiveRetries = 0;
+ }
continue;
}
if (!isComponentAvailable(activityInfo.intent.getComponent(),
@@ -281,14 +420,40 @@
activityInfo.userId)) {
continue;
}
+ // For 1st call (consecutiveRetries == 0), do not wait as there can be no posting
+ // for recheck.
+ if (activityInfo.consecutiveRetries > 0 && (timeSinceLastLaunchMs
+ < RECHECK_INTERVAL_MS)) {
+ // wait until next check interval comes.
+ continue;
+ }
+ if (activityInfo.consecutiveRetries >= MAX_NUMBER_OF_CONSECUTIVE_CRASH_RETRY) {
+ // re-tried too many times, give up for now.
+ if (!activityInfo.failureLogged) {
+ activityInfo.failureLogged = true;
+ Log.w(TAG_AM, "Too many relaunch failure of fixed activity:"
+ + activityInfo);
+ }
+ continue;
+ }
+
Log.i(TAG_AM, "Launching Activity for fixed mode. Intent:" + activityInfo.intent
+ ",userId:" + UserHandle.of(activityInfo.userId) + ",displayId:"
+ mRunningActivities.keyAt(i));
+ // Increase retry count if task is not in background. In case like other app is
+ // launched and the target activity is still in background, do not consider it
+ // as retry.
+ if (!activityInfo.inBackground) {
+ activityInfo.consecutiveRetries++;
+ }
try {
+ postRecheck(RECHECK_INTERVAL_MS);
+ postRecheck(CRASH_FORGET_INTERVAL_MS);
mContext.startActivityAsUser(activityInfo.intent,
activityInfo.activityOptions.toBundle(),
UserHandle.of(activityInfo.userId));
activityInfo.isVisible = true;
+ activityInfo.lastLaunchTimeMs = SystemClock.elapsedRealtime();
} catch (Exception e) { // Catch all for any app related issues.
Log.w(TAG_AM, "Cannot start activity:" + activityInfo.intent, e);
}
@@ -368,6 +533,10 @@
if (!isDisplayAllowedForFixedMode(displayId)) {
return false;
}
+ if (options == null) {
+ Log.e(TAG_AM, "startFixedActivityModeForDisplayAndUser, null options");
+ return false;
+ }
if (!isUserAllowedToLaunchActivity(userId)) {
Log.e(TAG_AM, "startFixedActivityModeForDisplayAndUser, requested user:" + userId
+ " cannot launch activity, Intent:" + intent);
@@ -390,7 +559,16 @@
startMonitoringEvents = true;
}
RunningActivityInfo activityInfo = mRunningActivities.get(displayId);
- if (activityInfo == null) {
+ boolean replaceEntry = true;
+ if (activityInfo != null && activityInfo.intent.equals(intent)
+ && options.equals(activityInfo.activityOptions)
+ && userId == activityInfo.userId) {
+ replaceEntry = false;
+ if (activityInfo.isVisible) { // already shown.
+ return true;
+ }
+ }
+ if (replaceEntry) {
activityInfo = new RunningActivityInfo(intent, options, userId);
mRunningActivities.put(displayId, activityInfo);
}
diff --git a/service/src/com/android/car/cluster/InstrumentClusterService.java b/service/src/com/android/car/cluster/InstrumentClusterService.java
index 6e186d5..6be45b5 100644
--- a/service/src/com/android/car/cluster/InstrumentClusterService.java
+++ b/service/src/com/android/car/cluster/InstrumentClusterService.java
@@ -31,6 +31,7 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.os.Binder;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -135,6 +136,7 @@
@Override
public boolean startFixedActivityModeForDisplayAndUser(Intent intent,
Bundle activityOptionsBundle, int userId) {
+ Binder.clearCallingIdentity();
ActivityOptions options = new ActivityOptions(activityOptionsBundle);
FixedActivityService service = CarLocalServices.getService(
FixedActivityService.class);
@@ -144,6 +146,7 @@
@Override
public void stopFixedActivityMode(int displayId) {
+ Binder.clearCallingIdentity();
FixedActivityService service = CarLocalServices.getService(
FixedActivityService.class);
service.stopFixedActivityMode(displayId);
diff --git a/service/src/com/android/car/stats/CarStatsService.java b/service/src/com/android/car/stats/CarStatsService.java
new file mode 100644
index 0000000..db23355
--- /dev/null
+++ b/service/src/com/android/car/stats/CarStatsService.java
@@ -0,0 +1,130 @@
+/*
+ * 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.stats;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.car.stats.VmsClientLog.ConnectionState;
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.function.Function;
+
+/**
+ * Implements collection and dumpsys reporting of statistics in CSV format.
+ */
+public class CarStatsService {
+ private static final boolean DEBUG = false;
+ private static final String TAG = "CarStatsService";
+ private static final String VMS_CONNECTION_STATS_DUMPSYS_HEADER =
+ "uid,packageName,attempts,connected,disconnected,terminated,errors";
+
+ private static final Function<VmsClientLog, String> VMS_CONNECTION_STATS_DUMPSYS_FORMAT =
+ entry -> String.format(Locale.US,
+ "%d,%s,%d,%d,%d,%d,%d",
+ entry.getUid(), entry.getPackageName(),
+ entry.getConnectionStateCount(ConnectionState.CONNECTING),
+ entry.getConnectionStateCount(ConnectionState.CONNECTED),
+ entry.getConnectionStateCount(ConnectionState.DISCONNECTED),
+ entry.getConnectionStateCount(ConnectionState.TERMINATED),
+ entry.getConnectionStateCount(ConnectionState.CONNECTION_ERROR));
+
+ private static final String VMS_CLIENT_STATS_DUMPSYS_HEADER =
+ "uid,layerType,layerChannel,layerVersion,"
+ + "txBytes,txPackets,rxBytes,rxPackets,droppedBytes,droppedPackets";
+
+ private static final Function<VmsClientStats, String> VMS_CLIENT_STATS_DUMPSYS_FORMAT =
+ entry -> String.format(
+ "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d",
+ entry.getUid(),
+ entry.getLayerType(), entry.getLayerChannel(), entry.getLayerVersion(),
+ entry.getTxBytes(), entry.getTxPackets(),
+ entry.getRxBytes(), entry.getRxPackets(),
+ entry.getDroppedBytes(), entry.getDroppedPackets());
+
+ private static final Comparator<VmsClientStats> VMS_CLIENT_STATS_ORDER =
+ Comparator.comparingInt(VmsClientStats::getUid)
+ .thenComparingInt(VmsClientStats::getLayerType)
+ .thenComparingInt(VmsClientStats::getLayerChannel)
+ .thenComparingInt(VmsClientStats::getLayerVersion);
+
+ private final PackageManager mPackageManager;
+
+ @GuardedBy("mVmsClientStats")
+ private final Map<Integer, VmsClientLog> mVmsClientStats = new ArrayMap<>();
+
+ public CarStatsService(Context context) {
+ mPackageManager = context.getPackageManager();
+ }
+
+ /**
+ * Gets a logger for the VMS client with a given UID.
+ */
+ public VmsClientLog getVmsClientLog(int clientUid) {
+ synchronized (mVmsClientStats) {
+ return mVmsClientStats.computeIfAbsent(
+ clientUid,
+ uid -> {
+ String packageName = mPackageManager.getNameForUid(uid);
+ if (DEBUG) {
+ Log.d(TAG, "Created VmsClientLog: " + packageName);
+ }
+ return new VmsClientLog(uid, packageName);
+ });
+ }
+ }
+
+ /**
+ * Dumps metrics in CSV format.
+ */
+ public void dump(FileDescriptor fd, PrintWriter writer, String[] args) {
+ List<String> flags = Arrays.asList(args);
+ if (args.length == 0 || flags.contains("--vms-client")) {
+ dumpVmsStats(writer);
+ }
+ }
+
+ private void dumpVmsStats(PrintWriter writer) {
+ synchronized (mVmsClientStats) {
+ writer.println(VMS_CONNECTION_STATS_DUMPSYS_HEADER);
+ mVmsClientStats.values().stream()
+ // Unknown UID will not have connection stats
+ .filter(entry -> entry.getUid() > 0)
+ // Sort stats by UID
+ .sorted(Comparator.comparingInt(VmsClientLog::getUid))
+ .forEachOrdered(entry -> writer.println(
+ VMS_CONNECTION_STATS_DUMPSYS_FORMAT.apply(entry)));
+ writer.println();
+
+ writer.println(VMS_CLIENT_STATS_DUMPSYS_HEADER);
+ mVmsClientStats.values().stream()
+ .flatMap(log -> log.getLayerEntries().stream())
+ .sorted(VMS_CLIENT_STATS_ORDER)
+ .forEachOrdered(entry -> writer.println(
+ VMS_CLIENT_STATS_DUMPSYS_FORMAT.apply(entry)));
+ }
+ }
+}
diff --git a/service/src/com/android/car/stats/VmsClientLog.java b/service/src/com/android/car/stats/VmsClientLog.java
new file mode 100644
index 0000000..506a5fc
--- /dev/null
+++ b/service/src/com/android/car/stats/VmsClientLog.java
@@ -0,0 +1,141 @@
+/*
+ * 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.stats;
+
+import android.annotation.Nullable;
+import android.car.vms.VmsLayer;
+import android.util.ArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.stream.Collectors;
+
+/**
+ * Logger for per-client VMS statistics.
+ */
+public class VmsClientLog {
+ /**
+ * Constants used for identifying client connection states.
+ */
+ public static class ConnectionState {
+ // Attempting to connect to the client
+ public static final int CONNECTING = 0;
+ // Client connection established
+ public static final int CONNECTED = 1;
+ // Client connection closed unexpectedly
+ public static final int DISCONNECTED = 2;
+ // Client connection closed by VMS
+ public static final int TERMINATED = 3;
+ // Error establishing the client connection
+ public static final int CONNECTION_ERROR = 4;
+ }
+
+ private final Object mLock = new Object();
+
+ private final int mUid;
+ private final String mPackageName;
+
+ @GuardedBy("mLock")
+ private Map<Integer, AtomicLong> mConnectionStateCounters = new ArrayMap<>();
+
+ @GuardedBy("mLock")
+ private final Map<VmsLayer, VmsClientStats> mLayerStats = new ArrayMap<>();
+
+ VmsClientLog(int clientUid, @Nullable String clientPackage) {
+ mUid = clientUid;
+ mPackageName = clientPackage != null ? clientPackage : "";
+ }
+
+ public int getUid() {
+ return mUid;
+ }
+
+ public String getPackageName() {
+ return mPackageName;
+ }
+
+ /**
+ * Logs a connection state change for the client.
+ *
+ * @param connectionState New connection state
+ */
+ public void logConnectionState(int connectionState) {
+ AtomicLong counter;
+ synchronized (mLock) {
+ counter = mConnectionStateCounters.computeIfAbsent(connectionState,
+ ignored -> new AtomicLong());
+ }
+ counter.incrementAndGet();
+ }
+
+ long getConnectionStateCount(int connectionState) {
+ AtomicLong counter;
+ synchronized (mLock) {
+ counter = mConnectionStateCounters.get(connectionState);
+ }
+ return counter == null ? 0L : counter.get();
+ }
+
+ /**
+ * Logs that a packet was published by the client.
+ *
+ * @param layer Layer of packet
+ * @param size Size of packet
+ */
+ public void logPacketSent(VmsLayer layer, long size) {
+ getLayerEntry(layer).packetSent(size);
+ }
+
+ /**
+ * Logs that a packet was received successfully by the client.
+ *
+ * @param layer Layer of packet
+ * @param size Size of packet
+ */
+ public void logPacketReceived(VmsLayer layer, long size) {
+ getLayerEntry(layer).packetReceived(size);
+ }
+
+ /**
+ * Logs that a packet was dropped due to an error delivering to the client.
+ *
+ * @param layer Layer of packet
+ * @param size Size of packet
+ */
+ public void logPacketDropped(VmsLayer layer, long size) {
+ getLayerEntry(layer).packetDropped(size);
+ }
+
+ Collection<VmsClientStats> getLayerEntries() {
+ synchronized (mLock) {
+ return mLayerStats.values().stream()
+ .map(VmsClientStats::new) // Make a deep copy of the entries
+ .collect(Collectors.toList());
+ }
+ }
+
+ private VmsClientStats getLayerEntry(VmsLayer layer) {
+ synchronized (mLock) {
+ return mLayerStats.computeIfAbsent(
+ layer,
+ (k) -> new VmsClientStats(mUid, layer));
+ }
+ }
+}
diff --git a/service/src/com/android/car/stats/VmsClientStats.java b/service/src/com/android/car/stats/VmsClientStats.java
new file mode 100644
index 0000000..f4ce7b8
--- /dev/null
+++ b/service/src/com/android/car/stats/VmsClientStats.java
@@ -0,0 +1,174 @@
+/*
+ * 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.stats;
+
+import android.car.vms.VmsLayer;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * Java representation of VmsClientStats statsd atom.
+ *
+ * All access to this class is synchronized through VmsClientLog.
+ */
+class VmsClientStats {
+ private final Object mLock = new Object();
+
+ private final int mUid;
+
+ private final int mLayerType;
+ private final int mLayerChannel;
+ private final int mLayerVersion;
+
+ @GuardedBy("mLock")
+ private long mTxBytes;
+ @GuardedBy("mLock")
+ private long mTxPackets;
+
+ @GuardedBy("mLock")
+ private long mRxBytes;
+ @GuardedBy("mLock")
+ private long mRxPackets;
+
+ @GuardedBy("mLock")
+ private long mDroppedBytes;
+ @GuardedBy("mLock")
+ private long mDroppedPackets;
+
+ /**
+ * Constructor for a VmsClientStats entry.
+ *
+ * @param uid UID of client package.
+ * @param layer Vehicle Maps Service layer.
+ */
+ VmsClientStats(int uid, VmsLayer layer) {
+ this.mUid = uid;
+
+ this.mLayerType = layer.getType();
+ this.mLayerChannel = layer.getSubtype();
+ this.mLayerVersion = layer.getVersion();
+ }
+
+ /**
+ * Copy constructor for entries exported from {@link VmsClientLog} to {@link CarStatsService}.
+ */
+ VmsClientStats(VmsClientStats other) {
+ synchronized (other.mLock) {
+ this.mUid = other.mUid;
+
+ this.mLayerType = other.mLayerType;
+ this.mLayerChannel = other.mLayerChannel;
+ this.mLayerVersion = other.mLayerVersion;
+
+ this.mTxBytes = other.mTxBytes;
+ this.mTxPackets = other.mTxPackets;
+ this.mRxBytes = other.mRxBytes;
+ this.mRxPackets = other.mRxPackets;
+ this.mDroppedBytes = other.mDroppedBytes;
+ this.mDroppedPackets = other.mDroppedPackets;
+ }
+ }
+
+ /**
+ * Records that a packet was sent by a publisher client.
+ *
+ * @param size Size of packet.
+ */
+ void packetSent(long size) {
+ synchronized (mLock) {
+ mTxBytes += size;
+ mTxPackets++;
+ }
+ }
+
+ /**
+ * Records that a packet was successfully received by a subscriber client.
+ *
+ * @param size Size of packet.
+ */
+ void packetReceived(long size) {
+ synchronized (mLock) {
+ mRxBytes += size;
+ mRxPackets++;
+ }
+ }
+
+ /**
+ * Records that a packet was dropped while being delivered to a subscriber client.
+ *
+ * @param size Size of packet.
+ */
+ void packetDropped(long size) {
+ synchronized (mLock) {
+ mDroppedBytes += size;
+ mDroppedPackets++;
+ }
+ }
+
+ int getUid() {
+ return mUid;
+ }
+
+ int getLayerType() {
+ return mLayerType;
+ }
+
+ int getLayerChannel() {
+ return mLayerChannel;
+ }
+
+ int getLayerVersion() {
+ return mLayerVersion;
+ }
+
+ long getTxBytes() {
+ synchronized (mLock) {
+ return mTxBytes;
+ }
+ }
+
+ long getTxPackets() {
+ synchronized (mLock) {
+ return mTxPackets;
+ }
+ }
+
+ long getRxBytes() {
+ synchronized (mLock) {
+ return mRxBytes;
+ }
+ }
+
+ long getRxPackets() {
+ synchronized (mLock) {
+ return mRxPackets;
+ }
+ }
+
+ long getDroppedBytes() {
+ synchronized (mLock) {
+ return mDroppedBytes;
+ }
+ }
+
+ long getDroppedPackets() {
+ synchronized (mLock) {
+ return mDroppedPackets;
+ }
+ }
+}
+
diff --git a/service/src/com/android/car/vms/VmsClientManager.java b/service/src/com/android/car/vms/VmsClientManager.java
index 04e218f..5b03820 100644
--- a/service/src/com/android/car/vms/VmsClientManager.java
+++ b/service/src/com/android/car/vms/VmsClientManager.java
@@ -30,6 +30,7 @@
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
+import android.os.Process;
import android.os.RemoteException;
import android.os.UserHandle;
import android.os.UserManager;
@@ -40,6 +41,9 @@
import com.android.car.R;
import com.android.car.VmsPublisherService;
import com.android.car.hal.VmsHalService;
+import com.android.car.stats.CarStatsService;
+import com.android.car.stats.VmsClientLog;
+import com.android.car.stats.VmsClientLog.ConnectionState;
import com.android.car.user.CarUserService;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -49,7 +53,6 @@
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
-import java.util.concurrent.atomic.AtomicLong;
import java.util.function.IntSupplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -68,11 +71,12 @@
private final Context mContext;
private final PackageManager mPackageManager;
- private final Handler mHandler;
private final UserManager mUserManager;
private final CarUserService mUserService;
- private final int mMillisBeforeRebind;
+ private final CarStatsService mStatsService;
+ private final Handler mHandler;
private final IntSupplier mGetCallingUid;
+ private final int mMillisBeforeRebind;
private final Object mLock = new Object();
@@ -96,9 +100,6 @@
@GuardedBy("mLock")
private final Map<IBinder, SubscriberConnection> mSubscribers = new HashMap<>();
- @GuardedBy("mRebindCounts")
- private final Map<String, AtomicLong> mRebindCounts = new ArrayMap<>();
-
@VisibleForTesting
final Runnable mSystemUserUnlockedListener = () -> {
synchronized (mLock) {
@@ -111,7 +112,10 @@
final CarUserService.UserCallback mUserCallback = new CarUserService.UserCallback() {
@Override
public void onUserLockChanged(int userId, boolean unlocked) {
- //Do nothing.
+ if (unlocked) {
+ if (DBG) Log.d(TAG, "onUserLockChanged: " + userId);
+ switchUser();
+ }
}
@Override
@@ -146,30 +150,34 @@
* Constructor for client manager.
*
* @param context Context to use for registering receivers and binding services.
- * @param brokerService Service managing the VMS publisher/subscriber state.
+ * @param statsService Service for logging client metrics.
* @param userService User service for registering system unlock listener.
+ * @param brokerService Service managing the VMS publisher/subscriber state.
* @param halService Service providing the HAL client interface
*/
- public VmsClientManager(Context context, VmsBrokerService brokerService,
- CarUserService userService, VmsHalService halService) {
- this(context, brokerService, userService, halService,
+ public VmsClientManager(Context context, CarStatsService statsService,
+ CarUserService userService, VmsBrokerService brokerService,
+ VmsHalService halService) {
+ this(context, statsService, userService, brokerService, halService,
new Handler(Looper.getMainLooper()), Binder::getCallingUid);
}
@VisibleForTesting
- VmsClientManager(Context context, VmsBrokerService brokerService,
- CarUserService userService, VmsHalService halService, Handler handler,
- IntSupplier getCallingUid) {
+ VmsClientManager(Context context, CarStatsService statsService,
+ CarUserService userService, VmsBrokerService brokerService,
+ VmsHalService halService, Handler handler, IntSupplier getCallingUid) {
mContext = context;
mPackageManager = context.getPackageManager();
- mHandler = handler;
- mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
+ mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
+ mStatsService = statsService;
mUserService = userService;
mCurrentUser = ActivityManager.getCurrentUser();
mBrokerService = brokerService;
- mMillisBeforeRebind = mContext.getResources().getInteger(
- com.android.car.R.integer.millisecondsBeforeRebindToVmsPublisher);
+ mHandler = handler;
mGetCallingUid = getCallingUid;
+ mMillisBeforeRebind = context.getResources().getInteger(
+ com.android.car.R.integer.millisecondsBeforeRebindToVmsPublisher);
+
halService.setClientManager(this);
}
@@ -186,7 +194,6 @@
@Override
public void init() {
- new Exception("initiaVmsManager").printStackTrace();
mUserService.runOnUser0Unlock(mSystemUserUnlockedListener);
mUserService.addUserCallback(mUserCallback);
}
@@ -206,11 +213,6 @@
@Override
public void dump(PrintWriter writer) {
- dumpMetrics(writer);
- }
-
- @Override
- public void dumpMetrics(PrintWriter writer) {
writer.println("*" + getClass().getSimpleName() + "*");
synchronized (mLock) {
writer.println("mCurrentUser:" + mCurrentUser);
@@ -226,12 +228,6 @@
writer.printf("\t%s\n", subscriber);
}
}
- synchronized (mRebindCounts) {
- writer.println("mRebindCounts:");
- for (Map.Entry<String, AtomicLong> entry : mRebindCounts.entrySet()) {
- writer.printf("\t%s: %s\n", entry.getKey(), entry.getValue());
- }
- }
}
@@ -242,7 +238,8 @@
*/
public void addSubscriber(IVmsSubscriberClient subscriberClient) {
if (subscriberClient == null) {
- Log.e(TAG, "Trying to add a null subscriber: " + getCallingPackage());
+ Log.e(TAG, "Trying to add a null subscriber: "
+ + getCallingPackage(mGetCallingUid.getAsInt()));
throw new IllegalArgumentException("subscriber cannot be null.");
}
@@ -253,13 +250,14 @@
return;
}
- int subscriberUserId = UserHandle.getUserId(mGetCallingUid.getAsInt());
+ int callingUid = mGetCallingUid.getAsInt();
+ int subscriberUserId = UserHandle.getUserId(callingUid);
if (subscriberUserId != mCurrentUser && subscriberUserId != UserHandle.USER_SYSTEM) {
throw new SecurityException("Caller must be foreground user or system");
}
SubscriberConnection subscriber = new SubscriberConnection(
- subscriberClient, getCallingPackage(), subscriberUserId);
+ subscriberClient, callingUid, getCallingPackage(callingUid), subscriberUserId);
if (DBG) Log.d(TAG, "Registering subscriber: " + subscriber);
try {
subscriberBinder.linkToDeath(subscriber, 0);
@@ -296,6 +294,16 @@
}
/**
+ * Gets the application UID associated with a subscriber client.
+ */
+ public int getSubscriberUid(IVmsSubscriberClient subscriberClient) {
+ synchronized (mLock) {
+ SubscriberConnection subscriber = mSubscribers.get(subscriberClient.asBinder());
+ return subscriber != null ? subscriber.mUid : Process.INVALID_UID;
+ }
+ }
+
+ /**
* Gets the package name for a given subscriber client.
*/
public String getPackageName(IVmsSubscriberClient subscriberClient) {
@@ -307,9 +315,6 @@
/**
* Registers the HAL client connections.
- *
- * @param publisherClient
- * @param subscriberClient
*/
public void onHalConnected(IVmsPublisherClient publisherClient,
IVmsSubscriberClient subscriberClient) {
@@ -317,9 +322,11 @@
mHalClient = publisherClient;
mPublisherService.onClientConnected(HAL_CLIENT_NAME, mHalClient);
mSubscribers.put(subscriberClient.asBinder(),
- new SubscriberConnection(subscriberClient, HAL_CLIENT_NAME,
+ new SubscriberConnection(subscriberClient, Process.myUid(), HAL_CLIENT_NAME,
UserHandle.USER_SYSTEM));
}
+ mStatsService.getVmsClientLog(Process.myUid())
+ .logConnectionState(ConnectionState.CONNECTED);
}
/**
@@ -329,14 +336,13 @@
synchronized (mLock) {
if (mHalClient != null) {
mPublisherService.onClientDisconnected(HAL_CLIENT_NAME);
+ mStatsService.getVmsClientLog(Process.myUid())
+ .logConnectionState(ConnectionState.DISCONNECTED);
}
mHalClient = null;
terminate(mSubscribers.values().stream()
.filter(subscriber -> HAL_CLIENT_NAME.equals(subscriber.mPackageName)));
}
- synchronized (mRebindCounts) {
- mRebindCounts.computeIfAbsent(HAL_CLIENT_NAME, k -> new AtomicLong()).incrementAndGet();
- }
}
private void dumpConnections(PrintWriter writer,
@@ -404,13 +410,17 @@
return;
}
+ VmsClientLog statsLog = mStatsService.getVmsClientLog(
+ UserHandle.getUid(userHandle.getIdentifier(), serviceInfo.applicationInfo.uid));
+
if (!Car.PERMISSION_BIND_VMS_CLIENT.equals(serviceInfo.permission)) {
Log.e(TAG, "Client service: " + clientName
+ " does not require " + Car.PERMISSION_BIND_VMS_CLIENT + " permission");
+ statsLog.logConnectionState(ConnectionState.CONNECTION_ERROR);
return;
}
- PublisherConnection connection = new PublisherConnection(name, userHandle);
+ PublisherConnection connection = new PublisherConnection(name, userHandle, statsLog);
if (connection.bind()) {
Log.i(TAG, "Client bound: " + connection);
connectionMap.put(clientName, connection);
@@ -428,15 +438,17 @@
private final ComponentName mName;
private final UserHandle mUser;
private final String mFullName;
+ private final VmsClientLog mStatsLog;
private boolean mIsBound = false;
private boolean mIsTerminated = false;
private boolean mRebindScheduled = false;
private IVmsPublisherClient mClientService;
- PublisherConnection(ComponentName name, UserHandle user) {
+ PublisherConnection(ComponentName name, UserHandle user, VmsClientLog statsLog) {
mName = name;
mUser = user;
mFullName = mName.flattenToString() + " U=" + mUser.getIdentifier();
+ mStatsLog = statsLog;
}
synchronized boolean bind() {
@@ -446,6 +458,7 @@
if (mIsTerminated) {
return false;
}
+ mStatsLog.logConnectionState(ConnectionState.CONNECTING);
if (DBG) Log.d(TAG, "binding: " + mFullName);
Intent intent = new Intent();
@@ -457,6 +470,10 @@
Log.e(TAG, "While binding " + mFullName, e);
}
+ if (!mIsBound) {
+ mStatsLog.logConnectionState(ConnectionState.CONNECTION_ERROR);
+ }
+
return mIsBound;
}
@@ -500,23 +517,20 @@
// If the client is not currently bound, unbind() will have no effect.
unbind();
bind();
- synchronized (mRebindCounts) {
- mRebindCounts.computeIfAbsent(mName.getPackageName(), k -> new AtomicLong())
- .incrementAndGet();
- }
}
synchronized void terminate() {
if (DBG) Log.d(TAG, "terminating: " + mFullName);
mIsTerminated = true;
- notifyOnDisconnect();
+ notifyOnDisconnect(ConnectionState.TERMINATED);
unbind();
}
- synchronized void notifyOnDisconnect() {
+ synchronized void notifyOnDisconnect(int connectionState) {
if (mClientService != null) {
mPublisherService.onClientDisconnected(mFullName);
mClientService = null;
+ mStatsLog.logConnectionState(connectionState);
}
}
@@ -525,19 +539,20 @@
if (DBG) Log.d(TAG, "onServiceConnected: " + mFullName);
mClientService = IVmsPublisherClient.Stub.asInterface(service);
mPublisherService.onClientConnected(mFullName, mClientService);
+ mStatsLog.logConnectionState(ConnectionState.CONNECTED);
}
@Override
public void onServiceDisconnected(ComponentName name) {
if (DBG) Log.d(TAG, "onServiceDisconnected: " + mFullName);
- notifyOnDisconnect();
+ notifyOnDisconnect(ConnectionState.DISCONNECTED);
scheduleRebind();
}
@Override
public void onBindingDied(ComponentName name) {
if (DBG) Log.d(TAG, "onBindingDied: " + mFullName);
- notifyOnDisconnect();
+ notifyOnDisconnect(ConnectionState.DISCONNECTED);
scheduleRebind();
}
@@ -553,8 +568,8 @@
}
// If we're in a binder call, returns back the package name of the caller of the binder call.
- private String getCallingPackage() {
- String packageName = mPackageManager.getNameForUid(mGetCallingUid.getAsInt());
+ private String getCallingPackage(int uid) {
+ String packageName = mPackageManager.getNameForUid(uid);
if (packageName == null) {
return UNKNOWN_PACKAGE;
} else {
@@ -564,12 +579,14 @@
private class SubscriberConnection implements IBinder.DeathRecipient {
private final IVmsSubscriberClient mClient;
+ private final int mUid;
private final String mPackageName;
private final int mUserId;
- SubscriberConnection(IVmsSubscriberClient subscriberClient, String packageName,
+ SubscriberConnection(IVmsSubscriberClient subscriberClient, int uid, String packageName,
int userId) {
mClient = subscriberClient;
+ mUid = uid;
mPackageName = packageName;
mUserId = userId;
}
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/PrivateVolumeSettings.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/PrivateVolumeSettings.java
index 3cd45c5..31d13c3 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/PrivateVolumeSettings.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/PrivateVolumeSettings.java
@@ -762,7 +762,7 @@
public Dialog onCreateDialog(Bundle savedInstanceState) {
return new AlertDialog.Builder(getActivity())
.setMessage(getContext().getString(R.string.storage_detail_dialog_system,
- Build.VERSION.RELEASE_OR_CODENAME))
+ Build.VERSION.RELEASE))
.setPositiveButton(android.R.string.ok, null)
.create();
}
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/firmwareversion/FirmwareVersionDetailPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/firmwareversion/FirmwareVersionDetailPreferenceController.java
index 4ee76b8..24b1e29 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/firmwareversion/FirmwareVersionDetailPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/firmwareversion/FirmwareVersionDetailPreferenceController.java
@@ -70,7 +70,7 @@
@Override
public CharSequence getSummary() {
- return Build.VERSION.RELEASE_OR_CODENAME;
+ return Build.VERSION.RELEASE;
}
@Override
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/firmwareversion/FirmwareVersionPreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/firmwareversion/FirmwareVersionPreferenceController.java
index 789d047..e9f70e6 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/firmwareversion/FirmwareVersionPreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/deviceinfo/firmwareversion/FirmwareVersionPreferenceController.java
@@ -34,6 +34,6 @@
@Override
public CharSequence getSummary() {
- return Build.VERSION.RELEASE_OR_CODENAME;
+ return Build.VERSION.RELEASE;
}
}
diff --git a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/system/SystemUpdatePreferenceController.java b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/system/SystemUpdatePreferenceController.java
index efa06ea..9209d3f 100644
--- a/tests/CarDeveloperOptions/src/com/android/car/developeroptions/system/SystemUpdatePreferenceController.java
+++ b/tests/CarDeveloperOptions/src/com/android/car/developeroptions/system/SystemUpdatePreferenceController.java
@@ -89,7 +89,7 @@
@Override
public CharSequence getSummary() {
CharSequence summary = mContext.getString(R.string.android_version_summary,
- Build.VERSION.RELEASE_OR_CODENAME);
+ Build.VERSION.RELEASE);
final FutureTask<Bundle> bundleFutureTask = new FutureTask<>(
// Put the API call in a future to avoid StrictMode violation.
() -> mUpdateManager.retrieveSystemUpdateInfo());
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index 2fbeef9..dcfffd1 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -158,6 +158,27 @@
android:grantUriPermissions="true"
android:exported="true"/>
+ <activity android:name=".AlwaysCrashingActivity"
+ android:label="@string/always_crashing_activity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".NoCrashActivity"
+ android:label="@string/no_crash_activity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+
+ <activity android:name=".EmptyActivity"
+ android:label="@string/empty_activity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
+
</application>
</manifest>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/empty_activity.xml b/tests/EmbeddedKitchenSinkApp/res/layout/empty_activity.xml
new file mode 100644
index 0000000..5312fee
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/empty_activity.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <TextView
+ android:id="@+id/empty_text"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/empty_activity"
+ android:layout_weight="1" />
+</LinearLayout>
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
index 6575ce1..0d56369 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -332,4 +332,9 @@
<string name="usernotice" translatable="false">This screen is for showing initial user notice and is not for product. Plz change config_userNoticeUiService in CarService before shipping.</string>
<string name="dismiss_now" translatable="false">Dismiss for now</string>
<string name="dismiss_forever" translatable="false">Do not show again</string>
+
+ <!-- [AlwaysCrashing|NoCrash|Empty]Activity -->
+ <string name="always_crashing_activity" translatable="false">Always Crash Activity</string>
+ <string name="no_crash_activity" translatable="false">No Crash Activity</string>
+ <string name="empty_activity" translatable="false">Empty Activity</string>
</resources>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/AlwaysCrashingActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/AlwaysCrashingActivity.java
new file mode 100644
index 0000000..05529b2
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/AlwaysCrashingActivity.java
@@ -0,0 +1,30 @@
+/*
+ * 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.google.android.car.kitchensink;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Activity for testing purpose. This one always crashes inside onCreate.
+ */
+public class AlwaysCrashingActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ throw new RuntimeException("Intended crash for testing");
+ }
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/EmptyActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/EmptyActivity.java
new file mode 100644
index 0000000..aad25cb
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/EmptyActivity.java
@@ -0,0 +1,29 @@
+/*
+ * 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.google.android.car.kitchensink;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class EmptyActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.empty_activity);
+ }
+
+}
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/NoCrashActivity.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/NoCrashActivity.java
new file mode 100644
index 0000000..f10e1db
--- /dev/null
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/NoCrashActivity.java
@@ -0,0 +1,31 @@
+/*
+ * 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.google.android.car.kitchensink;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.TextView;
+
+public class NoCrashActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.empty_activity);
+ TextView text = findViewById(R.id.empty_text);
+ text.setText(R.string.no_crash_activity);
+ }
+}
diff --git a/tests/android_car_api_test/Android.mk b/tests/android_car_api_test/Android.mk
index c292542..6eddb90 100644
--- a/tests/android_car_api_test/Android.mk
+++ b/tests/android_car_api_test/Android.mk
@@ -39,6 +39,9 @@
android.hidl.base-V1.0-java \
android.hardware.automotive.vehicle-V2.0-java \
android.car.cluster.navigation \
+ compatibility-device-util-axt \
+ testng \
+ truth-prebuilt \
LOCAL_JAVA_LIBRARIES := android.car android.test.runner android.test.base
diff --git a/tests/android_car_api_test/AndroidManifest.xml b/tests/android_car_api_test/AndroidManifest.xml
index 3339d28..301ec86 100644
--- a/tests/android_car_api_test/AndroidManifest.xml
+++ b/tests/android_car_api_test/AndroidManifest.xml
@@ -20,7 +20,9 @@
android:sharedUserId="com.google.android.car.uid.kitchensink"
android:debuggable="true" >
- <instrumentation android:name="android.test.InstrumentationTestRunner"
+ <uses-permission android:name="android.permission.FORCE_STOP_PACKAGES" />
+
+ <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
android:targetPackage="android.car.apitest"
android:label="Tests for Car APIs"
android:debuggable="true" />
@@ -35,5 +37,7 @@
</activity>
<service android:name=".CarProjectionManagerTest$TestService"
android:exported="true" />
+ <activity android:name=".CarActivityViewDisplayIdTest$ActivityInActivityView"/>
+ <activity android:name=".CarActivityViewDisplayIdTest$ActivityViewTestActivity"/>
</application>
</manifest>
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarActivityViewDisplayIdTest.java b/tests/android_car_api_test/src/android/car/apitest/CarActivityViewDisplayIdTest.java
new file mode 100644
index 0000000..e82fa65
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/CarActivityViewDisplayIdTest.java
@@ -0,0 +1,353 @@
+/*
+ * 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 android.car.apitest;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assume.assumeTrue;
+import static org.testng.Assert.assertThrows;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.ActivityOptions;
+import android.app.ActivityView;
+import android.app.Instrumentation;
+import android.car.Car;
+import android.car.app.CarActivityView;
+import android.car.drivingstate.CarUxRestrictionsManager;
+import android.content.Context;
+import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.os.Bundle;
+import android.os.SystemClock;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.Display;
+import android.view.ViewGroup;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.Before;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.junit.runners.MethodSorters;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Build/Install/Run:
+ * atest AndroidCarApiTest:CarActivityViewDisplayIdTest
+ */
+@RunWith(JUnit4.class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+@MediumTest
+public class CarActivityViewDisplayIdTest extends CarApiTestBase {
+ private static final String CAR_LAUNCHER_PKG_NAME = "com.android.car.carlauncher";
+ private static final String ACTIVITY_VIEW_DISPLAY_NAME = "TaskVirtualDisplay";
+ private static final String AM_START_HOME_ACTIVITY_COMMAND =
+ "am start -a android.intent.action.MAIN -c android.intent.category.HOME";
+ private static final int NONEXISTENT_DISPLAY_ID = Integer.MAX_VALUE;
+ private static final int TEST_TIMEOUT_SEC = 5;
+ private static final int TEST_TIMEOUT_MS = TEST_TIMEOUT_SEC * 1000;
+
+ private DisplayManager mDisplayManager;
+ private CarUxRestrictionsManager mCarUxRestrictionsManager;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ mDisplayManager = getContext().getSystemService(DisplayManager.class);
+ mCarUxRestrictionsManager = (CarUxRestrictionsManager)
+ getCar().getCarManager(Car.CAR_UX_RESTRICTION_SERVICE);
+ }
+
+ private int getMappedPhysicalDisplayOfVirtualDisplay(int displayId) {
+ return mCarUxRestrictionsManager.getMappedPhysicalDisplayOfVirtualDisplay(displayId);
+ }
+
+ @Test
+ public void testSingleActivityView() throws Exception {
+ ActivityViewTestActivity activity = startActivityViewTestActivity(DEFAULT_DISPLAY);
+ activity.waitForActivityViewReady();
+ int virtualDisplayId = activity.getActivityView().getVirtualDisplayId();
+
+ startTestActivity(ActivityInActivityView.class, virtualDisplayId);
+
+ assertThat(getMappedPhysicalDisplayOfVirtualDisplay(virtualDisplayId))
+ .isEqualTo(DEFAULT_DISPLAY);
+ assertThat(getMappedPhysicalDisplayOfVirtualDisplay(DEFAULT_DISPLAY))
+ .isEqualTo(INVALID_DISPLAY);
+
+ activity.finish();
+ activity.waitForActivityViewDestroyed();
+
+ assertThat(getMappedPhysicalDisplayOfVirtualDisplay(virtualDisplayId))
+ .isEqualTo(INVALID_DISPLAY);
+ assertThat(getMappedPhysicalDisplayOfVirtualDisplay(DEFAULT_DISPLAY))
+ .isEqualTo(INVALID_DISPLAY);
+ }
+
+ @Test
+ public void testDoubleActivityView() throws Exception {
+ ActivityViewTestActivity activity1 = startActivityViewTestActivity(DEFAULT_DISPLAY);
+ activity1.waitForActivityViewReady();
+ int virtualDisplayId1 = activity1.getActivityView().getVirtualDisplayId();
+
+ ActivityViewTestActivity activity2 = startActivityViewTestActivity(virtualDisplayId1);
+ activity2.waitForActivityViewReady();
+ int virtualDisplayId2 = activity2.getActivityView().getVirtualDisplayId();
+
+ startTestActivity(ActivityInActivityView.class, virtualDisplayId2);
+
+ assertThat(virtualDisplayId1).isNotEqualTo(virtualDisplayId2);
+ assertThat(getMappedPhysicalDisplayOfVirtualDisplay(virtualDisplayId1))
+ .isEqualTo(DEFAULT_DISPLAY);
+ assertThat(getMappedPhysicalDisplayOfVirtualDisplay(virtualDisplayId2))
+ .isEqualTo(DEFAULT_DISPLAY);
+ assertThat(getMappedPhysicalDisplayOfVirtualDisplay(DEFAULT_DISPLAY))
+ .isEqualTo(INVALID_DISPLAY);
+
+ activity2.finish();
+ activity1.finish();
+
+ activity2.waitForActivityViewDestroyed();
+ activity1.waitForActivityViewDestroyed();
+
+ assertThat(getMappedPhysicalDisplayOfVirtualDisplay(virtualDisplayId1))
+ .isEqualTo(INVALID_DISPLAY);
+ assertThat(getMappedPhysicalDisplayOfVirtualDisplay(virtualDisplayId2))
+ .isEqualTo(INVALID_DISPLAY);
+ assertThat(getMappedPhysicalDisplayOfVirtualDisplay(DEFAULT_DISPLAY))
+ .isEqualTo(INVALID_DISPLAY);
+ }
+
+ @Test
+ public void testThrowsExceptionOnReportingNonExistingDisplay() throws Exception {
+ ActivityViewTestActivity activity = startActivityViewTestActivity(DEFAULT_DISPLAY);
+ activity.waitForActivityViewReady();
+ int virtualDisplayId = activity.getActivityView().getVirtualDisplayId();
+
+ // This will pass since the test owns the display.
+ mCarUxRestrictionsManager.reportVirtualDisplayToPhysicalDisplay(virtualDisplayId,
+ NONEXISTENT_DISPLAY_ID);
+
+ assertThat(getMappedPhysicalDisplayOfVirtualDisplay(virtualDisplayId))
+ .isEqualTo(NONEXISTENT_DISPLAY_ID);
+
+ activity.finish();
+ activity.waitForActivityViewDestroyed();
+
+ // Now the display was released, so expect to throw an Exception.
+ assertThrows(
+ java.lang.IllegalArgumentException.class,
+ () -> mCarUxRestrictionsManager.reportVirtualDisplayToPhysicalDisplay(
+ virtualDisplayId, NONEXISTENT_DISPLAY_ID));
+ }
+
+ // TODO(b/143353546): Make the following tests not to rely on CarLauncher.
+ @Test
+ public void testThrowsExceptionOnReportingNonOwningDisplay() throws Exception {
+ int displayIdOfCarLauncher = waitForCarLauncherDisplayReady(INVALID_DISPLAY);
+ assumeTrue(INVALID_DISPLAY != displayIdOfCarLauncher);
+
+ // CarLauncher owns the display, so expect to throw an Exception.
+ assertThrows(
+ java.lang.SecurityException.class,
+ () -> mCarUxRestrictionsManager.reportVirtualDisplayToPhysicalDisplay(
+ displayIdOfCarLauncher, DEFAULT_DISPLAY + 1));
+ }
+
+ // The test name starts with 'testz' to run it at the last among the tests, since killing
+ // CarLauncher causes the system unstable for a while.
+ @Test
+ public void testzCleanUpAfterClientIsCrashed() throws Exception {
+ int displayIdOfCarLauncher = waitForCarLauncherDisplayReady(INVALID_DISPLAY);
+ assumeTrue(INVALID_DISPLAY != displayIdOfCarLauncher);
+
+ assertThat(getMappedPhysicalDisplayOfVirtualDisplay(displayIdOfCarLauncher))
+ .isEqualTo(DEFAULT_DISPLAY);
+
+ ActivityManager am = getContext().getSystemService(ActivityManager.class);
+ am.forceStopPackage(CAR_LAUNCHER_PKG_NAME);
+ launchHomeActivity();
+ int displayIdOfNewCarLauncher = waitForCarLauncherDisplayReady(displayIdOfCarLauncher);
+
+ assertThat(displayIdOfNewCarLauncher).isNotEqualTo(INVALID_DISPLAY);
+ assertThat(displayIdOfNewCarLauncher).isNotEqualTo(displayIdOfCarLauncher);
+ assertThat(getMappedPhysicalDisplayOfVirtualDisplay(displayIdOfCarLauncher))
+ .isEqualTo(INVALID_DISPLAY);
+ assertThat(getMappedPhysicalDisplayOfVirtualDisplay(displayIdOfNewCarLauncher))
+ .isEqualTo(DEFAULT_DISPLAY);
+ }
+
+ private int findDisplayIdOfCarLauncher() {
+ for (Display display: mDisplayManager.getDisplays()) {
+ String displayName = display.getName();
+ if (display.getName().contains(ACTIVITY_VIEW_DISPLAY_NAME)
+ && display.getOwnerPackageName().equals(CAR_LAUNCHER_PKG_NAME)) {
+ return display.getDisplayId();
+ }
+ }
+ return INVALID_DISPLAY;
+ }
+
+ /**
+ * Waits until CarLauncher is ready and returns the display id of its ActivityView.
+ * When killing a CarLauncher, findDisplayIdOfCarLauncher() can return the old one for
+ * a while until DisplayManagerService gets the update.
+ * To differentiate it, the method accepts the old display id and ignores it.
+ */
+ private int waitForCarLauncherDisplayReady(int oldDisplayId) {
+ for (int i = 0; i < TEST_TIMEOUT_SEC; ++i) {
+ int displayId = findDisplayIdOfCarLauncher();
+ if (displayId != INVALID_DISPLAY && displayId != oldDisplayId) {
+ return displayId;
+ }
+ SystemClock.sleep(/*ms=*/ 1000);
+ }
+ return findDisplayIdOfCarLauncher();
+ }
+
+ private static class TestActivity extends Activity {
+ private final CountDownLatch mResumed = new CountDownLatch(1);
+
+ @Override
+ protected void onPostResume() {
+ super.onPostResume();
+ mResumed.countDown();
+ }
+
+ void waitForResumeStateChange() throws Exception {
+ waitForLatch(mResumed);
+ }
+ }
+
+ private static void waitForLatch(CountDownLatch latch) throws Exception {
+ boolean result = latch.await(TEST_TIMEOUT_SEC, TimeUnit.SECONDS);
+ if (!result) {
+ throw new TimeoutException("Timed out waiting for task stack change notification");
+ }
+ }
+
+ /**
+ * Starts the provided activity and returns the started instance.
+ */
+ private TestActivity startTestActivity(Class<?> activityClass, int displayId) throws Exception {
+ Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+ activityClass.getName(), null, false);
+ getInstrumentation().addMonitor(monitor);
+
+ Context context = getContext();
+ Intent intent = new Intent(context, activityClass).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ ActivityOptions options = ActivityOptions.makeBasic();
+ if (displayId != DEFAULT_DISPLAY) {
+ intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
+ options.setLaunchDisplayId(displayId);
+ }
+ context.startActivity(intent, options.toBundle());
+
+ TestActivity activity = (TestActivity) monitor.waitForActivityWithTimeout(TEST_TIMEOUT_MS);
+ if (activity == null) {
+ throw new TimeoutException("Timed out waiting for Activity");
+ }
+ activity.waitForResumeStateChange();
+ return activity;
+ }
+
+ public static final class ActivityViewTestActivity extends TestActivity {
+ private static final class ActivityViewStateCallback extends ActivityView.StateCallback {
+ private final CountDownLatch mActivityViewReadyLatch = new CountDownLatch(1);
+ private final CountDownLatch mActivityViewDestroyedLatch = new CountDownLatch(1);
+
+ @Override
+ public void onActivityViewReady(ActivityView view) {
+ mActivityViewReadyLatch.countDown();
+ }
+
+ @Override
+ public void onActivityViewDestroyed(ActivityView view) {
+ mActivityViewDestroyedLatch.countDown();
+ }
+ }
+
+ private CarActivityView mActivityView;
+ private final ActivityViewStateCallback mCallback = new ActivityViewStateCallback();
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ mActivityView = new CarActivityView(this, /*attrs=*/null , /*defStyle=*/0 ,
+ /*singleTaskInstance=*/true);
+ mActivityView.setCallback(mCallback);
+ setContentView(mActivityView);
+
+ ViewGroup.LayoutParams layoutParams = mActivityView.getLayoutParams();
+ layoutParams.width = MATCH_PARENT;
+ layoutParams.height = MATCH_PARENT;
+ mActivityView.requestLayout();
+ }
+
+ @Override
+ protected void onDestroy() {
+ mActivityView.release();
+ super.onDestroy();
+ }
+
+ ActivityView getActivityView() {
+ return mActivityView;
+ }
+
+ void waitForActivityViewReady() throws Exception {
+ waitForLatch(mCallback.mActivityViewReadyLatch);
+ }
+
+ void waitForActivityViewDestroyed() throws Exception {
+ waitForLatch(mCallback.mActivityViewDestroyedLatch);
+ }
+ }
+
+ private ActivityViewTestActivity startActivityViewTestActivity(int displayId) throws Exception {
+ return (ActivityViewTestActivity) startTestActivity(ActivityViewTestActivity.class,
+ displayId);
+ }
+
+ // Activity that has {@link android.R.attr#resizeableActivity} attribute set to {@code true}
+ public static class ActivityInActivityView extends TestActivity {}
+
+ private ActivityInActivityView startActivityInActivityView(int displayId) throws Exception {
+ return (ActivityInActivityView) startTestActivity(ActivityInActivityView.class, displayId);
+ }
+
+ private void launchHomeActivity() {
+ try {
+ SystemUtil.runShellCommand(getInstrumentation(), AM_START_HOME_ACTIVITY_COMMAND);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java b/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
index dc167b1..d78d8d6 100644
--- a/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
+++ b/tests/android_car_api_test/src/android/car/apitest/CarApiTestBase.java
@@ -16,13 +16,15 @@
package android.car.apitest;
+import android.car.Car;
import android.content.ComponentName;
import android.content.ServiceConnection;
import android.os.IBinder;
import android.os.Looper;
-import android.car.Car;
import android.test.AndroidTestCase;
+import androidx.test.InstrumentationRegistry;
+
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@@ -41,6 +43,7 @@
@Override
protected void setUp() throws Exception {
super.setUp();
+ setContext(InstrumentationRegistry.getContext());
mCar = Car.createCar(getContext(), mConnectionListener);
mCar.connect();
mConnectionListener.waitForConnection(DEFAULT_WAIT_TIMEOUT_MS);
diff --git a/tests/carservice_unit_test/src/com/android/car/VmsPublisherServiceTest.java b/tests/carservice_unit_test/src/com/android/car/VmsPublisherServiceTest.java
index 15adadf..00f1243 100644
--- a/tests/carservice_unit_test/src/com/android/car/VmsPublisherServiceTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/VmsPublisherServiceTest.java
@@ -16,8 +16,6 @@
package com.android.car;
-import static com.google.common.truth.Truth.assertThat;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
@@ -45,24 +43,23 @@
import androidx.test.filters.SmallTest;
+import com.android.car.stats.CarStatsService;
+import com.android.car.stats.VmsClientLog;
import com.android.car.vms.VmsBrokerService;
import com.android.car.vms.VmsClientManager;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
-import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
-import java.io.ByteArrayOutputStream;
-import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
-import java.util.List;
@RunWith(MockitoJUnitRunner.class)
@SmallTest
@@ -72,18 +69,20 @@
private static final VmsLayersOffering OFFERING = new VmsLayersOffering(Collections.emptySet(),
54321);
private static final VmsLayer LAYER = new VmsLayer(1, 2, 3);
- private static final VmsLayer LAYER2 = new VmsLayer(2, 2, 8);
- private static final VmsLayer LAYER3 = new VmsLayer(3, 2, 8);
- private static final VmsLayer LAYER4 = new VmsLayer(4, 2, 8);
private static final int PUBLISHER_ID = 54321;
private static final byte[] PAYLOAD = new byte[]{1, 2, 3, 4};
- private static final byte[] PAYLOAD2 = new byte[]{1, 2, 3, 4, 5, 6};
- private static final byte[] PAYLOAD3 = new byte[]{10, 12, 93, 4, 5, 6, 1, 1, 1};
+
+ private static final int PUBLISHER_UID = 10100;
+ private static final int SUBSCRIBER_UID = 10101;
+ private static final int SUBSCRIBER_UID2 = 10102;
+ private static final int NO_SUBSCRIBERS_UID = -1;
@Mock
private Context mContext;
@Mock
+ private CarStatsService mStatsService;
+ @Mock
private VmsBrokerService mBrokerService;
@Captor
private ArgumentCaptor<VmsBrokerService.PublisherListener> mProxyCaptor;
@@ -91,13 +90,18 @@
private VmsClientManager mClientManager;
@Mock
+ private VmsClientLog mPublisherLog;
+ @Mock
+ private VmsClientLog mSubscriberLog;
+ @Mock
+ private VmsClientLog mSubscriberLog2;
+ @Mock
+ private VmsClientLog mNoSubscribersLog;
+
+ @Mock
private IVmsSubscriberClient mSubscriberClient;
@Mock
private IVmsSubscriberClient mSubscriberClient2;
- @Mock
- private IVmsSubscriberClient mThrowingSubscriberClient;
- @Mock
- private IVmsSubscriberClient mThrowingSubscriberClient2;
private VmsPublisherService mPublisherService;
private MockPublisherClient mPublisherClient;
@@ -105,14 +109,27 @@
@Before
public void setUp() {
- mPublisherService = new VmsPublisherService(mContext, mBrokerService, mClientManager);
+ mPublisherService = new VmsPublisherService(mContext, mStatsService, mBrokerService,
+ mClientManager, () -> PUBLISHER_UID);
verify(mClientManager).setPublisherService(mPublisherService);
+ when(mClientManager.getSubscriberUid(mSubscriberClient)).thenReturn(SUBSCRIBER_UID);
+ when(mClientManager.getSubscriberUid(mSubscriberClient2)).thenReturn(SUBSCRIBER_UID2);
+
+ when(mStatsService.getVmsClientLog(PUBLISHER_UID)).thenReturn(mPublisherLog);
+ when(mStatsService.getVmsClientLog(SUBSCRIBER_UID)).thenReturn(mSubscriberLog);
+ when(mStatsService.getVmsClientLog(SUBSCRIBER_UID2)).thenReturn(mSubscriberLog2);
+ when(mStatsService.getVmsClientLog(NO_SUBSCRIBERS_UID)).thenReturn(mNoSubscribersLog);
+
mPublisherClient = new MockPublisherClient();
mPublisherClient2 = new MockPublisherClient();
when(mBrokerService.getSubscribersForLayerFromPublisher(LAYER, PUBLISHER_ID))
.thenReturn(new HashSet<>(Arrays.asList(mSubscriberClient, mSubscriberClient2)));
+ }
+ @After
+ public void tearDown() {
+ verifyNoMoreInteractions(mPublisherLog, mSubscriberLog, mSubscriberLog2, mNoSubscribersLog);
}
@Test
@@ -188,6 +205,10 @@
PAYLOAD);
verify(mSubscriberClient).onVmsMessageReceived(LAYER, PAYLOAD);
verify(mSubscriberClient2).onVmsMessageReceived(LAYER, PAYLOAD);
+
+ verify(mPublisherLog).logPacketSent(LAYER, PAYLOAD.length);
+ verify(mSubscriberLog).logPacketReceived(LAYER, PAYLOAD.length);
+ verify(mSubscriberLog2).logPacketReceived(LAYER, PAYLOAD.length);
}
@Test
@@ -200,6 +221,19 @@
}
@Test
+ public void testPublish_NoSubscribers() throws Exception {
+ mPublisherService.onClientConnected("SomeClient", mPublisherClient);
+ when(mBrokerService.getSubscribersForLayerFromPublisher(LAYER, PUBLISHER_ID))
+ .thenReturn(Collections.emptySet());
+
+ mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
+ PAYLOAD);
+
+ verify(mPublisherLog).logPacketSent(LAYER, PAYLOAD.length);
+ verify(mNoSubscribersLog).logPacketDropped(LAYER, PAYLOAD.length);
+ }
+
+ @Test
public void testPublish_ClientError() throws Exception {
mPublisherService.onClientConnected("SomeClient", mPublisherClient);
doThrow(new RemoteException()).when(mSubscriberClient).onVmsMessageReceived(LAYER, PAYLOAD);
@@ -208,6 +242,10 @@
PAYLOAD);
verify(mSubscriberClient).onVmsMessageReceived(LAYER, PAYLOAD);
verify(mSubscriberClient2).onVmsMessageReceived(LAYER, PAYLOAD);
+
+ verify(mPublisherLog).logPacketSent(LAYER, PAYLOAD.length);
+ verify(mSubscriberLog).logPacketDropped(LAYER, PAYLOAD.length);
+ verify(mSubscriberLog2).logPacketReceived(LAYER, PAYLOAD.length);
}
@Test(expected = SecurityException.class)
@@ -299,341 +337,6 @@
}
@Test
- public void testDump_getPacketCount() throws Exception {
- mPublisherService.onClientConnected("SomeClient", mPublisherClient);
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- PrintWriter printWriter = new PrintWriter(outputStream);
-
- mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
- PAYLOAD);
- mPublisherService.dump(printWriter);
-
- printWriter.flush();
- String dumpString = outputStream.toString();
- String expectedPacketCountString = String.format(VmsPublisherService.PACKET_COUNT_FORMAT,
- LAYER, 1L);
- String expectedPacketSizeString = String.format(VmsPublisherService.PACKET_SIZE_FORMAT,
- LAYER, PAYLOAD.length);
- assertThat(dumpString.contains(expectedPacketCountString)).isTrue();
- assertThat(dumpString.contains(expectedPacketSizeString)).isTrue();
- }
-
- @Test
- public void testDump_getPacketCounts() throws Exception {
- mPublisherService.onClientConnected("SomeClient", mPublisherClient);
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- PrintWriter printWriter = new PrintWriter(outputStream);
-
- mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
- PAYLOAD);
- mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER2, PUBLISHER_ID,
- PAYLOAD);
- mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
- PAYLOAD2);
- mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
- PAYLOAD);
- mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER3, PUBLISHER_ID,
- PAYLOAD);
- mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
- PAYLOAD3);
- mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
- PAYLOAD3);
- mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER2, PUBLISHER_ID,
- PAYLOAD3);
- mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
- PAYLOAD3);
- mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER3, PUBLISHER_ID,
- PAYLOAD);
- mPublisherService.dump(printWriter);
-
- printWriter.flush();
- String dumpString = outputStream.toString();
-
- // LAYER called 6 times with PAYLOAD 2 times, PAYLOAD2 1 time, PAYLOAD3 3 times
- String expectedPacketCountString1 = String.format(VmsPublisherService.PACKET_COUNT_FORMAT,
- LAYER, 6L);
- String expectedPacketSizeString1 = String.format(VmsPublisherService.PACKET_SIZE_FORMAT,
- LAYER, 2 * PAYLOAD.length + PAYLOAD2.length + 3 * PAYLOAD3.length);
-
- // LAYER2 called 2 times with PAYLOAD 1 time, PAYLOAD2 0 time, PAYLOAD3 1 times
- String expectedPacketCountString2 = String.format(VmsPublisherService.PACKET_COUNT_FORMAT,
- LAYER2, 2L);
- String expectedPacketSizeString2 = String.format(VmsPublisherService.PACKET_SIZE_FORMAT,
- LAYER2, PAYLOAD.length + PAYLOAD3.length);
-
- // LAYER3 called 2 times with PAYLOAD 2 times, PAYLOAD2 0 time, PAYLOAD3 0 times
- String expectedPacketCountString3 = String.format(VmsPublisherService.PACKET_COUNT_FORMAT,
- LAYER3, 2L);
- String expectedPacketSizeString3 = String.format(VmsPublisherService.PACKET_SIZE_FORMAT,
- LAYER3, 2 * PAYLOAD.length);
-
- assertThat(dumpString.contains(expectedPacketCountString1)).isTrue();
- assertThat(dumpString.contains(expectedPacketSizeString1)).isTrue();
- assertThat(dumpString.contains(expectedPacketCountString2)).isTrue();
- assertThat(dumpString.contains(expectedPacketSizeString2)).isTrue();
- assertThat(dumpString.contains(expectedPacketCountString3)).isTrue();
- assertThat(dumpString.contains(expectedPacketSizeString3)).isTrue();
- }
-
- @Test
- public void testDumpNoListeners_getPacketFailureCount() throws Exception {
- mPublisherService.onClientConnected("SomeClient", mPublisherClient);
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- PrintWriter printWriter = new PrintWriter(outputStream);
-
- // Layer 2 has no listeners and should therefore result in a packet failure to be recorded.
- mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER2, PUBLISHER_ID,
- PAYLOAD);
- mPublisherService.dump(printWriter);
-
- printWriter.flush();
- String dumpString = outputStream.toString();
-
- String expectedPacketFailureString = String.format(
- VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT,
- LAYER2, "SomeClient", "", 1L);
- String expectedPacketFailureSizeString = String.format(
- VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT,
- LAYER2, "SomeClient", "", PAYLOAD.length);
-
- assertThat(dumpString.contains(expectedPacketFailureString)).isTrue();
- assertThat(dumpString.contains(expectedPacketFailureSizeString)).isTrue();
- }
-
- @Test
- public void testDumpNoListeners_getPacketFailureCounts() throws Exception {
- // LAYER2 and LAYER3 both have no listeners
- when(mBrokerService.getSubscribersForLayerFromPublisher(LAYER2, PUBLISHER_ID))
- .thenReturn(new HashSet<>());
- when(mBrokerService.getSubscribersForLayerFromPublisher(LAYER3, PUBLISHER_ID))
- .thenReturn(new HashSet<>());
-
- mPublisherService.onClientConnected("SomeClient", mPublisherClient);
- mPublisherService.onClientConnected("SomeClient2", mPublisherClient2);
-
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- PrintWriter printWriter = new PrintWriter(outputStream);
-
- // Layer 2 has no listeners and should therefore result in a packet failure to be recorded.
- mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER2, PUBLISHER_ID,
- PAYLOAD);
- mPublisherClient2.mPublisherService.publish(mPublisherClient2.mToken, LAYER3, PUBLISHER_ID,
- PAYLOAD);
-
- mPublisherService.dump(printWriter);
-
- printWriter.flush();
- String dumpString = outputStream.toString();
-
- String expectedPacketFailureString = String.format(
- VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT,
- LAYER2, "SomeClient", "", 1L);
- String expectedPacketFailureString2 = String.format(
- VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT,
- LAYER3, "SomeClient2", "", 1L);
- String expectedPacketFailureSizeString = String.format(
- VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT,
- LAYER2, "SomeClient", "", PAYLOAD.length);
- String expectedPacketFailureSizeString2 = String.format(
- VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT,
- LAYER3, "SomeClient2", "", PAYLOAD.length);
-
- assertThat(dumpString.contains(expectedPacketFailureString)).isTrue();
- assertThat(dumpString.contains(expectedPacketFailureSizeString)).isTrue();
- assertThat(dumpString.contains(expectedPacketFailureString2)).isTrue();
- assertThat(dumpString.contains(expectedPacketFailureSizeString2)).isTrue();
- }
-
- @Test
- public void testDumpRemoteException_getPacketFailureCount() throws Exception {
- // The listener on LAYER3 will throw on LAYER3 and PAYLOAD
- Mockito.doThrow(new RemoteException()).when(mThrowingSubscriberClient).onVmsMessageReceived(
- LAYER3, PAYLOAD);
- when(mBrokerService.getSubscribersForLayerFromPublisher(LAYER3, PUBLISHER_ID))
- .thenReturn(new HashSet<>(Arrays.asList(mThrowingSubscriberClient)));
- when(mClientManager.getPackageName(mThrowingSubscriberClient)).thenReturn("Thrower");
-
- mPublisherService.onClientConnected("SomeClient", mPublisherClient);
-
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- PrintWriter printWriter = new PrintWriter(outputStream);
-
- // Layer 2 has no listeners and should therefore result in a packet failure to be recorded.
- mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER3, PUBLISHER_ID,
- PAYLOAD);
-
- mPublisherService.dump(printWriter);
-
- printWriter.flush();
- String dumpString = outputStream.toString();
-
- String expectedPacketFailureString = String.format(
- VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT,
- LAYER3, "SomeClient", "Thrower", 1L);
- String expectedPacketFailureSizeString = String.format(
- VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT,
- LAYER3, "SomeClient", "Thrower", PAYLOAD.length);
-
- assertThat(dumpString.contains(expectedPacketFailureString)).isTrue();
- assertThat(dumpString.contains(expectedPacketFailureSizeString)).isTrue();
- }
-
- @Test
- public void testDumpRemoteException_getPacketFailureCounts() throws Exception {
- // The listeners will throw on LAYER3 or LAYER4 and PAYLOAD
- Mockito.doThrow(new RemoteException()).when(mThrowingSubscriberClient).onVmsMessageReceived(
- LAYER3, PAYLOAD);
- Mockito.doThrow(new RemoteException()).when(mThrowingSubscriberClient).onVmsMessageReceived(
- LAYER4, PAYLOAD);
- Mockito.doThrow(new RemoteException()).when(
- mThrowingSubscriberClient2).onVmsMessageReceived(LAYER3, PAYLOAD);
- Mockito.doThrow(new RemoteException()).when(
- mThrowingSubscriberClient2).onVmsMessageReceived(LAYER4, PAYLOAD);
-
- when(mBrokerService.getSubscribersForLayerFromPublisher(LAYER3, PUBLISHER_ID))
- .thenReturn(new HashSet<>(
- Arrays.asList(mThrowingSubscriberClient, mThrowingSubscriberClient2)));
- when(mBrokerService.getSubscribersForLayerFromPublisher(LAYER4, PUBLISHER_ID))
- .thenReturn(new HashSet<>(
- Arrays.asList(mThrowingSubscriberClient, mThrowingSubscriberClient2)));
-
- when(mClientManager.getPackageName(mThrowingSubscriberClient)).thenReturn("Thrower");
- when(mClientManager.getPackageName(mThrowingSubscriberClient2)).thenReturn("Thrower2");
-
- mPublisherService.onClientConnected("SomeClient", mPublisherClient);
- mPublisherService.onClientConnected("SomeClient2", mPublisherClient2);
-
- // Layer 2 has no listeners and should therefore result in a packet failure to be recorded.
- mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER3, PUBLISHER_ID,
- PAYLOAD);
- mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER3, PUBLISHER_ID,
- PAYLOAD);
- mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER4, PUBLISHER_ID,
- PAYLOAD);
- mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER4, PUBLISHER_ID,
- PAYLOAD);
-
- mPublisherClient2.mPublisherService.publish(mPublisherClient2.mToken, LAYER3, PUBLISHER_ID,
- PAYLOAD);
- mPublisherClient2.mPublisherService.publish(mPublisherClient2.mToken, LAYER3, PUBLISHER_ID,
- PAYLOAD);
- mPublisherClient2.mPublisherService.publish(mPublisherClient2.mToken, LAYER4, PUBLISHER_ID,
- PAYLOAD);
- mPublisherClient2.mPublisherService.publish(mPublisherClient2.mToken, LAYER4, PUBLISHER_ID,
- PAYLOAD);
-
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- PrintWriter printWriter = new PrintWriter(outputStream);
- mPublisherService.dump(printWriter);
-
- printWriter.flush();
- String dumpString = outputStream.toString();
-
- List<String> expectedStrings = Arrays.asList(
- String.format(VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT, LAYER3, "SomeClient",
- "Thrower", 2L),
- String.format(VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT, LAYER3, "SomeClient",
- "Thrower2", 2L),
- String.format(VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT, LAYER4, "SomeClient",
- "Thrower", 2L),
- String.format(VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT, LAYER4, "SomeClient",
- "Thrower2", 2L),
- String.format(VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT, LAYER3,
- "SomeClient2",
- "Thrower", 2L),
- String.format(VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT, LAYER3,
- "SomeClient2",
- "Thrower2", 2L),
- String.format(VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT, LAYER4,
- "SomeClient2",
- "Thrower", 2L),
- String.format(VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT, LAYER4,
- "SomeClient2",
- "Thrower2", 2L),
-
- String.format(VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT, LAYER3, "SomeClient",
- "Thrower", 2 * PAYLOAD.length),
- String.format(VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT, LAYER3, "SomeClient",
- "Thrower2", 2 * PAYLOAD.length),
- String.format(VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT, LAYER4, "SomeClient",
- "Thrower", 2 * PAYLOAD.length),
- String.format(VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT, LAYER4, "SomeClient",
- "Thrower2", 2 * PAYLOAD.length),
- String.format(VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT, LAYER3, "SomeClient2",
- "Thrower", 2 * PAYLOAD.length),
- String.format(VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT, LAYER3, "SomeClient2",
- "Thrower2", 2 * PAYLOAD.length),
- String.format(VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT, LAYER4, "SomeClient2",
- "Thrower", 2 * PAYLOAD.length),
- String.format(VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT, LAYER4, "SomeClient2",
- "Thrower2", 2 * PAYLOAD.length));
-
- for (String expected : expectedStrings) {
- assertThat(dumpString.contains(expected)).isTrue();
- }
- }
-
- @Test
- public void testDump_getAllMetrics() throws Exception {
-
- // LAYER3 has no subscribers
- when(mBrokerService.getSubscribersForLayerFromPublisher(LAYER3, PUBLISHER_ID))
- .thenReturn(new HashSet<>(Arrays.asList()));
-
- // LAYER4 has a subscriber that will always throw
- Mockito.doThrow(new RemoteException()).when(mThrowingSubscriberClient).onVmsMessageReceived(
- LAYER4, PAYLOAD);
-
- when(mBrokerService.getSubscribersForLayerFromPublisher(LAYER4, PUBLISHER_ID))
- .thenReturn(new HashSet<>(
- Arrays.asList(mThrowingSubscriberClient)));
-
- when(mClientManager.getPackageName(mThrowingSubscriberClient)).thenReturn("Thrower");
-
- mPublisherService.onClientConnected("SomeClient", mPublisherClient);
- mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
- PAYLOAD);
- mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER, PUBLISHER_ID,
- PAYLOAD2);
- mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER3, PUBLISHER_ID,
- PAYLOAD3);
- mPublisherClient.mPublisherService.publish(mPublisherClient.mToken, LAYER4, PUBLISHER_ID,
- PAYLOAD);
-
- ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
- PrintWriter printWriter = new PrintWriter(outputStream);
- mPublisherService.dump(printWriter);
-
- printWriter.flush();
- String dumpString = outputStream.toString();
-
- List<String> expectedStrings = Arrays.asList(
- String.format(VmsPublisherService.PACKET_COUNT_FORMAT, LAYER, 2),
- String.format(VmsPublisherService.PACKET_COUNT_FORMAT, LAYER3, 1),
- String.format(VmsPublisherService.PACKET_COUNT_FORMAT, LAYER4, 1),
- String.format(VmsPublisherService.PACKET_SIZE_FORMAT, LAYER,
- PAYLOAD.length + PAYLOAD2.length),
- String.format(VmsPublisherService.PACKET_SIZE_FORMAT, LAYER3, PAYLOAD3.length),
- String.format(VmsPublisherService.PACKET_SIZE_FORMAT, LAYER4, PAYLOAD.length),
- String.format(VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT, LAYER3, "SomeClient",
- "",
- 1),
- String.format(VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT, LAYER3, "SomeClient",
- "",
- PAYLOAD3.length),
- String.format(VmsPublisherService.PACKET_FAILURE_COUNT_FORMAT, LAYER4, "SomeClient",
- "Thrower", 1),
- String.format(VmsPublisherService.PACKET_FAILURE_SIZE_FORMAT, LAYER4, "SomeClient",
- "Thrower", PAYLOAD.length)
- );
-
- for (String expected : expectedStrings) {
- assertThat(dumpString.contains(expected)).isTrue();
- }
- }
-
-
- @Test
public void testRelease() {
mPublisherService.release();
}
diff --git a/tests/carservice_unit_test/src/com/android/car/stats/CarStatsServiceTest.java b/tests/carservice_unit_test/src/com/android/car/stats/CarStatsServiceTest.java
new file mode 100644
index 0000000..a18c88f
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/stats/CarStatsServiceTest.java
@@ -0,0 +1,357 @@
+/*
+ * 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.stats;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.when;
+
+import android.car.vms.VmsLayer;
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.car.stats.VmsClientLog.ConnectionState;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+@SmallTest
+@RunWith(JUnit4.class)
+public class CarStatsServiceTest {
+ private static final int CLIENT_UID = 10101;
+ private static final int CLIENT_UID2 = 10102;
+ private static final String CLIENT_PACKAGE = "test.package";
+ private static final String CLIENT_PACKAGE2 = "test.package2";
+ private static final VmsLayer LAYER = new VmsLayer(1, 2, 3);
+ private static final VmsLayer LAYER2 = new VmsLayer(2, 3, 4);
+ private static final VmsLayer LAYER3 = new VmsLayer(3, 4, 5);
+
+ @Rule
+ public MockitoRule mMockitoRule = MockitoJUnit.rule();
+ @Mock
+ private Context mContext;
+ @Mock
+ private PackageManager mPackageManager;
+
+ private CarStatsService mCarStatsService;
+ private StringWriter mDumpsysOutput;
+ private PrintWriter mDumpsysWriter;
+
+ @Before
+ public void setUp() {
+ when(mContext.getPackageManager()).thenReturn(mPackageManager);
+ when(mPackageManager.getNameForUid(CLIENT_UID)).thenReturn(CLIENT_PACKAGE);
+ when(mPackageManager.getNameForUid(CLIENT_UID2)).thenReturn(CLIENT_PACKAGE2);
+
+ mCarStatsService = new CarStatsService(mContext);
+ mDumpsysOutput = new StringWriter();
+ mDumpsysWriter = new PrintWriter(mDumpsysOutput);
+ }
+
+ @Test
+ public void testEmptyStats() {
+ mCarStatsService.dump(null, mDumpsysWriter, new String[0]);
+ assertEquals(
+ "uid,packageName,attempts,connected,disconnected,terminated,errors\n"
+ + "\nuid,layerType,layerChannel,layerVersion,"
+ + "txBytes,txPackets,rxBytes,rxPackets,droppedBytes,droppedPackets\n",
+ mDumpsysOutput.toString());
+ }
+
+ @Test
+ public void testLogConnectionState_Connecting() {
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logConnectionState(ConnectionState.CONNECTING);
+ validateConnectionStats("10101,test.package,1,0,0,0,0");
+ }
+
+ @Test
+ public void testLogConnectionState_Connected() {
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logConnectionState(ConnectionState.CONNECTED);
+ validateConnectionStats("10101,test.package,0,1,0,0,0");
+ }
+
+ @Test
+ public void testLogConnectionState_Disconnected() {
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logConnectionState(ConnectionState.DISCONNECTED);
+ validateConnectionStats("10101,test.package,0,0,1,0,0");
+ }
+
+ @Test
+ public void testLogConnectionState_Terminated() {
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logConnectionState(ConnectionState.TERMINATED);
+ validateConnectionStats("10101,test.package,0,0,0,1,0");
+ }
+
+ @Test
+ public void testLogConnectionState_ConnectionError() {
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logConnectionState(ConnectionState.CONNECTION_ERROR);
+ validateConnectionStats("10101,test.package,0,0,0,0,1");
+ }
+
+ @Test
+ public void testLogConnectionState_UnknownUID() {
+ mCarStatsService.getVmsClientLog(-1)
+ .logConnectionState(ConnectionState.CONNECTING);
+ testEmptyStats();
+ }
+
+ @Test
+ public void testLogConnectionState_MultipleClients_MultipleStates() {
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logConnectionState(ConnectionState.CONNECTING);
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logConnectionState(ConnectionState.CONNECTED);
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logConnectionState(ConnectionState.DISCONNECTED);
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logConnectionState(ConnectionState.CONNECTED);
+
+ mCarStatsService.getVmsClientLog(CLIENT_UID2)
+ .logConnectionState(ConnectionState.CONNECTING);
+ mCarStatsService.getVmsClientLog(CLIENT_UID2)
+ .logConnectionState(ConnectionState.CONNECTED);
+ mCarStatsService.getVmsClientLog(CLIENT_UID2)
+ .logConnectionState(ConnectionState.TERMINATED);
+ mCarStatsService.getVmsClientLog(CLIENT_UID2)
+ .logConnectionState(ConnectionState.CONNECTING);
+ mCarStatsService.getVmsClientLog(CLIENT_UID2)
+ .logConnectionState(ConnectionState.CONNECTION_ERROR);
+ validateConnectionStats(
+ "10101,test.package,1,2,1,0,0\n"
+ + "10102,test.package2,2,1,0,1,1");
+ }
+
+ @Test
+ public void testLogPacketSent() {
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketSent(LAYER, 5);
+ validateClientStats("10101,1,2,3,5,1,0,0,0,0");
+ }
+
+ @Test
+ public void testLogPacketSent_MultiplePackets() {
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketSent(LAYER, 3);
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketSent(LAYER, 2);
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketSent(LAYER, 1);
+
+ validateClientStats("10101,1,2,3,6,3,0,0,0,0");
+ }
+
+ @Test
+ public void testLogPacketSent_MultipleLayers() {
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketSent(LAYER, 3);
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketSent(LAYER2, 2);
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketSent(LAYER3, 1);
+
+ validateClientStats(
+ "10101,1,2,3,3,1,0,0,0,0\n"
+ + "10101,2,3,4,2,1,0,0,0,0\n"
+ + "10101,3,4,5,1,1,0,0,0,0");
+ }
+
+ @Test
+ public void testLogPacketSent_MultipleClients() {
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketSent(LAYER, 3);
+ mCarStatsService.getVmsClientLog(CLIENT_UID2)
+ .logPacketSent(LAYER, 2);
+ mCarStatsService.getVmsClientLog(CLIENT_UID2)
+ .logPacketSent(LAYER2, 1);
+
+ validateDumpsys(
+ "10101,test.package,0,0,0,0,0\n"
+ + "10102,test.package2,0,0,0,0,0\n",
+ "10101,1,2,3,3,1,0,0,0,0\n"
+ + "10102,1,2,3,2,1,0,0,0,0\n"
+ + "10102,2,3,4,1,1,0,0,0,0\n");
+ }
+
+ @Test
+ public void testLogPacketReceived() {
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketReceived(LAYER, 5);
+ validateClientStats("10101,1,2,3,0,0,5,1,0,0");
+ }
+
+ @Test
+ public void testLogPacketReceived_MultiplePackets() {
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketReceived(LAYER, 3);
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketReceived(LAYER, 2);
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketReceived(LAYER, 1);
+
+ validateClientStats("10101,1,2,3,0,0,6,3,0,0");
+ }
+
+ @Test
+ public void testLogPacketReceived_MultipleLayers() {
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketReceived(LAYER, 3);
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketReceived(LAYER2, 2);
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketReceived(LAYER3, 1);
+
+ validateClientStats(
+ "10101,1,2,3,0,0,3,1,0,0\n"
+ + "10101,2,3,4,0,0,2,1,0,0\n"
+ + "10101,3,4,5,0,0,1,1,0,0");
+ }
+
+ @Test
+ public void testLogPacketReceived_MultipleClients() {
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketReceived(LAYER, 3);
+ mCarStatsService.getVmsClientLog(CLIENT_UID2)
+ .logPacketReceived(LAYER, 2);
+ mCarStatsService.getVmsClientLog(CLIENT_UID2)
+ .logPacketReceived(LAYER2, 1);
+
+ validateDumpsys(
+ "10101,test.package,0,0,0,0,0\n"
+ + "10102,test.package2,0,0,0,0,0\n",
+ "10101,1,2,3,0,0,3,1,0,0\n"
+ + "10102,1,2,3,0,0,2,1,0,0\n"
+ + "10102,2,3,4,0,0,1,1,0,0\n");
+ }
+
+ @Test
+ public void testLogPacketDropped() {
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketDropped(LAYER, 5);
+ validateClientStats("10101,1,2,3,0,0,0,0,5,1");
+ }
+
+ @Test
+ public void testLogPacketDropped_MultiplePackets() {
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketDropped(LAYER, 3);
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketDropped(LAYER, 2);
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketDropped(LAYER, 1);
+
+ validateClientStats("10101,1,2,3,0,0,0,0,6,3");
+ }
+
+ @Test
+ public void testLogPacketDropped_MultipleLayers() {
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketDropped(LAYER, 3);
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketDropped(LAYER2, 2);
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketDropped(LAYER3, 1);
+
+ validateClientStats(
+ "10101,1,2,3,0,0,0,0,3,1\n"
+ + "10101,2,3,4,0,0,0,0,2,1\n"
+ + "10101,3,4,5,0,0,0,0,1,1");
+ }
+
+ @Test
+ public void testLogPacketDropped_MultipleClients() {
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketDropped(LAYER, 3);
+ mCarStatsService.getVmsClientLog(CLIENT_UID2)
+ .logPacketDropped(LAYER, 2);
+ mCarStatsService.getVmsClientLog(CLIENT_UID2)
+ .logPacketDropped(LAYER2, 1);
+
+ validateDumpsys(
+ "10101,test.package,0,0,0,0,0\n"
+ + "10102,test.package2,0,0,0,0,0\n",
+ "10101,1,2,3,0,0,0,0,3,1\n"
+ + "10102,1,2,3,0,0,0,0,2,1\n"
+ + "10102,2,3,4,0,0,0,0,1,1\n");
+ }
+
+ @Test
+ public void testLogPackets_MultipleClients_MultipleLayers_MultipleOperations() {
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketSent(LAYER, 3);
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketReceived(LAYER, 2);
+ mCarStatsService.getVmsClientLog(CLIENT_UID)
+ .logPacketDropped(LAYER, 1);
+ mCarStatsService.getVmsClientLog(CLIENT_UID2)
+ .logPacketReceived(LAYER, 2);
+ mCarStatsService.getVmsClientLog(CLIENT_UID2)
+ .logPacketReceived(LAYER, 2);
+ mCarStatsService.getVmsClientLog(CLIENT_UID2)
+ .logPacketReceived(LAYER, 2);
+ mCarStatsService.getVmsClientLog(CLIENT_UID2)
+ .logPacketSent(LAYER2, 2);
+ mCarStatsService.getVmsClientLog(-1)
+ .logPacketDropped(LAYER2, 12);
+
+
+ validateDumpsys(
+ "10101,test.package,0,0,0,0,0\n"
+ + "10102,test.package2,0,0,0,0,0\n",
+ "-1,2,3,4,0,0,0,0,12,1\n"
+ + "10101,1,2,3,3,1,2,1,1,1\n"
+ + "10102,1,2,3,0,0,6,3,0,0\n"
+ + "10102,2,3,4,2,1,0,0,0,0\n");
+ }
+
+
+ private void validateConnectionStats(String vmsConnectionStats) {
+ validateDumpsys(vmsConnectionStats + "\n", "");
+ }
+
+ private void validateClientStats(String vmsClientStats) {
+ validateDumpsys(
+ "10101,test.package,0,0,0,0,0\n",
+ vmsClientStats + "\n");
+ }
+
+ private void validateDumpsys(String vmsConnectionStats, String vmsClientStats) {
+ mCarStatsService.dump(null, mDumpsysWriter, new String[0]);
+ assertEquals(
+ "uid,packageName,attempts,connected,disconnected,terminated,errors\n"
+ + vmsConnectionStats
+ + "\n"
+ + "uid,layerType,layerChannel,layerVersion,"
+ + "txBytes,txPackets,rxBytes,rxPackets,droppedBytes,droppedPackets\n"
+ + vmsClientStats,
+ mDumpsysOutput.toString());
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/vms/VmsClientManagerTest.java b/tests/carservice_unit_test/src/com/android/car/vms/VmsClientManagerTest.java
index dc89528..87bf8b1 100644
--- a/tests/carservice_unit_test/src/com/android/car/vms/VmsClientManagerTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/vms/VmsClientManagerTest.java
@@ -50,12 +50,14 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.content.res.Resources;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
+import android.os.Process;
import android.os.UserHandle;
import android.os.UserManager;
@@ -63,6 +65,9 @@
import com.android.car.VmsPublisherService;
import com.android.car.hal.VmsHalService;
+import com.android.car.stats.CarStatsService;
+import com.android.car.stats.VmsClientLog;
+import com.android.car.stats.VmsClientLog.ConnectionState;
import com.android.car.user.CarUserService;
import com.android.car.user.CarUserService.UserCallback;
@@ -98,6 +103,11 @@
private static final String HAL_CLIENT_NAME = "HalClient";
private static final String UNKNOWN_PACKAGE = "UnknownPackage";
+ private static final int TEST_APP_ID = 12345;
+ private static final int TEST_SYSTEM_UID = 12345;
+ private static final int TEST_USER_UID = 1012345;
+ private static final int TEST_USER_UID_U11 = 1112345;
+
private static final long MILLIS_BEFORE_REBIND = 100;
@Mock
@@ -106,7 +116,8 @@
private PackageManager mPackageManager;
@Mock
private Resources mResources;
-
+ @Mock
+ private CarStatsService mStatsService;
@Mock
private UserManager mUserManager;
@Mock
@@ -145,11 +156,23 @@
private UserCallback mUserCallback;
private MockitoSession mSession;
+ @Mock
+ private VmsClientLog mSystemClientLog;
+ @Mock
+ private VmsClientLog mUserClientLog;
+ @Mock
+ private VmsClientLog mUserClientLog2;
+ @Mock
+ private VmsClientLog mHalClientLog;
+
private VmsClientManager mClientManager;
private int mForegroundUserId;
private int mCallingAppUid;
+ private ServiceInfo mSystemServiceInfo;
+ private ServiceInfo mUserServiceInfo;
+
@Before
public void setUp() throws Exception {
mSession = mockitoSession()
@@ -159,9 +182,24 @@
.startMocking();
resetContext();
- ServiceInfo serviceInfo = new ServiceInfo();
- serviceInfo.permission = Car.PERMISSION_BIND_VMS_CLIENT;
- when(mPackageManager.getServiceInfo(any(), anyInt())).thenReturn(serviceInfo);
+ mSystemServiceInfo = new ServiceInfo();
+ mSystemServiceInfo.permission = Car.PERMISSION_BIND_VMS_CLIENT;
+ mSystemServiceInfo.applicationInfo = new ApplicationInfo();
+ mSystemServiceInfo.applicationInfo.uid = TEST_APP_ID;
+ when(mPackageManager.getServiceInfo(eq(SYSTEM_CLIENT_COMPONENT), anyInt()))
+ .thenReturn(mSystemServiceInfo);
+ when(mStatsService.getVmsClientLog(TEST_SYSTEM_UID)).thenReturn(mSystemClientLog);
+
+ mUserServiceInfo = new ServiceInfo();
+ mUserServiceInfo.permission = Car.PERMISSION_BIND_VMS_CLIENT;
+ mUserServiceInfo.applicationInfo = new ApplicationInfo();
+ mUserServiceInfo.applicationInfo.uid = TEST_APP_ID;
+ when(mPackageManager.getServiceInfo(eq(USER_CLIENT_COMPONENT), anyInt()))
+ .thenReturn(mUserServiceInfo);
+ when(mStatsService.getVmsClientLog(TEST_USER_UID)).thenReturn(mUserClientLog);
+ when(mStatsService.getVmsClientLog(TEST_USER_UID_U11)).thenReturn(mUserClientLog2);
+
+ when(mStatsService.getVmsClientLog(Process.myUid())).thenReturn(mHalClientLog);
when(mResources.getInteger(
com.android.car.R.integer.millisecondsBeforeRebindToVmsPublisher)).thenReturn(
@@ -179,8 +217,8 @@
mForegroundUserId = USER_ID;
mCallingAppUid = UserHandle.getUid(USER_ID, 0);
- mClientManager = new VmsClientManager(mContext, mBrokerService, mUserService, mHal,
- mHandler, () -> mCallingAppUid);
+ mClientManager = new VmsClientManager(mContext, mStatsService, mUserService,
+ mBrokerService, mHal, mHandler, () -> mCallingAppUid);
verify(mHal).setClientManager(mClientManager);
mClientManager.setPublisherService(mPublisherService);
@@ -196,6 +234,7 @@
verify(mContext, atLeast(0)).getResources();
verify(mContext, atLeast(0)).getPackageManager();
verifyNoMoreInteractions(mContext, mBrokerService, mHal, mPublisherService, mHandler);
+ verifyNoMoreInteractions(mSystemClientLog, mUserClientLog, mUserClientLog2, mHalClientLog);
mSession.finishMocking();
}
@@ -238,14 +277,12 @@
@Test
public void testSystemUserUnlocked_WrongPermission() throws Exception {
- ServiceInfo serviceInfo = new ServiceInfo();
- serviceInfo.permission = Car.PERMISSION_VMS_PUBLISHER;
- when(mPackageManager.getServiceInfo(eq(SYSTEM_CLIENT_COMPONENT), anyInt()))
- .thenReturn(serviceInfo);
+ mSystemServiceInfo.permission = Car.PERMISSION_VMS_PUBLISHER;
notifySystemUserUnlocked();
// Process will not be bound
verifySystemBind(0);
+ verify(mSystemClientLog).logConnectionState(ConnectionState.CONNECTION_ERROR);
}
@Test
@@ -256,6 +293,7 @@
// Failure state will trigger another attempt on event
verifySystemBind(2);
+ verify(mSystemClientLog, times(2)).logConnectionState(ConnectionState.CONNECTION_ERROR);
}
@Test
@@ -267,6 +305,7 @@
// Failure state will trigger another attempt on event
verifySystemBind(2);
+ verify(mSystemClientLog, times(2)).logConnectionState(ConnectionState.CONNECTION_ERROR);
}
@Test
@@ -298,14 +337,12 @@
@Test
public void testUserUnlocked_WrongPermission() throws Exception {
- ServiceInfo serviceInfo = new ServiceInfo();
- serviceInfo.permission = Car.PERMISSION_VMS_PUBLISHER;
- when(mPackageManager.getServiceInfo(eq(USER_CLIENT_COMPONENT), anyInt()))
- .thenReturn(serviceInfo);
+ mUserServiceInfo.permission = Car.PERMISSION_VMS_PUBLISHER;
notifyUserUnlocked(USER_ID, true);
// Process will not be bound
verifyUserBind(0);
+ verify(mUserClientLog).logConnectionState(ConnectionState.CONNECTION_ERROR);
}
@Test
@@ -317,6 +354,7 @@
// Failure state will trigger another attempt
verifyUserBind(2);
+ verify(mUserClientLog, times(2)).logConnectionState(ConnectionState.CONNECTION_ERROR);
}
@Test
@@ -328,6 +366,7 @@
// Failure state will trigger another attempt
verifyUserBind(2);
+ verify(mUserClientLog, times(2)).logConnectionState(ConnectionState.CONNECTION_ERROR);
}
@Test
@@ -339,6 +378,7 @@
// Failure state will trigger another attempt
verifyUserBind(2);
+ verify(mUserClientLog, times(2)).logConnectionState(ConnectionState.CONNECTION_ERROR);
}
@Test
@@ -347,6 +387,7 @@
.thenReturn(false);
notifySystemUserUnlocked();
verifySystemBind(1);
+ verify(mSystemClientLog).logConnectionState(ConnectionState.CONNECTION_ERROR);
resetContext();
when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), eq(UserHandle.SYSTEM)))
@@ -362,6 +403,7 @@
.thenReturn(false);
notifySystemUserUnlocked();
verifySystemBind(1);
+ verify(mSystemClientLog).logConnectionState(ConnectionState.CONNECTION_ERROR);
resetContext();
when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), eq(UserHandle.SYSTEM)))
@@ -370,6 +412,7 @@
notifyUserUnlocked(USER_ID, true);
verifySystemBind(2); // Failure state will trigger another attempt
+ verify(mSystemClientLog, times(2)).logConnectionState(ConnectionState.CONNECTION_ERROR);
verifyUserBind(1);
}
@@ -379,6 +422,7 @@
.thenThrow(new SecurityException());
notifySystemUserUnlocked();
verifySystemBind(1);
+ verify(mSystemClientLog).logConnectionState(ConnectionState.CONNECTION_ERROR);
resetContext();
when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), eq(UserHandle.SYSTEM)))
@@ -387,6 +431,7 @@
notifyUserUnlocked(USER_ID, true);
verifySystemBind(2); // Failure state will trigger another attempt
+ verify(mSystemClientLog, times(2)).logConnectionState(ConnectionState.CONNECTION_ERROR);
verifyUserBind(1);
}
@@ -429,6 +474,7 @@
public void testOnSystemServiceConnected() {
IBinder binder = bindSystemClient();
verifyOnClientConnected(SYSTEM_CLIENT_NAME, binder);
+ verify(mSystemClientLog).logConnectionState(ConnectionState.CONNECTED);
}
private IBinder bindSystemClient() {
@@ -446,6 +492,7 @@
public void testOnUserServiceConnected() {
IBinder binder = bindUserClient();
verifyOnClientConnected(USER_CLIENT_NAME, binder);
+ verify(mUserClientLog).logConnectionState(ConnectionState.CONNECTED);
}
private IBinder bindUserClient() {
@@ -467,10 +514,12 @@
ServiceConnection connection = mConnectionCaptor.getValue();
connection.onServiceConnected(null, createPublisherBinder());
+ verify(mSystemClientLog).logConnectionState(ConnectionState.CONNECTED);
reset(mPublisherService);
connection.onServiceDisconnected(null);
verify(mPublisherService).onClientDisconnected(eq(SYSTEM_CLIENT_NAME));
+ verify(mSystemClientLog).logConnectionState(ConnectionState.DISCONNECTED);
verifyAndRunRebindTask();
verify(mContext).unbindService(connection);
@@ -487,14 +536,17 @@
IBinder binder = createPublisherBinder();
connection.onServiceConnected(null, binder);
verifyOnClientConnected(SYSTEM_CLIENT_NAME, binder);
- reset(mPublisherService);
+ verify(mSystemClientLog).logConnectionState(ConnectionState.CONNECTED);
+ reset(mPublisherService, mSystemClientLog);
connection.onServiceDisconnected(null);
verify(mPublisherService).onClientDisconnected(eq(SYSTEM_CLIENT_NAME));
+ verify(mSystemClientLog).logConnectionState(ConnectionState.DISCONNECTED);
binder = createPublisherBinder();
connection.onServiceConnected(null, binder);
verifyOnClientConnected(SYSTEM_CLIENT_NAME, binder);
+ verify(mSystemClientLog).logConnectionState(ConnectionState.CONNECTED);
verifyAndRunRebindTask();
// No more interactions (verified by tearDown)
@@ -509,10 +561,12 @@
ServiceConnection connection = mConnectionCaptor.getValue();
connection.onServiceConnected(null, createPublisherBinder());
+ verify(mSystemClientLog).logConnectionState(ConnectionState.CONNECTED);
reset(mPublisherService);
connection.onBindingDied(null);
verify(mPublisherService).onClientDisconnected(eq(SYSTEM_CLIENT_NAME));
+ verify(mSystemClientLog).logConnectionState(ConnectionState.DISCONNECTED);
verifyAndRunRebindTask();
verify(mContext).unbindService(connection);
@@ -543,10 +597,12 @@
ServiceConnection connection = mConnectionCaptor.getValue();
connection.onServiceConnected(null, createPublisherBinder());
+ verify(mUserClientLog).logConnectionState(ConnectionState.CONNECTED);
reset(mPublisherService);
connection.onServiceDisconnected(null);
verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
+ verify(mUserClientLog).logConnectionState(ConnectionState.DISCONNECTED);
verifyAndRunRebindTask();
verify(mContext).unbindService(connection);
@@ -563,14 +619,17 @@
IBinder binder = createPublisherBinder();
connection.onServiceConnected(null, binder);
verifyOnClientConnected(USER_CLIENT_NAME, binder);
- reset(mPublisherService);
+ verify(mUserClientLog).logConnectionState(ConnectionState.CONNECTED);
+ reset(mPublisherService, mUserClientLog);
connection.onServiceDisconnected(null);
verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
+ verify(mUserClientLog).logConnectionState(ConnectionState.DISCONNECTED);
binder = createPublisherBinder();
connection.onServiceConnected(null, binder);
verifyOnClientConnected(USER_CLIENT_NAME, binder);
+ verify(mUserClientLog).logConnectionState(ConnectionState.CONNECTED);
verifyAndRunRebindTask();
// No more interactions (verified by tearDown)
@@ -584,10 +643,12 @@
ServiceConnection connection = mConnectionCaptor.getValue();
connection.onServiceConnected(null, createPublisherBinder());
+ verify(mUserClientLog).logConnectionState(ConnectionState.CONNECTED);
reset(mPublisherService);
connection.onBindingDied(null);
verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
+ verify(mUserClientLog).logConnectionState(ConnectionState.DISCONNECTED);
verifyAndRunRebindTask();
verify(mContext).unbindService(connection);
@@ -614,15 +675,18 @@
public void testOnUserSwitched_UserChange() {
notifyUserUnlocked(USER_ID, true);
verifyUserBind(1);
+ resetContext();
+
ServiceConnection connection = mConnectionCaptor.getValue();
connection.onServiceConnected(null, createPublisherBinder());
- resetContext();
+ verify(mUserClientLog).logConnectionState(ConnectionState.CONNECTED);
reset(mPublisherService);
notifyUserSwitched(USER_ID_U11, true);
verify(mContext).unbindService(connection);
verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
+ verify(mUserClientLog).logConnectionState(ConnectionState.TERMINATED);
verifyUserBind(1);
}
@@ -639,6 +703,7 @@
verify(mContext).unbindService(connection);
verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
+ verify(mUserClientLog).logConnectionState(ConnectionState.TERMINATED);
verifyUserBind(0);
}
@@ -655,6 +720,7 @@
verify(mContext).unbindService(connection);
verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
+ verify(mUserClientLog).logConnectionState(ConnectionState.TERMINATED);
verifyUserBind(0);
}
@@ -697,6 +763,7 @@
verify(mContext).unbindService(connection);
verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
+ verify(mUserClientLog).logConnectionState(ConnectionState.TERMINATED);
verifyUserBind(1);
}
@@ -715,6 +782,7 @@
verify(mContext).unbindService(connection);
verify(mPublisherService).onClientDisconnected(eq(USER_CLIENT_NAME));
+ verify(mUserClientLog).logConnectionState(ConnectionState.TERMINATED);
// User processes will not be bound for system user
verifyUserBind(0);
}
@@ -736,7 +804,9 @@
public void testAddSubscriber() {
mClientManager.addSubscriber(mSubscriberClient1);
assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+ assertEquals(mCallingAppUid, mClientManager.getSubscriberUid(mSubscriberClient1));
assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient2));
+ assertEquals(-1, mClientManager.getSubscriberUid(mSubscriberClient2));
}
@Test
@@ -746,7 +816,9 @@
mClientManager.addSubscriber(mSubscriberClient1);
assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+ assertEquals(mCallingAppUid, mClientManager.getSubscriberUid(mSubscriberClient1));
assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient2));
+ assertEquals(-1, mClientManager.getSubscriberUid(mSubscriberClient2));
}
@Test
@@ -760,6 +832,7 @@
// expected
}
assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+ assertEquals(-1, mClientManager.getSubscriberUid(mSubscriberClient1));
}
@Test
@@ -768,7 +841,9 @@
mClientManager.addSubscriber(mSubscriberClient1);
verify(mPackageManager, atMost(1)).getNameForUid(anyInt());
assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+ assertEquals(mCallingAppUid, mClientManager.getSubscriberUid(mSubscriberClient1));
assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient2));
+ assertEquals(-1, mClientManager.getSubscriberUid(mSubscriberClient2));
}
@Test
@@ -777,11 +852,14 @@
mClientManager.addSubscriber(mSubscriberClient2);
verify(mPackageManager, atMost(2)).getNameForUid(anyInt());
assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+ assertEquals(mCallingAppUid, mClientManager.getSubscriberUid(mSubscriberClient1));
assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient2));
+ assertEquals(mCallingAppUid, mClientManager.getSubscriberUid(mSubscriberClient2));
}
@Test
public void testAddSubscriber_MultipleClients_ForegroundAndSystemUsers_SamePackage() {
+ int clientUid1 = mCallingAppUid;
mClientManager.addSubscriber(mSubscriberClient1);
mCallingAppUid = UserHandle.getUid(UserHandle.USER_SYSTEM, 0);
@@ -790,12 +868,15 @@
verify(mPackageManager, atMost(2)).getNameForUid(anyInt());
assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+ assertEquals(clientUid1, mClientManager.getSubscriberUid(mSubscriberClient1));
assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient2));
+ assertEquals(mCallingAppUid, mClientManager.getSubscriberUid(mSubscriberClient2));
}
@Test
public void testAddSubscriber_MultipleClients_MultiplePackages() {
+ int clientUid1 = mCallingAppUid;
mClientManager.addSubscriber(mSubscriberClient1);
mCallingAppUid = UserHandle.getUid(mForegroundUserId, 1);
@@ -804,7 +885,9 @@
verify(mPackageManager, times(2)).getNameForUid(anyInt());
assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+ assertEquals(clientUid1, mClientManager.getSubscriberUid(mSubscriberClient1));
assertEquals("test.package2", mClientManager.getPackageName(mSubscriberClient2));
+ assertEquals(mCallingAppUid, mClientManager.getSubscriberUid(mSubscriberClient2));
}
@Test
@@ -813,12 +896,14 @@
mClientManager.removeSubscriber(mSubscriberClient1);
verify(mBrokerService).removeDeadSubscriber(mSubscriberClient1);
assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+ assertEquals(-1, mClientManager.getSubscriberUid(mSubscriberClient1));
}
@Test
public void testRemoveSubscriber_NotRegistered() {
mClientManager.removeSubscriber(mSubscriberClient1);
assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+ assertEquals(-1, mClientManager.getSubscriberUid(mSubscriberClient1));
}
@Test
@@ -830,6 +915,7 @@
verify(mBrokerService).removeDeadSubscriber(mSubscriberClient1);
assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+ assertEquals(-1, mClientManager.getSubscriberUid(mSubscriberClient1));
}
@Test
@@ -840,6 +926,7 @@
mClientManager.switchUser();
verify(mBrokerService).removeDeadSubscriber(mSubscriberClient1);
assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+ assertEquals(-1, mClientManager.getSubscriberUid(mSubscriberClient1));
assertTrue(mClientManager.getAllSubscribers().isEmpty());
}
@@ -855,7 +942,10 @@
when(mPackageManager.getNameForUid(mCallingAppUid)).thenReturn(TEST_PACKAGE);
mClientManager.addSubscriber(mSubscriberClient2);
+ assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(mSubscriberClient1));
+ assertEquals(-1, mClientManager.getSubscriberUid(mSubscriberClient1));
assertEquals(TEST_PACKAGE, mClientManager.getPackageName(mSubscriberClient2));
+ assertEquals(mCallingAppUid, mClientManager.getSubscriberUid(mSubscriberClient2));
assertFalse(mClientManager.getAllSubscribers().contains(mSubscriberClient1));
assertTrue(mClientManager.getAllSubscribers().contains(mSubscriberClient2));
}
@@ -883,6 +973,7 @@
IVmsPublisherClient publisherClient = createPublisherClient();
IVmsSubscriberClient subscriberClient = createSubscriberClient();
mClientManager.onHalConnected(publisherClient, subscriberClient);
+ verify(mHalClientLog).logConnectionState(ConnectionState.CONNECTED);
reset(mPublisherService);
mForegroundUserId = USER_ID_U11;
@@ -898,6 +989,7 @@
IVmsSubscriberClient subscriberClient = createSubscriberClient();
mClientManager.onHalConnected(publisherClient, subscriberClient);
verify(mPublisherService).onClientConnected(eq(HAL_CLIENT_NAME), same(publisherClient));
+ verify(mHalClientLog).logConnectionState(ConnectionState.CONNECTED);
assertTrue(mClientManager.getAllSubscribers().contains(subscriberClient));
assertEquals(HAL_CLIENT_NAME, mClientManager.getPackageName(subscriberClient));
}
@@ -910,6 +1002,7 @@
mClientManager.onHalConnected(publisherClient, subscriberClient);
verify(mPublisherService).onClientConnected(eq(HAL_CLIENT_NAME), same(publisherClient));
+ verify(mHalClientLog).logConnectionState(ConnectionState.CONNECTED);
assertTrue(mClientManager.getAllSubscribers().contains(subscriberClient));
assertEquals(HAL_CLIENT_NAME, mClientManager.getPackageName(subscriberClient));
}
@@ -919,11 +1012,13 @@
IVmsPublisherClient publisherClient = createPublisherClient();
IVmsSubscriberClient subscriberClient = createSubscriberClient();
mClientManager.onHalConnected(publisherClient, subscriberClient);
+ verify(mHalClientLog).logConnectionState(ConnectionState.CONNECTED);
reset(mPublisherService);
mClientManager.onHalDisconnected();
verify(mPublisherService).onClientDisconnected(eq(HAL_CLIENT_NAME));
verify(mBrokerService).removeDeadSubscriber(eq(subscriberClient));
+ verify(mHalClientLog).logConnectionState(ConnectionState.DISCONNECTED);
assertFalse(mClientManager.getAllSubscribers().contains(subscriberClient));
assertEquals(UNKNOWN_PACKAGE, mClientManager.getPackageName(subscriberClient));
}
@@ -936,7 +1031,7 @@
}
private void resetContext() {
- reset(mContext);
+ reset(mContext, mSystemClientLog, mUserClientLog);
when(mContext.getPackageManager()).thenReturn(mPackageManager);
when(mContext.bindServiceAsUser(any(), any(), anyInt(), any(), any())).thenReturn(true);
when(mContext.getResources()).thenReturn(mResources);
@@ -966,10 +1061,16 @@
}
private void verifySystemBind(int times) {
+ verify(mSystemClientLog, times(times)).logConnectionState(ConnectionState.CONNECTING);
verifyBind(times, SYSTEM_CLIENT_COMPONENT, UserHandle.SYSTEM);
}
private void verifyUserBind(int times) {
+ if (mForegroundUserId == USER_ID) {
+ verify(mUserClientLog, times(times)).logConnectionState(ConnectionState.CONNECTING);
+ } else if (mForegroundUserId == USER_ID_U11) {
+ verify(mUserClientLog2, times(times)).logConnectionState(ConnectionState.CONNECTING);
+ }
verifyBind(times, USER_CLIENT_COMPONENT, UserHandle.of(mForegroundUserId));
}
diff --git a/tests/fixed_activity_mode_test/fixed_activity_mode_test.sh b/tests/fixed_activity_mode_test/fixed_activity_mode_test.sh
new file mode 100755
index 0000000..8beb1f4
--- /dev/null
+++ b/tests/fixed_activity_mode_test/fixed_activity_mode_test.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+if [ -z "$ANDROID_PRODUCT_OUT" ]; then
+ echo "ANDROID_PRODUCT_OUT not set"
+ exit
+fi
+DISP_ID=1
+if [[ $# -eq 1 ]]; then
+ echo "$1"
+ DISP_ID=$1
+fi
+echo "Use display:$DISP_ID"
+
+adb root
+# Check always crashing one
+echo "Start AlwaysCrashingActivity in fixed mode"
+adb shell dumpsys car_service start-fixed-activity-mode $DISP_ID com.google.android.car.kitchensink com.google.android.car.kitchensink.AlwaysCrashingActivity
+sleep 1
+read -p "AlwaysCrashingAvtivity should not be tried any more. Press Enter"
+# package update
+echo "Will try package update:"
+adb install -r -g $ANDROID_PRODUCT_OUT/system/priv-app/EmbeddedKitchenSinkApp/EmbeddedKitchenSinkApp.apk
+read -p "AlwaysCrashingAvtivity should have been retried. Press Enter"
+# suspend-resume
+echo "Check retry for suspend - resume"
+adb shell setprop android.car.garagemodeduration 1
+adb shell dumpsys car_service suspend
+adb shell dumpsys car_service resume
+read -p "AlwaysCrashingAvtivity should have been retried. Press Enter"
+# starting other Activity
+echo "Switch to no crash Activity"
+adb shell dumpsys car_service start-fixed-activity-mode $DISP_ID com.google.android.car.kitchensink com.google.android.car.kitchensink.NoCrashActivity
+read -p "NoCrashAvtivity should have been shown. Press Enter"
+# stating other non-fixed Activity
+adb shell am start-activity --display $DISP_ID -n com.google.android.car.kitchensink/.EmptyActivity
+read -p "NoCrashAvtivity should be shown after showing EmptyActivity. Press Enter"
+# package update
+echo "Will try package update:"
+adb install -r -g $ANDROID_PRODUCT_OUT/system/priv-app/EmbeddedKitchenSinkApp/EmbeddedKitchenSinkApp.apk
+read -p "NoCrashActivity should be shown. Press Enter"
+# stop the mode
+echo "Stop fixed activity mode"
+adb shell dumpsys car_service stop-fixed-activity-mode $DISP_ID
+adb shell am start-activity --display $DISP_ID -n com.google.android.car.kitchensink/.EmptyActivity
+read -p "EmptyActivity should be shown. Press Enter"