Merge "Revert "Include CarManagedProvisioning in car.mk"" into sc-v2-dev
diff --git a/car-internal-lib/src/com/android/car/internal/ICarServiceHelper.aidl b/car-internal-lib/src/com/android/car/internal/ICarServiceHelper.aidl
index 22148eb..cabbc07 100644
--- a/car-internal-lib/src/com/android/car/internal/ICarServiceHelper.aidl
+++ b/car-internal-lib/src/com/android/car/internal/ICarServiceHelper.aidl
@@ -55,4 +55,10 @@
* Creates the given user, even when it's disallowed by DevicePolicyManager.
*/
UserInfo createUserEvenWhenDisallowed(String name, String userType, int flags);
+
+ /**
+ * Designates the given {@code activity} to be launched in {@code TaskDisplayArea} of
+ * {@code featureId} in the display of {@code displayId}.
+ */
+ int setPersistentActivity(in ComponentName activity, int displayId, int featureId);
}
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index 23ed24d..6647a32 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -32,6 +32,7 @@
import android.car.admin.CarDevicePolicyManager;
import android.car.annotation.MandatoryFeature;
import android.car.annotation.OptionalFeature;
+import android.car.app.CarActivityManager;
import android.car.cluster.CarInstrumentClusterManager;
import android.car.cluster.ClusterActivityState;
import android.car.cluster.ClusterHomeManager;
@@ -373,6 +374,14 @@
@OptionalFeature
public static final String CAR_TELEMETRY_SERVICE = "car_telemetry_service";
+ /**
+ * Service name for {@link android.car.app.CarActivityManager}
+ *
+ * @hide
+ */
+ @MandatoryFeature
+ public static final String CAR_ACTIVITY_SERVICE = "car_activity_service";
+
/** Permission necessary to access car's mileage information.
* @hide
*/
@@ -868,6 +877,14 @@
public static final String PERMISSION_COLLECT_CAR_WATCHDOG_METRICS =
"android.car.permission.COLLECT_CAR_WATCHDOG_METRICS";
+ /**
+ * Permission necessary to control launching applications in Car.
+ *
+ * @hide
+ */
+ public static final String PERMISSION_CONTROL_CAR_APP_LAUNCH =
+ "android.car.permission.CONTROL_CAR_APP_LAUNCH";
+
/** @hide */
@IntDef({CONNECTION_TYPE_EMBEDDED})
@Retention(RetentionPolicy.SOURCE)
@@ -904,25 +921,6 @@
public static final String CAR_EXTRA_BROWSE_SERVICE_FOR_SESSION =
"android.media.session.BROWSE_SERVICE";
- /**
- * If some specific Activity should be launched on the designated TDA all the time, include this
- * integer extra in the first launching Intent and ActivityOption with the launch TDA.
- * If the value is {@link #LAUNCH_PERSISTENT_ADD}, CarLaunchParamsModifier will memorize
- * the Activity and the TDA pair, and assign the TDA in the following Intents for the Activity.
- * If there is any assigned Activity on the TDA, it'll be replaced with the new Activity.
- * If the value is {@Link #LAUNCH_PERSISTENT_DELETE}, it'll remove the stored info for the given
- * Activity.
- *
- * @hide
- */
- public static final String CAR_EXTRA_LAUNCH_PERSISTENT =
- "android.car.intent.extra.launchparams.PERSISTENT";
-
- /** @hide */
- public static final int LAUNCH_PERSISTENT_DELETE = 0;
- /** @hide */
- public static final int LAUNCH_PERSISTENT_ADD = 1;
-
/** @hide */
public static final String CAR_SERVICE_INTERFACE_NAME = CommonConstants.CAR_SERVICE_INTERFACE;
@@ -1919,6 +1917,9 @@
case CAR_TELEMETRY_SERVICE:
manager = new CarTelemetryManager(this, binder);
break;
+ case CAR_ACTIVITY_SERVICE:
+ manager = new CarActivityManager(this, binder);
+ break;
default:
// Experimental or non-existing
String className = null;
diff --git a/car-lib/src/android/car/app/CarActivityManager.java b/car-lib/src/android/car/app/CarActivityManager.java
new file mode 100644
index 0000000..dd521f9
--- /dev/null
+++ b/car-lib/src/android/car/app/CarActivityManager.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.app;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.app.Activity;
+import android.car.Car;
+import android.car.CarManagerBase;
+import android.car.user.CarUserManager;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceSpecificException;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * API to manage {@link android.app.Activity} in Car.
+ *
+ * @hide
+ */
+public final class CarActivityManager extends CarManagerBase {
+ private static final String TAG = CarUserManager.class.getSimpleName();
+
+ /** Indicates that the operation was successful. */
+ public static final int RESULT_SUCCESS = 0;
+ /** Indicates that the operation was failed with the unknown reason. */
+ public static final int RESULT_FAILURE = -1;
+ /**
+ * Indicates that the operation was failed because the requester isn't the current user or
+ * the system user
+ */
+ public static final int RESULT_INVALID_USER = -2;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(prefix = "RESULT_", value = {
+ RESULT_SUCCESS,
+ RESULT_FAILURE,
+ RESULT_INVALID_USER,
+ })
+ @Target({ElementType.TYPE_USE})
+ public @interface ResultTypeEnum {}
+
+ /**
+ * Internal error code for throwing {@link ActivityNotFoundException} from service.
+ * @hide
+ */
+ public static final int ERROR_CODE_ACTIVITY_NOT_FOUND = -101;
+
+ private final ICarActivityService mService;
+
+ /**
+ * @hide
+ */
+ public CarActivityManager(@NonNull Car car, @NonNull IBinder service) {
+ this(car, ICarActivityService.Stub.asInterface(service));
+ }
+
+ /**
+ * @hide
+ */
+ @VisibleForTesting
+ public CarActivityManager(@NonNull Car car, @NonNull ICarActivityService service) {
+ super(car);
+
+ mService = service;
+ }
+
+ /**
+ * Designates the given {@code activity} to be launched in {@code TaskDisplayArea} of
+ * {@code featureId} in the display of {@code displayId}.
+ * <p>Note: this will not affect the existing {@link Activity}.
+ * Note: You can map assign {@code Activity} to one {@code TaskDisplayArea} only. If
+ * you assign it to the multiple {@code TaskDisplayArea}s, then the last one wins.
+ * Note: The requester should be the current user or the system user, if not, the operation will
+ * be failed with {@code RESULT_INVALID_USER}.
+ *
+ * @param activity {@link Activity} to designate
+ * @param displayId {@code Display} where {@code TaskDisplayArea} is located in
+ * @param featureId {@code TaskDisplayArea} where {@link Activity} is launched in, if it is
+ * {@code DisplayAreaOrganizer.FEATURE_UNDEFINED}, then it'll remove the existing one.
+ * @return {@code ResultTypeEnum}. {@code RESULT_SUCCESS} if the operation is successful,
+ * otherwise, {@code RESULT_XXX} depending on the type of the error.
+ * @throws {@link IllegalArgumentException} if {@code displayId} or {@code featureId} is
+ * invalid. {@link ActivityNotFoundException} if {@code activity} is not found
+ * when it tries to remove.
+ */
+ @RequiresPermission(Car.PERMISSION_CONTROL_CAR_APP_LAUNCH)
+ @ResultTypeEnum
+ public int setPersistentActivity(
+ @NonNull ComponentName activity, int displayId, int featureId) {
+ try {
+ return mService.setPersistentActivity(activity, displayId, featureId);
+ } catch (IllegalArgumentException | IllegalStateException | SecurityException e) {
+ throw e;
+ } catch (ServiceSpecificException e) {
+ return handleServiceSpecificFromCarService(e);
+ } catch (RemoteException | RuntimeException e) {
+ return handleExceptionFromCarService(e, RESULT_FAILURE);
+ }
+ }
+
+ /** @hide */
+ @Override
+ protected void onCarDisconnected() {
+ // nothing to do
+ }
+
+ private int handleServiceSpecificFromCarService(ServiceSpecificException e)
+ throws ActivityNotFoundException {
+ if (e.errorCode == ERROR_CODE_ACTIVITY_NOT_FOUND) {
+ throw new ActivityNotFoundException(e.getMessage());
+ }
+ // don't know what this is
+ throw new IllegalStateException(e);
+ }
+}
diff --git a/car-lib/src/android/car/app/ICarActivityService.aidl b/car-lib/src/android/car/app/ICarActivityService.aidl
new file mode 100644
index 0000000..9f3e764
--- /dev/null
+++ b/car-lib/src/android/car/app/ICarActivityService.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.app;
+
+import android.content.ComponentName;
+
+/** @hide */
+interface ICarActivityService {
+ /**
+ * Designates the given {@code activity} to be launched in {@code TaskDisplayArea} of
+ * {@code featureId} in the display of {@code displayId}.
+ */
+ int setPersistentActivity(in ComponentName activity, int displayId, int featureId) = 0;
+}
+
diff --git a/car-maps-placeholder/res/values-te/strings.xml b/car-maps-placeholder/res/values-te/strings.xml
index 96c809e..3ecc950 100644
--- a/car-maps-placeholder/res/values-te/strings.xml
+++ b/car-maps-placeholder/res/values-te/strings.xml
@@ -17,5 +17,5 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="app_name" msgid="6575346965016311017">"Maps"</string>
- <string name="error_text" msgid="5575174711944349180">"మ్యాప్స్ అప్లికేషన్ ఇన్స్టాల్ చేయబడలేదు. దయచేసి మీ కారుని తయారు చేసినవారిని సంప్రదించండి"</string>
+ <string name="error_text" msgid="5575174711944349180">"మ్యాప్స్ అప్లికేషన్ ఏదీ ఇన్స్టాల్ చేయలేదు. దయచేసి మీ కారు తయారీదారు సంస్థను సంప్రదించండి."</string>
</resources>
diff --git a/car-test-lib/Android.bp b/car-test-lib/Android.bp
index bb72590..bad376e 100644
--- a/car-test-lib/Android.bp
+++ b/car-test-lib/Android.bp
@@ -48,5 +48,6 @@
"android.hardware.automotive.vehicle-V2.0-java",
"mockito-target-extended",
"compatibility-device-util-axt",
+ "android.test.mock",
],
}
diff --git a/car-test-lib/src/android/car/test/util/BroadcastingFakeContext.java b/car-test-lib/src/android/car/test/util/BroadcastingFakeContext.java
new file mode 100644
index 0000000..014e283
--- /dev/null
+++ b/car-test-lib/src/android/car/test/util/BroadcastingFakeContext.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.test.util;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.BroadcastReceiver;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.test.mock.MockContext;
+
+import com.google.common.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * A fake implementation for {@link android.content.Context}, that helps broadcast {@link Intent}s
+ * to registered {@link BroadcastReceiver} instances.
+ */
+// TODO(b/202420937): Add unit tests for this class.
+public final class BroadcastingFakeContext extends MockContext {
+ private BroadcastReceiver mReceiver;
+ private IntentFilter mIntentFilter;
+ private Handler mHandler;
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter) {
+ mReceiver = receiver;
+ mIntentFilter = filter;
+
+ return null;
+ }
+
+ @Override
+ public Intent registerReceiver(BroadcastReceiver receiver, IntentFilter filter,
+ String broadcastPermission, Handler scheduler) {
+ mReceiver = receiver;
+ mIntentFilter = filter;
+ mHandler = scheduler;
+
+ return null;
+ }
+
+ @Override
+ public void sendBroadcast(Intent intent) {
+ if (mHandler == null) {
+ mReceiver.onReceive(this, intent);
+ return;
+ }
+
+ CountDownLatch latch = new CountDownLatch(1);
+ mHandler.getLooper().getQueue().addIdleHandler(() -> {
+ latch.countDown();
+ return false;
+ });
+
+ mHandler.post(() -> mReceiver.onReceive(this, intent));
+
+ // wait until the queue is idle
+ try {
+ latch.await();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException(
+ "Interrupted while waiting for Broadcast Intent to be received");
+ }
+ }
+
+ @Override
+ public void unregisterReceiver(BroadcastReceiver receiver) {
+ if (receiver == mReceiver) {
+ mReceiver = null;
+ mIntentFilter = null;
+ mHandler = null;
+ }
+ }
+
+ public void verifyReceiverNotRegistered() {
+ assertThat(mIntentFilter).isNull();
+ assertThat(mReceiver).isNull();
+ assertThat(mHandler).isNull();
+ }
+
+ public void verifyReceiverRegistered(String expectedAction) {
+ assertThat(mIntentFilter.actionsIterator()).isNotNull();
+ ArrayList<String> actions = Lists.newArrayList(mIntentFilter.actionsIterator());
+ assertWithMessage("IntentFilter actions").that(actions).contains(expectedAction);
+ assertWithMessage("Registered BroadcastReceiver").that(mReceiver).isNotNull();
+ }
+}
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/config.xml b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/config.xml
index c912ab9..059f7b1 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/config.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/values/config.xml
@@ -25,4 +25,13 @@
<item>com.android.car.carlauncher/.AppGridActivity</item>
<item>com.android.car.notification/.CarNotificationCenterActivity</item>
</string-array>
+
+ <string-array name="config_ignoreOpeningForegroundDA" translatable="false">
+ <item>com.android.car.carlauncher/.CarLauncher</item>
+ <item>com.android.car.carlauncher/.ControlBarActivity</item>
+ <item>com.android.car.settings/.FallbackHome</item>
+ <item>com.google.android.gms/.auth.uiflows.common.UnpackingRedirectActivity</item>
+ <item>com.google.android.gms/.auth.auto.SignInMethodActivity</item>
+ <item>com.google.android.gms/.auth.uiflows.minutemaid.MinuteMaidActivity</item>
+ </string-array>
</resources>
diff --git a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/xml/overlays.xml b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/xml/overlays.xml
index 44c23f8..d15e291 100644
--- a/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/xml/overlays.xml
+++ b/car_product/car_ui_portrait/rro/CarUiPortraitLauncherRRO/res/xml/overlays.xml
@@ -64,4 +64,5 @@
<item target="array/config_homeCardModuleClasses" value="@array/config_homeCardModuleClasses"/>
<item target="array/config_foregroundDAComponents" value="@array/config_foregroundDAComponents"/>
+ <item target="array/config_ignoreOpeningForegroundDA" value="@array/config_ignoreOpeningForegroundDA"/>
</overlay>
\ No newline at end of file
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-bn/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-bn/strings.xml
index d977eee..31e0c0f 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-bn/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-bn/strings.xml
@@ -19,6 +19,5 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="owner_name" msgid="3416113395996003764">"ড্রাইভার"</string>
<string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"শুধুমাত্র অ্যাপটি খোলা থাকলে আপনার আনুমানিক লোকেশন অ্যাক্সেস করা"</string>
- <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
- <skip />
+ <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2297855099350562159">"মাইক্রোফোন চালু করুন"</string>
</resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-fr-rCA/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-fr-rCA/strings.xml
index eb9354f..8b0f9ed 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-fr-rCA/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-fr-rCA/strings.xml
@@ -19,6 +19,5 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="owner_name" msgid="3416113395996003764">"Conducteur"</string>
<string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"accéder à votre position approximative seulement en avant-plan"</string>
- <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
- <skip />
+ <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2297855099350562159">"Activer le microphone"</string>
</resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-gu/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-gu/strings.xml
index d92cc32..9cf7aca 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-gu/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-gu/strings.xml
@@ -19,6 +19,5 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="owner_name" msgid="3416113395996003764">"ડ્રાઇવર"</string>
<string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"ફૉરગ્રાઉન્ડમાં ફક્ત અંદાજિત સ્થાન ઍક્સેસ કરો"</string>
- <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
- <skip />
+ <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2297855099350562159">"માઇક્રોફોન ચાલુ કરો"</string>
</resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-kn/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-kn/strings.xml
index bb5ea0f..359c573 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-kn/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-kn/strings.xml
@@ -19,6 +19,5 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="owner_name" msgid="3416113395996003764">"ಡ್ರೈವರ್"</string>
<string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"ಮುನ್ನೆಲೆಯಲ್ಲಿ ಮಾತ್ರ ಅಂದಾಜು ಸ್ಥಳವನ್ನು ಪ್ರವೇಶಿಸಿ"</string>
- <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
- <skip />
+ <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2297855099350562159">"ಮೈಕ್ರೋಫೋನ್ ಆನ್ ಮಾಡಿ"</string>
</resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-mr/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-mr/strings.xml
index c087704..96f9785 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-mr/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-mr/strings.xml
@@ -19,6 +19,5 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="owner_name" msgid="3416113395996003764">"ड्रायव्हर"</string>
<string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"फक्त फोअरग्राउंडमध्ये अंदाजे स्थान अॅक्सेस करा"</string>
- <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
- <skip />
+ <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2297855099350562159">"मायक्रोफोन सुरू करा"</string>
</resources>
diff --git a/car_product/overlay/frameworks/base/core/res/res/values-ur/strings.xml b/car_product/overlay/frameworks/base/core/res/res/values-ur/strings.xml
index f3cf815..256885e 100644
--- a/car_product/overlay/frameworks/base/core/res/res/values-ur/strings.xml
+++ b/car_product/overlay/frameworks/base/core/res/res/values-ur/strings.xml
@@ -19,6 +19,5 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="owner_name" msgid="3416113395996003764">"ڈرائیور"</string>
<string name="permlab_accessCoarseLocation" msgid="2494909511737161237">"صرف پیش منظر میں تخمینی مقام تک رسائی"</string>
- <!-- no translation found for sensor_privacy_start_use_dialog_turn_on_button (2297855099350562159) -->
- <skip />
+ <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="2297855099350562159">"مائیکروفون آن کریں"</string>
</resources>
diff --git a/service/AndroidManifest.xml b/service/AndroidManifest.xml
index f8a5e9c..9cf986d 100644
--- a/service/AndroidManifest.xml
+++ b/service/AndroidManifest.xml
@@ -892,6 +892,14 @@
android:label="@string/car_permission_label_template_renderer"
android:description="@string/car_permission_desc_template_renderer"/>
+ <!-- Allows an application to control launching applications in Car.
+ <p>Protection level: signature|privileged
+ -->
+ <permission android:name="android.car.permission.CONTROL_CAR_APP_LAUNCH"
+ android:description="@string/car_permission_desc_control_car_app_launch"
+ android:label="@string/car_permission_label_control_car_app_launch"
+ android:protectionLevel="signature|privileged" />
+
<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.DEVICE_POWER"/>
<uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS"/>
diff --git a/service/res/values-bn/strings.xml b/service/res/values-bn/strings.xml
index eff0449..32f7b2a 100644
--- a/service/res/values-bn/strings.xml
+++ b/service/res/values-bn/strings.xml
@@ -182,20 +182,12 @@
<string name="factory_reset_later_button" msgid="2401829720674483843">"পরে রিসেট করুন"</string>
<string name="factory_reset_later_text" msgid="5896142140528784784">"ইনফোটেইনমেন্ট সিস্টেম পরের বার গাড়ি চালু হলে রিসেট হবে।"</string>
<string name="factory_reset_driving_text" msgid="6702298505761254553">"রিসেট শুরু করতে গাড়িকে পার্কিং অবস্থায় থাকতে হবে।"</string>
- <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
- <skip />
- <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
- <skip />
+ <string name="resource_overuse_notification_title" msgid="3385149030747234969">"<xliff:g id="ID_1">^1</xliff:g> আপনার সিস্টেমের পারফর্ম্যান্সে প্রভাব ফেলছে"</string>
+ <string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"সিস্টেমের পারফর্ম্যান্স উন্নত করতে অ্যাপ বন্ধ করুন। সেটিংস থেকে আপনি আবার অ্যাপ চালু করতে পারবেন।"</string>
+ <string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"ব্যবহার করা চালিয়ে যেতে অ্যাপকে অগ্রাধিকার দিন।"</string>
+ <string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"সিস্টেমের পারফর্ম্যান্স উন্নত করতে অ্যাপটি আনইনস্টল করুন।"</string>
+ <string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"অ্যাপ বন্ধ করুন"</string>
+ <string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"অ্যাপকে অগ্রাধিকার দিন"</string>
+ <string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"অ্যাপ আনইনস্টল করুন"</string>
+ <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"<xliff:g id="ID_1">^1</xliff:g> বন্ধ করা হয়েছে। সেটিংস থেকে আপনি আবার এটি চালু করতে পারবেন।"</string>
</resources>
diff --git a/service/res/values-de/strings.xml b/service/res/values-de/strings.xml
index afb1467..c9ad990 100644
--- a/service/res/values-de/strings.xml
+++ b/service/res/values-de/strings.xml
@@ -182,12 +182,12 @@
<string name="factory_reset_later_button" msgid="2401829720674483843">"Später zurücksetzen"</string>
<string name="factory_reset_later_text" msgid="5896142140528784784">"Das Infotainmentsystem wird beim nächsten Start zurückgesetzt."</string>
<string name="factory_reset_driving_text" msgid="6702298505761254553">"Zum Zurücksetzen muss das Auto stehen."</string>
- <string name="resource_overuse_notification_title" msgid="3385149030747234969">"<xliff:g id="ID_1">^1</xliff:g> beeinträchtigt deine Systemleistung"</string>
+ <string name="resource_overuse_notification_title" msgid="3385149030747234969">"<xliff:g id="ID_1">^1</xliff:g> beeinträchtigt die Systemleistung"</string>
<string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"Wenn du die Systemleistung verbessern möchtest, deaktiviere die App. Du kannst sie in den Einstellungen später wieder aktivieren."</string>
<string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"Wenn du die App weiterhin nutzen möchtest, priorisiere sie."</string>
<string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"Wenn du die Systemleistung verbessern möchtest, deinstalliere die App."</string>
<string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"App deaktivieren"</string>
<string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"App priorisieren"</string>
<string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"App deinstallieren"</string>
- <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"<xliff:g id="ID_1">^1</xliff:g> wurde deaktiviert. Du kannst sie in den Einstellungen wieder aktivieren."</string>
+ <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"<xliff:g id="ID_1">^1</xliff:g> wurde deaktiviert. Du kannst die App in den Einstellungen wieder aktivieren."</string>
</resources>
diff --git a/service/res/values-gu/strings.xml b/service/res/values-gu/strings.xml
index 3cd0a0d..feb17b3 100644
--- a/service/res/values-gu/strings.xml
+++ b/service/res/values-gu/strings.xml
@@ -182,20 +182,12 @@
<string name="factory_reset_later_button" msgid="2401829720674483843">"પછીથી રીસેટ કરો"</string>
<string name="factory_reset_later_text" msgid="5896142140528784784">"આગલી વખતે જ્યારે કારને ચાલુ કરવામાં આવે ત્યારે ઇન્ફોટેનમેન્ટ સિસ્ટમ રીસેટ થશે."</string>
<string name="factory_reset_driving_text" msgid="6702298505761254553">"રીસેટ શરૂ કરવા માટે કાર પાર્ક કરેલી હોવી જરૂરી છે."</string>
- <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
- <skip />
- <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
- <skip />
+ <string name="resource_overuse_notification_title" msgid="3385149030747234969">"<xliff:g id="ID_1">^1</xliff:g> તમારી સિસ્ટમની કાર્યક્ષમતાને અસર કરી રહી છે"</string>
+ <string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"સિસ્ટમની કાર્યક્ષમતા બહેતર બનાવવા માટે, ઍપ બંધ કરો. તમે સેટિંગમાંથી ઍપ ફરીથી ચાલુ કરી શકશો."</string>
+ <string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"ઍપનો ઉપયોગ કરવાનું ચાલુ રાખવા માટે, ઍપને પ્રાધાન્યતા આપો."</string>
+ <string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"સિસ્ટમની કાર્યક્ષમતા બહેતર બનાવવા માટે, ઍપ અનઇન્સ્ટૉલ કરો."</string>
+ <string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"ઍપ બંધ કરો"</string>
+ <string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"ઍપને પ્રાધાન્યતા આપો"</string>
+ <string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"ઍપ અનઇન્સ્ટૉલ કરો"</string>
+ <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"<xliff:g id="ID_1">^1</xliff:g> બંધ કરવામાં આવી છે. તમે સેટિંગમાંથી તેને ફરીથી ચાલુ કરી શકો છો."</string>
</resources>
diff --git a/service/res/values-kn/strings.xml b/service/res/values-kn/strings.xml
index f44622e..30f5010 100644
--- a/service/res/values-kn/strings.xml
+++ b/service/res/values-kn/strings.xml
@@ -182,20 +182,12 @@
<string name="factory_reset_later_button" msgid="2401829720674483843">"ನಂತರ ರೀಸೆಟ್ ಮಾಡಿ"</string>
<string name="factory_reset_later_text" msgid="5896142140528784784">"ಮುಂದಿನ ಬಾರಿ ಕಾರ್ ಆರಂಭವಾದಾಗ ಇನ್ಫೋಟೈನ್ಮೆಂಟ್ ಸಿಸ್ಟಂ ಅನ್ನು ರೀಸೆಟ್ ಮಾಡಲಾಗುತ್ತದೆ."</string>
<string name="factory_reset_driving_text" msgid="6702298505761254553">"ರೀಸೆಟ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಲು ಕಾರ್ ಅನ್ನು ನಿಲ್ಲಿಸಿರಬೇಕು."</string>
- <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
- <skip />
- <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
- <skip />
+ <string name="resource_overuse_notification_title" msgid="3385149030747234969">"<xliff:g id="ID_1">^1</xliff:g> ನಿಮ್ಮ ಸಿಸ್ಟಂ ಕಾರ್ಯಕ್ಷಮತೆಯ ಮೇಲೆ ಪರಿಣಾಮ ಬೀರುತ್ತಿದೆ"</string>
+ <string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"ಸಿಸ್ಟಂ ಕಾರ್ಯಕ್ಷಮತೆಯನ್ನು ಸುಧಾರಿಸಲು ಆ್ಯಪ್ ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ. ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ ನೀವು ಆ್ಯಪ್ ಅನ್ನು ಪುನಃ ಸಕ್ರಿಯಗೊಳಿಸಬಹುದು."</string>
+ <string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"ಆ್ಯಪ್ ಬಳಸುವುದನ್ನು ಮುಂದುವರಿಸಲು ಆ್ಯಪ್ ಅನ್ನು ಆದ್ಯತೆಗೊಳಿಸಿ."</string>
+ <string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"ಸಿಸ್ಟಂ ಕಾರ್ಯಕ್ಷಮತೆಯನ್ನು ಸುಧಾರಿಸಲು ಆ್ಯಪ್ ಅನ್ನು ಅನ್ಇನ್ಸ್ಟಾಲ್ ಮಾಡಿ."</string>
+ <string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"ಆ್ಯಪ್ ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ"</string>
+ <string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"ಆ್ಯಪ್ ಅನ್ನು ಆದ್ಯತೆಗೊಳಿಸಿ"</string>
+ <string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"ಆ್ಯಪ್ ಅನ್ಇನ್ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
+ <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"<xliff:g id="ID_1">^1</xliff:g> ಅನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಲಾಗಿದೆ. ಸೆಟ್ಟಿಂಗ್ಗಳಲ್ಲಿ ನೀವು ಇದನ್ನು ಪುನಃ ಸಕ್ರಿಯಗೊಳಿಸಬಹುದು."</string>
</resources>
diff --git a/service/res/values-mr/strings.xml b/service/res/values-mr/strings.xml
index 7173d57..abc96a9 100644
--- a/service/res/values-mr/strings.xml
+++ b/service/res/values-mr/strings.xml
@@ -182,20 +182,12 @@
<string name="factory_reset_later_button" msgid="2401829720674483843">"नंतर रीसेट करा"</string>
<string name="factory_reset_later_text" msgid="5896142140528784784">"पुढील वेळी कार सुरू झाल्यावर इंफोटेनमेंट सिस्टम रीसेट होईल."</string>
<string name="factory_reset_driving_text" msgid="6702298505761254553">"रीसेट सुरू करण्यासाठी कार पार्क केलेली असावी."</string>
- <!-- no translation found for resource_overuse_notification_title (3385149030747234969) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_text_disable_app (4538000369374274293) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_text_prioritize_app (4782324719261106243) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_text_uninstall_app (531108846448668467) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_button_disable_app (5511548570206345274) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_button_prioritize_app (5327141954014335559) -->
- <skip />
- <!-- no translation found for resource_overuse_notification_button_uninstall_app (7327141273608850448) -->
- <skip />
- <!-- no translation found for resource_overuse_toast_disable_app_now (3182983639177825069) -->
- <skip />
+ <string name="resource_overuse_notification_title" msgid="3385149030747234969">"तुमच्या सिस्टीमच्या परफॉर्मन्सवर <xliff:g id="ID_1">^1</xliff:g> मुळे परिणाम होत आहे"</string>
+ <string name="resource_overuse_notification_text_disable_app" msgid="4538000369374274293">"सिस्टीमच्या परफॉर्मन्समध्ये सुधारणा करण्यासाठी ॲप बंद करा. तुम्ही सेटिंग्ज मध्ये ॲप पुन्हा एकदा सुरू करू शकता."</string>
+ <string name="resource_overuse_notification_text_prioritize_app" msgid="4782324719261106243">"ॲप वापरणे सुरू ठेवण्यासाठी ॲपला प्राधान्य द्या."</string>
+ <string name="resource_overuse_notification_text_uninstall_app" msgid="531108846448668467">"सिस्टीमच्या परफॉर्मन्समध्ये सुधारणा करण्यासाठी ॲप अनइंस्टॉल करा."</string>
+ <string name="resource_overuse_notification_button_disable_app" msgid="5511548570206345274">"अॅप बंद करा"</string>
+ <string name="resource_overuse_notification_button_prioritize_app" msgid="5327141954014335559">"ॲपला प्राधान्य द्या"</string>
+ <string name="resource_overuse_notification_button_uninstall_app" msgid="7327141273608850448">"अॅप अनइंस्टॉल करा"</string>
+ <string name="resource_overuse_toast_disable_app_now" msgid="3182983639177825069">"<xliff:g id="ID_1">^1</xliff:g> बंद केले आहे. तुम्ही सेटिंग्ज मध्ये ते पुन्हा सुरू करू शकता."</string>
</resources>
diff --git a/service/res/values-te/strings.xml b/service/res/values-te/strings.xml
index 48b71ad..ad75dde 100644
--- a/service/res/values-te/strings.xml
+++ b/service/res/values-te/strings.xml
@@ -19,7 +19,7 @@
<string name="car_permission_label" msgid="2215078736675564541">"కారు సమాచారం"</string>
<string name="car_permission_desc" msgid="3584369074931334964">"మీ కారుకు సంబంధించిన సమాచారాన్ని యాక్సెస్ చేయండి"</string>
<string name="car_permission_label_camera" msgid="3725702064841827180">"కారు కెమెరాను యాక్సెస్ చేయగలవు"</string>
- <string name="car_permission_desc_camera" msgid="917024932164501426">"మీ కారు యొక్క కామెరా(లు)ని యాక్సెస్ చేయండి."</string>
+ <string name="car_permission_desc_camera" msgid="917024932164501426">"మీ కారు కామెరా(ల)ను యాక్సెస్ చేయడం."</string>
<string name="car_permission_label_energy" msgid="7409144323527821558">"కారు శక్తి సమాచారాన్ని యాక్సెస్ చేయగలవు"</string>
<string name="car_permission_desc_energy" msgid="3392963810053235407">"మీ కారు శక్తి సమాచారాన్ని యాక్సెస్ చేయండి."</string>
<string name="car_permission_label_adjust_range_remaining" msgid="839033553999920138">"కారు యొక్క మిగిలిన ప్రయాణ దూరాన్ని సర్దుబాటు చేయండి"</string>
@@ -84,9 +84,9 @@
<string name="car_permission_label_diag_clear" msgid="4783070510879698157">"సమస్య విశ్లేషణ డేటాను క్లియర్ చేయగలవు"</string>
<string name="car_permission_desc_diag_clear" msgid="7453222114866042786">"కారు నుండి సమస్య విశ్లేషణ డేటాను క్లియర్ చేయగలవు."</string>
<string name="car_permission_label_vms_publisher" msgid="3049934078926106641">"VMS ప్రచురణకర్త"</string>
- <string name="car_permission_desc_vms_publisher" msgid="5589489298597386828">"VMS మెసేజ్లను ప్రచురించండి"</string>
+ <string name="car_permission_desc_vms_publisher" msgid="5589489298597386828">"VMS మెసేజ్లను పబ్లిష్ చేయండి"</string>
<string name="car_permission_label_vms_subscriber" msgid="5648841182059222299">"VMS సభ్యులు"</string>
- <string name="car_permission_desc_vms_subscriber" msgid="7551009457847673620">"VMS మెసేజ్లను పొందడానికి సభ్యత్వం తీసుకోండి"</string>
+ <string name="car_permission_desc_vms_subscriber" msgid="7551009457847673620">"VMS మెసేజ్లను పొందడానికి సబ్స్క్రయిబ్ చేయండి"</string>
<string name="car_permission_label_bind_vms_client" msgid="4889732900973280313">"VMS క్లయింట్ సేవ"</string>
<string name="car_permission_desc_bind_vms_client" msgid="4062835325264330564">"VMS క్లయింట్లను ఆచరించండి"</string>
<string name="car_permission_label_storage_monitoring" msgid="2327639346522530549">"ఫ్లాష్ నిల్వ పర్యవేక్షణ"</string>
diff --git a/service/res/values/strings.xml b/service/res/values/strings.xml
index 416981b..70a6ecf 100644
--- a/service/res/values/strings.xml
+++ b/service/res/values/strings.xml
@@ -548,6 +548,11 @@
<!-- Permission text: app can render templates provided by another app [CHAR LIMIT=NONE] -->
<string name="car_permission_desc_template_renderer">Render templates.</string>
+ <!-- Permission text: app can control launching applications in Car [CHAR LIMIT=NONE] -->
+ <string name="car_permission_label_control_car_app_launch">control launching applications</string>
+ <!-- Permission text: app can control launching applications in Car [CHAR LIMIT=NONE] -->
+ <string name="car_permission_desc_control_car_app_launch">Control launching applications.</string>
+
<!-- The default name of device enrolled as trust device [CHAR LIMIT=NONE] -->
<string name="trust_device_default_name">My Device</string>
diff --git a/service/src/com/android/car/CarFeatureController.java b/service/src/com/android/car/CarFeatureController.java
index f5df9e4..528353d 100644
--- a/service/src/com/android/car/CarFeatureController.java
+++ b/service/src/com/android/car/CarFeatureController.java
@@ -63,6 +63,7 @@
Car.APP_FOCUS_SERVICE,
Car.AUDIO_SERVICE,
Car.BLUETOOTH_SERVICE,
+ Car.CAR_ACTIVITY_SERVICE,
Car.CAR_BUGREPORT_SERVICE,
Car.CAR_DEVICE_POLICY_SERVICE,
Car.CAR_DRIVING_STATE_SERVICE,
diff --git a/service/src/com/android/car/CarLog.java b/service/src/com/android/car/CarLog.java
index 4585eda..3cec3bb 100644
--- a/service/src/com/android/car/CarLog.java
+++ b/service/src/com/android/car/CarLog.java
@@ -43,6 +43,7 @@
public static final String TAG_SERVICE = "CAR.SERVICE";
public static final String TAG_STORAGE = "CAR.STORAGE";
public static final String TAG_TELEMETRY = "CAR.TELEMETRY";
+ public static final String TAG_TIME = "CAR.TIME";
public static final String TAG_WATCHDOG = "CAR.WATCHDOG";
/**
diff --git a/service/src/com/android/car/CarServiceUtils.java b/service/src/com/android/car/CarServiceUtils.java
index 2dddeeb..c4f77cf 100644
--- a/service/src/com/android/car/CarServiceUtils.java
+++ b/service/src/com/android/car/CarServiceUtils.java
@@ -154,8 +154,17 @@
}
public static float[] toFloatArray(List<Float> list) {
- final int size = list.size();
- final float[] array = new float[size];
+ int size = list.size();
+ float[] array = new float[size];
+ for (int i = 0; i < size; ++i) {
+ array[i] = list.get(i);
+ }
+ return array;
+ }
+
+ public static long[] toLongArray(List<Long> list) {
+ int size = list.size();
+ long[] array = new long[size];
for (int i = 0; i < size; ++i) {
array[i] = list.get(i);
}
@@ -163,8 +172,8 @@
}
public static int[] toIntArray(List<Integer> list) {
- final int size = list.size();
- final int[] array = new int[size];
+ int size = list.size();
+ int[] array = new int[size];
for (int i = 0; i < size; ++i) {
array[i] = list.get(i);
}
@@ -172,8 +181,8 @@
}
public static byte[] toByteArray(List<Byte> list) {
- final int size = list.size();
- final byte[] array = new byte[size];
+ int size = list.size();
+ byte[] array = new byte[size];
for (int i = 0; i < size; ++i) {
array[i] = list.get(i);
}
diff --git a/service/src/com/android/car/FastPairGattServer.java b/service/src/com/android/car/FastPairGattServer.java
index 6fc537d..b5b9881 100644
--- a/service/src/com/android/car/FastPairGattServer.java
+++ b/service/src/com/android/car/FastPairGattServer.java
@@ -222,8 +222,6 @@
.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS, offset,
mEncryptedResponse);
processPairingKey(value);
- mBluetoothGattServer
- .notifyCharacteristicChanged(device, mPasskeyCharacteristic, false);
} else {
Log.w(TAG, "onWriteOther" + characteristic.getUuid());
@@ -569,6 +567,8 @@
return;
}
mPasskeyCharacteristic.setValue(mEncryptedResponse);
+ mBluetoothGattServer
+ .notifyCharacteristicChanged(mRemoteGattDevice, mPasskeyCharacteristic, false);
}
/**
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index d590083..4b7c75d 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -54,6 +54,7 @@
import com.android.car.admin.CarDevicePolicyService;
import com.android.car.admin.FactoryResetActivity;
+import com.android.car.am.CarActivityService;
import com.android.car.am.FixedActivityService;
import com.android.car.audio.CarAudioService;
import com.android.car.cluster.ClusterHomeService;
@@ -136,6 +137,7 @@
private final ClusterHomeService mClusterHomeService;
private final CarEvsService mCarEvsService;
private final CarTelemetryService mCarTelemetryService;
+ private final CarActivityService mCarActivityService;
private final CarServiceBase[] mAllServices;
@@ -358,6 +360,8 @@
} else {
mCarTelemetryService = null;
}
+ mCarActivityService = constructWithTrace(t, CarActivityService.class,
+ () -> new CarActivityService(serviceContext));
// Be careful with order. Service depending on other service should be inited later.
List<CarServiceBase> allServices = new ArrayList<>();
@@ -394,6 +398,7 @@
addServiceIfNonNull(allServices, mClusterHomeService);
addServiceIfNonNull(allServices, mCarEvsService);
addServiceIfNonNull(allServices, mCarTelemetryService);
+ allServices.add(mCarActivityService);
// Always put mCarExperimentalFeatureServiceController in last.
addServiceIfNonNull(allServices, mCarExperimentalFeatureServiceController);
@@ -463,6 +468,7 @@
mSystemInterface.setCarServiceHelper(carServiceHelper);
mCarOccupantZoneService.setCarServiceHelper(carServiceHelper);
mCarUserService.setCarServiceHelper(carServiceHelper);
+ mCarActivityService.setICarServiceHelper(carServiceHelper);
bundle = new Bundle();
bundle.putBinder(ICAR_SYSTEM_SERVER_CLIENT, mICarSystemServerClientImpl.asBinder());
@@ -631,6 +637,8 @@
return mCarEvsService;
case Car.CAR_TELEMETRY_SERVICE:
return mCarTelemetryService;
+ case Car.CAR_ACTIVITY_SERVICE:
+ return mCarActivityService;
default:
IBinder service = null;
if (mCarExperimentalFeatureServiceController != null) {
diff --git a/service/src/com/android/car/am/CarActivityService.java b/service/src/com/android/car/am/CarActivityService.java
new file mode 100644
index 0000000..61d309e
--- /dev/null
+++ b/service/src/com/android/car/am/CarActivityService.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.am;
+
+import android.app.ActivityManager;
+import android.car.Car;
+import android.car.app.CarActivityManager;
+import android.car.app.ICarActivityService;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.IndentingPrintWriter;
+
+import com.android.car.CarServiceBase;
+import com.android.car.internal.ICarServiceHelper;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+/**
+ * Service responsible for Activities in Car.
+ */
+public final class CarActivityService extends ICarActivityService.Stub
+ implements CarServiceBase {
+
+ private final Context mContext;
+
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ ICarServiceHelper mICarServiceHelper;
+
+ public CarActivityService(Context context) {
+ mContext = context;
+ }
+
+ @Override
+ public void init() {}
+
+ @Override
+ public void release() {}
+
+ /**
+ * Sets {@code ICarServiceHelper}.
+ */
+ public void setICarServiceHelper(ICarServiceHelper helper) {
+ synchronized (mLock) {
+ mICarServiceHelper = helper;
+ }
+ }
+
+ @Override
+ public int setPersistentActivity(ComponentName activity, int displayId, int featureId) throws
+ RemoteException {
+ if (PackageManager.PERMISSION_GRANTED != mContext.checkCallingOrSelfPermission(
+ Car.PERMISSION_CONTROL_CAR_APP_LAUNCH)) {
+ throw new SecurityException("Requires " + Car.PERMISSION_CONTROL_CAR_APP_LAUNCH);
+ }
+ int caller = getCaller();
+ if (caller != UserHandle.USER_SYSTEM && caller != ActivityManager.getCurrentUser()) {
+ return CarActivityManager.RESULT_INVALID_USER;
+ }
+
+ ICarServiceHelper helper;
+ synchronized (mLock) {
+ helper = mICarServiceHelper;
+ }
+ if (helper == null) {
+ throw new IllegalStateException("ICarServiceHelper isn't connected yet");
+ }
+ return helper.setPersistentActivity(activity, displayId, featureId);
+ }
+
+ @VisibleForTesting
+ int getCaller() { // Non static for mocking.
+ return UserHandle.getUserId(Binder.getCallingUid());
+ }
+
+ @Override
+ public void dump(IndentingPrintWriter writer) {}
+}
diff --git a/service/src/com/android/car/hal/TimeHalService.java b/service/src/com/android/car/hal/TimeHalService.java
new file mode 100644
index 0000000..a166850
--- /dev/null
+++ b/service/src/com/android/car/hal/TimeHalService.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.hal;
+
+import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.EPOCH_TIME;
+
+import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
+
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.hardware.automotive.vehicle.V2_0.VehicleArea;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropertyStatus;
+import android.util.IndentingPrintWriter;
+
+import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport;
+
+import java.io.PrintWriter;
+import java.time.Instant;
+import java.util.Collection;
+import java.util.List;
+
+/** Writes the Android System time to EPOCH_TIME in the VHAL, if supported. */
+public final class TimeHalService extends HalServiceBase {
+
+ private static final int[] SUPPORTED_PROPERTIES = new int[]{EPOCH_TIME};
+
+ private final Context mContext;
+
+ private final VehicleHal mHal;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ if (Intent.ACTION_TIME_CHANGED.equals(intent.getAction())) {
+ updateProperty(System.currentTimeMillis());
+ }
+ }
+ };
+
+ private boolean mReceiverRegistered;
+
+ @Nullable
+ private Instant mLastAndroidTimeReported;
+
+ private boolean mAndroidTimeSupported;
+
+ TimeHalService(Context context, VehicleHal hal) {
+ mContext = requireNonNull(context);
+ mHal = requireNonNull(hal);
+ }
+
+ @Override
+ public void init() {
+ if (!mAndroidTimeSupported) {
+ return;
+ }
+
+ updateProperty(System.currentTimeMillis());
+
+ IntentFilter filter = new IntentFilter(Intent.ACTION_TIME_CHANGED);
+ mContext.registerReceiver(mReceiver, filter);
+ mReceiverRegistered = true;
+ }
+
+ @Override
+ public void release() {
+ if (mReceiverRegistered) {
+ mContext.unregisterReceiver(mReceiver);
+ mReceiverRegistered = false;
+ }
+
+ mAndroidTimeSupported = false;
+ mLastAndroidTimeReported = null;
+ }
+
+ @Override
+ public int[] getAllSupportedProperties() {
+ return SUPPORTED_PROPERTIES;
+ }
+
+ @Override
+ public void takeProperties(Collection<VehiclePropConfig> properties) {
+ for (VehiclePropConfig property : properties) {
+ switch (property.prop) {
+ case EPOCH_TIME:
+ mAndroidTimeSupported = true;
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void onHalEvents(List<VehiclePropValue> values) {
+ }
+
+ public boolean isAndroidTimeSupported() {
+ return mAndroidTimeSupported;
+ }
+
+ private void updateProperty(long timeMillis) {
+ VehiclePropValue propValue = new VehiclePropValue();
+ propValue.prop = EPOCH_TIME;
+ propValue.areaId = VehicleArea.GLOBAL;
+ propValue.status = VehiclePropertyStatus.AVAILABLE;
+ propValue.timestamp = timeMillis;
+ propValue.value.int64Values.add(timeMillis);
+
+ mHal.set(propValue);
+ mLastAndroidTimeReported = Instant.ofEpochMilli(timeMillis);
+ }
+
+ @Override
+ @ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
+ public void dump(PrintWriter printWriter) {
+ IndentingPrintWriter writer = new IndentingPrintWriter(printWriter);
+ writer.println("*ExternalTime HAL*");
+ writer.increaseIndent();
+ writer.printf(
+ "mLastAndroidTimeReported: %d millis",
+ mLastAndroidTimeReported.toEpochMilli());
+ writer.decreaseIndent();
+ writer.flush();
+ }
+}
diff --git a/service/src/com/android/car/hal/VehicleHal.java b/service/src/com/android/car/hal/VehicleHal.java
index 726f34f..6b67b8d 100644
--- a/service/src/com/android/car/hal/VehicleHal.java
+++ b/service/src/com/android/car/hal/VehicleHal.java
@@ -19,6 +19,7 @@
import static com.android.car.CarServiceUtils.toByteArray;
import static com.android.car.CarServiceUtils.toFloatArray;
import static com.android.car.CarServiceUtils.toIntArray;
+import static com.android.car.CarServiceUtils.toLongArray;
import static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO;
import static java.lang.Integer.toHexString;
@@ -97,6 +98,7 @@
private final DiagnosticHalService mDiagnosticHal;
private final ClusterHalService mClusterHalService;
private final EvsHalService mEvsHal;
+ private final TimeHalService mTimeHalService;
private final Object mLock = new Object();
@@ -136,6 +138,8 @@
mDiagnosticHal = new DiagnosticHalService(this);
mClusterHalService = new ClusterHalService(this);
mEvsHal = new EvsHalService(this);
+ mTimeHalService = new TimeHalService(context, this);
+ //TODO(b/202396546): Dedupe this assignment with the other one in constructor below
mAllServices.addAll(Arrays.asList(mPowerHal,
mInputHal,
mDiagnosticHal,
@@ -143,6 +147,7 @@
mUserHal,
mClusterHalService,
mEvsHal,
+ mTimeHalService,
mPropertyHal)); // mPropertyHal should be the last.
mHalClient = new HalClient(vehicle, mHandlerThread.getLooper(),
/* callback= */ this);
@@ -160,6 +165,7 @@
UserHalService userHal,
DiagnosticHalService diagnosticHal,
ClusterHalService clusterHalService,
+ TimeHalService timeHalService,
HalClient halClient,
HandlerThread handlerThread) {
mHandlerThread = handlerThread;
@@ -172,12 +178,14 @@
mDiagnosticHal = diagnosticHal;
mClusterHalService = clusterHalService;
mEvsHal = new EvsHalService(this);
+ mTimeHalService = timeHalService;
mAllServices.addAll(Arrays.asList(mPowerHal,
mInputHal,
mDiagnosticHal,
mVmsHal,
mUserHal,
mEvsHal,
+ mTimeHalService,
mPropertyHal));
mHalClient = halClient;
}
@@ -322,6 +330,10 @@
return mEvsHal;
}
+ public TimeHalService getTimeHalService() {
+ return mTimeHalService;
+ }
+
private void assertServiceOwnerLocked(HalServiceBase service, int property) {
if (service != mPropertyHandlers.get(property)) {
throw new IllegalArgumentException("Property 0x" + toHexString(property)
@@ -517,18 +529,25 @@
VehiclePropValue propValue;
propValue = mHalClient.getValue(requestedPropValue);
- if (clazz == Integer.class || clazz == int.class) {
+ if (clazz == Long.class || clazz == long.class) {
+ return (T) propValue.value.int64Values.get(0);
+ } else if (clazz == Integer.class || clazz == int.class) {
return (T) propValue.value.int32Values.get(0);
} else if (clazz == Boolean.class || clazz == boolean.class) {
return (T) Boolean.valueOf(propValue.value.int32Values.get(0) == 1);
} else if (clazz == Float.class || clazz == float.class) {
return (T) propValue.value.floatValues.get(0);
+ } else if (clazz == Long[].class) {
+ Long[] longArray = new Long[propValue.value.int64Values.size()];
+ return (T) propValue.value.int32Values.toArray(longArray);
} else if (clazz == Integer[].class) {
Integer[] intArray = new Integer[propValue.value.int32Values.size()];
return (T) propValue.value.int32Values.toArray(intArray);
} else if (clazz == Float[].class) {
Float[] floatArray = new Float[propValue.value.floatValues.size()];
return (T) propValue.value.floatValues.toArray(floatArray);
+ } else if (clazz == long[].class) {
+ return (T) toLongArray(propValue.value.int64Values);
} else if (clazz == int[].class) {
return (T) toIntArray(propValue.value.int32Values);
} else if (clazz == float[].class) {
diff --git a/service/src/com/android/car/pm/CarPackageManagerService.java b/service/src/com/android/car/pm/CarPackageManagerService.java
index fb11fd8..e732a50 100644
--- a/service/src/com/android/car/pm/CarPackageManagerService.java
+++ b/service/src/com/android/car/pm/CarPackageManagerService.java
@@ -273,6 +273,9 @@
mEnableActivityBlocking = res.getBoolean(R.bool.enableActivityBlockingForSafety);
String blockingActivity = res.getString(R.string.activityBlockingActivity);
mActivityBlockingActivity = ComponentName.unflattenFromString(blockingActivity);
+ if (mEnableActivityBlocking && mActivityBlockingActivity == null) {
+ Slogf.wtf(TAG, "mActivityBlockingActivity can't be null when enabled");
+ }
mAllowedAppInstallSources = Arrays.asList(
res.getStringArray(R.array.allowedAppInstallSources));
mVendorServiceController = new VendorServiceController(
@@ -1308,7 +1311,7 @@
private void blockTopActivityIfNecessary(TopTaskInfoContainer topTask) {
synchronized (mLock) {
- if (!topTask.topActivity.equals(mActivityBlockingActivity)
+ if (!Objects.equals(mActivityBlockingActivity, topTask.topActivity)
&& mTopActivityWithDialogPerDisplay.contains(topTask.displayId)
&& !topTask.topActivity.equals(
mTopActivityWithDialogPerDisplay.get(topTask.displayId))) {
diff --git a/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java b/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java
index 39a2f17..94f1b11 100644
--- a/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java
+++ b/service/src/com/android/car/telemetry/databroker/DataBrokerImpl.java
@@ -143,10 +143,12 @@
mPublisherFactory = publisherFactory;
mResultStore = resultStore;
mScriptExecutorListener = new ScriptExecutorListener(this);
- mPublisherFactory.setFailureConsumer(this::onPublisherFailure);
+ mPublisherFactory.setFailureListener(this::onPublisherFailure);
}
- private void onPublisherFailure(AbstractPublisher publisher, Throwable error) {
+ private void onPublisherFailure(
+ AbstractPublisher publisher, List<TelemetryProto.MetricsConfig> affectedConfigs,
+ Throwable error) {
// TODO(b/193680465): disable MetricsConfig and log the error
Slog.w(CarLog.TAG_TELEMETRY, "publisher failed", error);
}
diff --git a/service/src/com/android/car/telemetry/proto/stats_log.proto b/service/src/com/android/car/telemetry/proto/stats_log.proto
index 04308d2..ec76cb8 100644
--- a/service/src/com/android/car/telemetry/proto/stats_log.proto
+++ b/service/src/com/android/car/telemetry/proto/stats_log.proto
@@ -99,3 +99,15 @@
reserved 1, 10;
}
+
+message StatsdStatsReport {
+ message ConfigStats {
+ optional int32 uid = 1;
+ optional int64 id = 2;
+ optional bool is_valid = 9;
+ }
+
+ repeated ConfigStats config_stats = 3;
+
+ reserved 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19;
+}
diff --git a/service/src/com/android/car/telemetry/publisher/AbstractPublisher.java b/service/src/com/android/car/telemetry/publisher/AbstractPublisher.java
index e91831e..088859b 100644
--- a/service/src/com/android/car/telemetry/publisher/AbstractPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/AbstractPublisher.java
@@ -16,9 +16,10 @@
package com.android.car.telemetry.publisher;
+import com.android.car.telemetry.TelemetryProto;
import com.android.car.telemetry.databroker.DataSubscriber;
-import java.util.function.BiConsumer;
+import java.util.List;
/**
* Abstract class for publishers. It is 1-1 with data source and manages sending data to
@@ -28,15 +29,25 @@
* configuration. Single publisher instance can send data as several
* {@link com.android.car.telemetry.TelemetryProto.Publisher} to subscribers.
*
- * <p>Child classes must be called from the telemetry thread.
+ * <p>The methods must be called from the telemetry thread.
*/
public abstract class AbstractPublisher {
- // TODO(b/199211673): provide list of bad MetricsConfigs to failureConsumer.
- /** Consumes the publisher failures, such as failing to connect to a underlying service. */
- private final BiConsumer<AbstractPublisher, Throwable> mFailureConsumer;
+ private final PublisherFailureListener mFailureListener;
- AbstractPublisher(BiConsumer<AbstractPublisher, Throwable> failureConsumer) {
- mFailureConsumer = failureConsumer;
+ /**
+ * Listener for publisher failures, such as failing to connect to a underlying service or
+ * invalid Publisher configuration. When publishers fail, the affected configs should be
+ * disabled, because the associated scripts cannot receive data from the failed publishers.
+ */
+ public interface PublisherFailureListener {
+ /** Called by publishers when they fail. */
+ void onPublisherFailure(
+ AbstractPublisher publisher,
+ List<TelemetryProto.MetricsConfig> affectedConfigs, Throwable error);
+ }
+
+ AbstractPublisher(PublisherFailureListener failureListener) {
+ mFailureListener = failureListener;
}
/**
@@ -69,10 +80,11 @@
public abstract boolean hasDataSubscriber(DataSubscriber subscriber);
/**
- * Notifies the failure consumer that this publisher cannot recover from the hard failure.
- * For example, it cannot connect to the underlying service.
+ * Notifies the failure Listener that this publisher failed. See
+ * {@link PublisherFailureListener} for details.
*/
- protected void notifyFailureConsumer(Throwable error) {
- mFailureConsumer.accept(this, error);
+ protected void onPublisherFailure(
+ List<TelemetryProto.MetricsConfig> affectedConfigs, Throwable error) {
+ mFailureListener.onPublisherFailure(this, affectedConfigs, error);
}
}
diff --git a/service/src/com/android/car/telemetry/publisher/CarTelemetrydPublisher.java b/service/src/com/android/car/telemetry/publisher/CarTelemetrydPublisher.java
index 490ab83..c71e728 100644
--- a/service/src/com/android/car/telemetry/publisher/CarTelemetrydPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/CarTelemetrydPublisher.java
@@ -34,7 +34,7 @@
import com.android.server.utils.Slogf;
import java.util.ArrayList;
-import java.util.function.BiConsumer;
+import java.util.stream.Collectors;
/**
* Publisher for cartelemtryd service (aka ICarTelemetry).
@@ -69,9 +69,8 @@
}
};
- CarTelemetrydPublisher(BiConsumer<AbstractPublisher, Throwable> failureConsumer,
- Handler telemetryHandler) {
- super(failureConsumer);
+ CarTelemetrydPublisher(PublisherFailureListener failureListener, Handler telemetryHandler) {
+ super(failureListener);
this.mTelemetryHandler = telemetryHandler;
}
@@ -83,7 +82,9 @@
mCarTelemetryInternal.asBinder().unlinkToDeath(this::onBinderDied, BINDER_FLAGS);
mCarTelemetryInternal = null;
}
- notifyFailureConsumer(new IllegalStateException("ICarTelemetryInternal binder died"));
+ onPublisherFailure(
+ getMetricsConfigs(),
+ new IllegalStateException("ICarTelemetryInternal binder died"));
});
}
@@ -94,15 +95,21 @@
}
IBinder binder = ServiceManager.checkService(SERVICE_NAME);
if (binder == null) {
- notifyFailureConsumer(new IllegalStateException(
- "Failed to connect to the ICarTelemetryInternal: service is not ready"));
+ onPublisherFailure(
+ getMetricsConfigs(),
+ new IllegalStateException(
+ "Failed to connect to the ICarTelemetryInternal: service is not "
+ + "ready"));
return;
}
try {
binder.linkToDeath(this::onBinderDied, BINDER_FLAGS);
} catch (RemoteException e) {
- notifyFailureConsumer(new IllegalStateException(
- "Failed to connect to the ICarTelemetryInternal: linkToDeath failed", e));
+ onPublisherFailure(
+ getMetricsConfigs(),
+ new IllegalStateException(
+ "Failed to connect to the ICarTelemetryInternal: linkToDeath failed",
+ e));
return;
}
mCarTelemetryInternal = ICarTelemetryInternal.Stub.asInterface(binder);
@@ -111,12 +118,19 @@
} catch (RemoteException e) {
binder.unlinkToDeath(this::onBinderDied, BINDER_FLAGS);
mCarTelemetryInternal = null;
- notifyFailureConsumer(new IllegalStateException(
- "Failed to connect to the ICarTelemetryInternal: Cannot set CarData listener",
- e));
+ onPublisherFailure(
+ getMetricsConfigs(),
+ new IllegalStateException(
+ "Failed to connect to the ICarTelemetryInternal: Cannot set CarData "
+ + "listener", e));
}
}
+ private ArrayList<TelemetryProto.MetricsConfig> getMetricsConfigs() {
+ return new ArrayList<>(mSubscribers.stream().map(DataSubscriber::getMetricsConfig).collect(
+ Collectors.toSet()));
+ }
+
/**
* Disconnects from ICarTelemetryInternal service.
*
diff --git a/service/src/com/android/car/telemetry/publisher/PublisherFactory.java b/service/src/com/android/car/telemetry/publisher/PublisherFactory.java
index d777847..0c210b6 100644
--- a/service/src/com/android/car/telemetry/publisher/PublisherFactory.java
+++ b/service/src/com/android/car/telemetry/publisher/PublisherFactory.java
@@ -22,7 +22,6 @@
import com.android.car.telemetry.TelemetryProto;
import java.io.File;
-import java.util.function.BiConsumer;
/**
* Lazy factory class for Publishers. It's expected to have a single factory instance.
@@ -43,7 +42,7 @@
private CarTelemetrydPublisher mCarTelemetrydPublisher;
private StatsPublisher mStatsPublisher;
- private BiConsumer<AbstractPublisher, Throwable> mFailureConsumer;
+ private AbstractPublisher.PublisherFailureListener mFailureListener;
public PublisherFactory(
CarPropertyService carPropertyService,
@@ -64,19 +63,19 @@
case TelemetryProto.Publisher.VEHICLE_PROPERTY_FIELD_NUMBER:
if (mVehiclePropertyPublisher == null) {
mVehiclePropertyPublisher = new VehiclePropertyPublisher(
- mCarPropertyService, mFailureConsumer, mTelemetryHandler);
+ mCarPropertyService, mFailureListener, mTelemetryHandler);
}
return mVehiclePropertyPublisher;
case TelemetryProto.Publisher.CARTELEMETRYD_FIELD_NUMBER:
if (mCarTelemetrydPublisher == null) {
mCarTelemetrydPublisher = new CarTelemetrydPublisher(
- mFailureConsumer, mTelemetryHandler);
+ mFailureListener, mTelemetryHandler);
}
return mCarTelemetrydPublisher;
case TelemetryProto.Publisher.STATS_FIELD_NUMBER:
if (mStatsPublisher == null) {
mStatsPublisher = new StatsPublisher(
- mFailureConsumer, mStatsManager, mRootDirectory);
+ mFailureListener, mStatsManager, mRootDirectory, mTelemetryHandler);
}
return mStatsPublisher;
default:
@@ -102,11 +101,12 @@
}
/**
- * Sets the publisher failure consumer for all the publishers. This is expected to be called
- * before {@link #getPublisher} method. This is not the best approach, but it suits for this
+ * Sets the publisher failure listener for all the publishers. This is expected to be called
+ * before {@link #getPublisher} method, because the listener is set after
+ * {@code PublisherFactory} initialized. This is not the best approach, but it suits for this
* case.
*/
- public void setFailureConsumer(BiConsumer<AbstractPublisher, Throwable> consumer) {
- mFailureConsumer = consumer;
+ public void setFailureListener(AbstractPublisher.PublisherFailureListener listener) {
+ mFailureListener = listener;
}
}
diff --git a/service/src/com/android/car/telemetry/publisher/StatsManagerImpl.java b/service/src/com/android/car/telemetry/publisher/StatsManagerImpl.java
index b184f28..ef54b3c 100644
--- a/service/src/com/android/car/telemetry/publisher/StatsManagerImpl.java
+++ b/service/src/com/android/car/telemetry/publisher/StatsManagerImpl.java
@@ -41,4 +41,9 @@
public void removeConfig(long configKey) throws StatsUnavailableException {
mStatsManager.removeConfig(configKey);
}
+
+ @Override
+ public byte[] getStatsMetadata() throws StatsUnavailableException {
+ return mStatsManager.getStatsMetadata();
+ }
}
diff --git a/service/src/com/android/car/telemetry/publisher/StatsManagerProxy.java b/service/src/com/android/car/telemetry/publisher/StatsManagerProxy.java
index 046ac44..5507e28 100644
--- a/service/src/com/android/car/telemetry/publisher/StatsManagerProxy.java
+++ b/service/src/com/android/car/telemetry/publisher/StatsManagerProxy.java
@@ -29,4 +29,7 @@
/** See {@link StatsManager#removeConfig(long)}. */
void removeConfig(long configKey) throws StatsUnavailableException;
+
+ /** See {@link StatsManager#getStatsMetadata()}. */
+ byte[] getStatsMetadata() throws StatsUnavailableException;
}
diff --git a/service/src/com/android/car/telemetry/publisher/StatsPublisher.java b/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
index 00481fe..0d08091 100644
--- a/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/StatsPublisher.java
@@ -20,8 +20,8 @@
import android.app.StatsManager.StatsUnavailableException;
import android.os.Handler;
-import android.os.Looper;
import android.os.PersistableBundle;
+import android.os.Process;
import android.util.LongSparseArray;
import android.util.Slog;
@@ -33,7 +33,6 @@
import com.android.car.telemetry.TelemetryProto;
import com.android.car.telemetry.TelemetryProto.Publisher.PublisherCase;
import com.android.car.telemetry.databroker.DataSubscriber;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.Preconditions;
@@ -46,10 +45,9 @@
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.function.BiConsumer;
/**
* Publisher for {@link TelemetryProto.StatsPublisher}.
@@ -73,6 +71,8 @@
@VisibleForTesting
static final String SAVED_STATS_CONFIGS_FILE = "stats_config_keys_versions";
+ // TODO(b/202115033): Flatten the load spike by pulling reports for each MetricsConfigs
+ // using separate periodical timers.
private static final Duration PULL_REPORTS_PERIOD = Duration.ofMinutes(10);
private static final String BUNDLE_CONFIG_KEY_PREFIX = "statsd-publisher-config-id-";
@@ -103,15 +103,12 @@
AtomsProto.ProcessMemoryState.SWAP_IN_BYTES_FIELD_NUMBER))
.build();
- // TODO(b/197766340): remove unnecessary lock
- private final Object mLock = new Object();
-
private final StatsManagerProxy mStatsManager;
private final File mSavedStatsConfigsFile;
private final Handler mTelemetryHandler;
// True if the publisher is periodically pulling reports from StatsD.
- private final AtomicBoolean mIsPullingReports = new AtomicBoolean(false);
+ private boolean mIsPullingReports = false;
/** Assign the method to {@link Runnable}, otherwise the handler fails to remove it. */
private final Runnable mPullReportsPeriodically = this::pullReportsPeriodically;
@@ -120,28 +117,18 @@
// than 100 items. We're expecting much less number of subscribers, so these data structures
// are ok.
// Maps config_key to the set of DataSubscriber.
- @GuardedBy("mLock")
private final LongSparseArray<DataSubscriber> mConfigKeyToSubscribers = new LongSparseArray<>();
private final PersistableBundle mSavedStatsConfigs;
- // TODO(b/198331078): Use telemetry thread
StatsPublisher(
- BiConsumer<AbstractPublisher, Throwable> failureConsumer,
- StatsManagerProxy statsManager,
- File rootDirectory) {
- this(failureConsumer, statsManager, rootDirectory, new Handler(Looper.myLooper()));
- }
-
- @VisibleForTesting
- StatsPublisher(
- BiConsumer<AbstractPublisher, Throwable> failureConsumer,
+ PublisherFailureListener failureListener,
StatsManagerProxy statsManager,
File rootDirectory,
- Handler handler) {
- super(failureConsumer);
+ Handler telemetryHandler) {
+ super(failureListener);
mStatsManager = statsManager;
- mTelemetryHandler = handler;
+ mTelemetryHandler = telemetryHandler;
mSavedStatsConfigsFile = new File(rootDirectory, SAVED_STATS_CONFIGS_FILE);
mSavedStatsConfigs = loadBundle();
}
@@ -182,15 +169,16 @@
Preconditions.checkArgument(
publisherParam.getPublisherCase() == PublisherCase.STATS,
"Subscribers only with StatsPublisher are supported by this class.");
- synchronized (mLock) {
- long configKey = addStatsConfigLocked(subscriber);
- mConfigKeyToSubscribers.put(configKey, subscriber);
- }
- if (!mIsPullingReports.getAndSet(true)) {
+ long configKey = buildConfigKey(subscriber);
+ mConfigKeyToSubscribers.put(configKey, subscriber);
+ addStatsConfig(configKey, subscriber);
+ if (!mIsPullingReports) {
Slog.d(CarLog.TAG_TELEMETRY, "Stats report will be pulled in "
+ PULL_REPORTS_PERIOD.toMinutes() + " minutes.");
- mTelemetryHandler.postDelayed(mPullReportsPeriodically, PULL_REPORTS_PERIOD.toMillis());
+ mIsPullingReports = true;
+ mTelemetryHandler.postDelayed(
+ mPullReportsPeriodically, PULL_REPORTS_PERIOD.toMillis());
}
}
@@ -199,7 +187,7 @@
if (report.getReportsCount() == 0) {
return;
}
- DataSubscriber subscriber = getSubscriberByConfigKey(configKey);
+ DataSubscriber subscriber = mConfigKeyToSubscribers.get(configKey);
if (subscriber == null) {
Slog.w(CarLog.TAG_TELEMETRY, "No subscribers found for config " + configKey);
return;
@@ -238,39 +226,61 @@
}
}
- private void pullReportsPeriodically() {
- for (long configKey : getActiveConfigKeys()) {
- try {
- processReport(configKey, StatsLogProto.ConfigMetricsReportList.parseFrom(
- mStatsManager.getReports(configKey)));
- } catch (StatsUnavailableException e) {
- // If the StatsD is not available, retry in the next pullReportsPeriodically call.
- break;
- } catch (InvalidProtocolBufferException e) {
- // This case should never happen.
- Slog.w(CarLog.TAG_TELEMETRY,
- "Failed to parse report from statsd, configKey=" + configKey);
+ private void processStatsMetadata(StatsLogProto.StatsdStatsReport statsReport) {
+ int myUid = Process.myUid();
+ // configKey and StatsdConfig.id are the same, see this#addStatsConfig().
+ HashSet<Long> activeConfigKeys = new HashSet<>(getActiveConfigKeys());
+ HashSet<TelemetryProto.MetricsConfig> failedConfigs = new HashSet<>();
+ for (int i = 0; i < statsReport.getConfigStatsCount(); i++) {
+ StatsLogProto.StatsdStatsReport.ConfigStats stats = statsReport.getConfigStats(i);
+ if (stats.getUid() != myUid || !activeConfigKeys.contains(stats.getId())) {
+ continue;
+ }
+ if (!stats.getIsValid()) {
+ Slog.w(CarLog.TAG_TELEMETRY, "Config key " + stats.getId() + " is invalid.");
+ failedConfigs.add(mConfigKeyToSubscribers.get(stats.getId()).getMetricsConfig());
}
}
+ if (!failedConfigs.isEmpty()) {
+ // Notify DataBroker so it can disable invalid MetricsConfigs.
+ onPublisherFailure(
+ new ArrayList<>(failedConfigs),
+ new IllegalStateException("Found invalid configs"));
+ }
+ }
- if (mIsPullingReports.get()) {
+ private void pullReportsPeriodically() {
+ if (mIsPullingReports) {
Slog.d(CarLog.TAG_TELEMETRY, "Stats report will be pulled in "
+ PULL_REPORTS_PERIOD.toMinutes() + " minutes.");
mTelemetryHandler.postDelayed(mPullReportsPeriodically, PULL_REPORTS_PERIOD.toMillis());
}
+
+ try {
+ // TODO(b/202131100): Get the active list of configs using
+ // StatsManager#setActiveConfigsChangedOperation()
+ processStatsMetadata(
+ StatsLogProto.StatsdStatsReport.parseFrom(mStatsManager.getStatsMetadata()));
+
+ for (long configKey : getActiveConfigKeys()) {
+ processReport(configKey, StatsLogProto.ConfigMetricsReportList.parseFrom(
+ mStatsManager.getReports(configKey)));
+ }
+ } catch (InvalidProtocolBufferException | StatsUnavailableException e) {
+ // If the StatsD is not available, retry in the next pullReportsPeriodically call.
+ Slog.w(CarLog.TAG_TELEMETRY, e);
+ }
}
private List<Long> getActiveConfigKeys() {
ArrayList<Long> result = new ArrayList<>();
- synchronized (mLock) {
- for (String key : mSavedStatsConfigs.keySet()) {
- // filter out all the config versions
- if (!key.startsWith(BUNDLE_CONFIG_KEY_PREFIX)) {
- continue;
- }
- // the remaining values are config keys
- result.add(mSavedStatsConfigs.getLong(key));
+ for (String key : mSavedStatsConfigs.keySet()) {
+ // filter out all the config versions
+ if (!key.startsWith(BUNDLE_CONFIG_KEY_PREFIX)) {
+ continue;
}
+ // the remaining values are config keys
+ result.add(mSavedStatsConfigs.getLong(key));
}
return result;
}
@@ -290,49 +300,49 @@
+ publisherParam.getPublisherCase().name());
return;
}
- synchronized (mLock) {
- long configKey = removeStatsConfigLocked(subscriber);
- mConfigKeyToSubscribers.remove(configKey);
- }
-
+ long configKey = removeStatsConfig(subscriber);
+ mConfigKeyToSubscribers.remove(configKey);
if (mConfigKeyToSubscribers.size() == 0) {
- mIsPullingReports.set(false);
+ mIsPullingReports = false;
mTelemetryHandler.removeCallbacks(mPullReportsPeriodically);
}
}
- /** Removes all the subscribers from the publisher removes StatsdConfigs from StatsD service. */
+ /**
+ * Removes all the subscribers from the publisher removes StatsdConfigs from StatsD service.
+ */
@Override
public void removeAllDataSubscribers() {
- synchronized (mLock) {
- for (String key : mSavedStatsConfigs.keySet()) {
- // filter out all the config versions
- if (!key.startsWith(BUNDLE_CONFIG_KEY_PREFIX)) {
- continue;
- }
- // the remaining values are config keys
- long configKey = mSavedStatsConfigs.getLong(key);
- try {
- mStatsManager.removeConfig(configKey);
- String bundleVersion = buildBundleConfigVersionKey(configKey);
- mSavedStatsConfigs.remove(key);
- mSavedStatsConfigs.remove(bundleVersion);
- } catch (StatsUnavailableException e) {
- Slog.w(CarLog.TAG_TELEMETRY, "Failed to remove config " + configKey
- + ". Ignoring the failure. Will retry removing again when"
- + " removeAllDataSubscribers() is called.", e);
- // If it cannot remove statsd config, it's less likely it can delete it even if
- // retry. So we will just ignore the failures. The next call of this method
- // will ry deleting StatsD configs again.
- }
+ for (String key : mSavedStatsConfigs.keySet()) {
+ // filter out all the config versions
+ if (!key.startsWith(BUNDLE_CONFIG_KEY_PREFIX)) {
+ continue;
}
- saveBundle();
- mSavedStatsConfigs.clear();
+ // the remaining values are config keys
+ long configKey = mSavedStatsConfigs.getLong(key);
+ try {
+ mStatsManager.removeConfig(configKey);
+ String bundleVersion = buildBundleConfigVersionKey(configKey);
+ mSavedStatsConfigs.remove(key);
+ mSavedStatsConfigs.remove(bundleVersion);
+ } catch (StatsUnavailableException e) {
+ Slog.w(CarLog.TAG_TELEMETRY, "Failed to remove config " + configKey
+ + ". Ignoring the failure. Will retry removing again when"
+ + " removeAllDataSubscribers() is called.", e);
+ // If it cannot remove statsd config, it's less likely it can delete it even if
+ // retry. So we will just ignore the failures. The next call of this method
+ // will ry deleting StatsD configs again.
+ }
}
- mIsPullingReports.set(false);
+ saveBundle();
+ mSavedStatsConfigs.clear();
+ mIsPullingReports = false;
mTelemetryHandler.removeCallbacks(mPullReportsPeriodically);
}
+ /**
+ * Returns true if the publisher has the subscriber.
+ */
@Override
public boolean hasDataSubscriber(DataSubscriber subscriber) {
TelemetryProto.Publisher publisherParam = subscriber.getPublisherParam();
@@ -340,16 +350,16 @@
return false;
}
long configKey = buildConfigKey(subscriber);
- synchronized (mLock) {
- return mConfigKeyToSubscribers.indexOfKey(configKey) >= 0;
- }
+ return mConfigKeyToSubscribers.indexOfKey(configKey) >= 0;
}
- /** Returns a subscriber for the given statsd config key. Returns null if not found. */
- private DataSubscriber getSubscriberByConfigKey(long configKey) {
- synchronized (mLock) {
- return mConfigKeyToSubscribers.get(configKey);
+ /** Returns all the {@link TelemetryProto.MetricsConfig} associated with added subscribers. */
+ private List<TelemetryProto.MetricsConfig> getMetricsConfigs() {
+ HashSet<TelemetryProto.MetricsConfig> uniqueConfigs = new HashSet<>();
+ for (int i = 0; i < mConfigKeyToSubscribers.size(); i++) {
+ uniqueConfigs.add(mConfigKeyToSubscribers.valueAt(i).getMetricsConfig());
}
+ return new ArrayList<>(uniqueConfigs);
}
/**
@@ -374,16 +384,14 @@
* previously added config_keys in the persistable bundle and only updates StatsD when
* the MetricsConfig (of CarTelemetryService) has a new version.
*/
- @GuardedBy("mLock")
- private long addStatsConfigLocked(DataSubscriber subscriber) {
- long configKey = buildConfigKey(subscriber);
+ private void addStatsConfig(long configKey, DataSubscriber subscriber) {
// Store MetricsConfig (of CarTelemetryService) version per handler_function.
String bundleVersion = buildBundleConfigVersionKey(configKey);
if (mSavedStatsConfigs.getInt(bundleVersion) != 0) {
int currentVersion = mSavedStatsConfigs.getInt(bundleVersion);
if (currentVersion >= subscriber.getMetricsConfig().getVersion()) {
// It's trying to add current or older MetricsConfig version, just ignore it.
- return configKey;
+ return;
} // if the subscriber's MetricsConfig version is newer, it will replace the old one.
}
String bundleConfigKey = buildBundleConfigKey(subscriber);
@@ -397,18 +405,15 @@
saveBundle();
} catch (StatsUnavailableException e) {
Slog.w(CarLog.TAG_TELEMETRY, "Failed to add config" + configKey, e);
- // TODO(b/189143813): if StatsManager is not ready, retry N times and hard fail after
- // by notifying DataBroker.
// We will notify the failure immediately, as we're expecting StatsManager to be stable.
- notifyFailureConsumer(
+ onPublisherFailure(
+ getMetricsConfigs(),
new IllegalStateException("Failed to add config " + configKey, e));
}
- return configKey;
}
/** Removes StatsdConfig and returns configKey. */
- @GuardedBy("mLock")
- private long removeStatsConfigLocked(DataSubscriber subscriber) {
+ private long removeStatsConfig(DataSubscriber subscriber) {
String bundleConfigKey = buildBundleConfigKey(subscriber);
long configKey = buildConfigKey(subscriber);
// Store MetricsConfig (of CarTelemetryService) version per handler_function.
diff --git a/service/src/com/android/car/telemetry/publisher/VehiclePropertyPublisher.java b/service/src/com/android/car/telemetry/publisher/VehiclePropertyPublisher.java
index dbfc002..d723d1c 100644
--- a/service/src/com/android/car/telemetry/publisher/VehiclePropertyPublisher.java
+++ b/service/src/com/android/car/telemetry/publisher/VehiclePropertyPublisher.java
@@ -35,7 +35,6 @@
import com.android.internal.util.Preconditions;
import java.util.List;
-import java.util.function.BiConsumer;
/**
* Publisher for Vehicle Property changes, aka {@code CarPropertyService}.
@@ -77,8 +76,8 @@
};
public VehiclePropertyPublisher(CarPropertyService carPropertyService,
- BiConsumer<AbstractPublisher, Throwable> failureConsumer, Handler handler) {
- super(failureConsumer);
+ PublisherFailureListener failureListener, Handler handler) {
+ super(failureListener);
mCarPropertyService = carPropertyService;
mTelemetryHandler = handler;
// Load car property list once, as the list doesn't change runtime.
diff --git a/service/src/com/android/car/watchdog/CarWatchdogService.java b/service/src/com/android/car/watchdog/CarWatchdogService.java
index 93243d4..318d132 100644
--- a/service/src/com/android/car/watchdog/CarWatchdogService.java
+++ b/service/src/com/android/car/watchdog/CarWatchdogService.java
@@ -77,6 +77,7 @@
"com.android.server.jobscheduler.GARAGE_MODE_ON";
static final String ACTION_GARAGE_MODE_OFF =
"com.android.server.jobscheduler.GARAGE_MODE_OFF";
+ static final int MISSING_ARG_VALUE = -1;
static final TimeSourceInterface SYSTEM_INSTANCE = new TimeSourceInterface() {
@Override
public Instant now() {
@@ -106,25 +107,15 @@
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
- boolean isGarageMode = false;
- if (action.equals(ACTION_GARAGE_MODE_ON)) {
- isGarageMode = true;
- } else if (!action.equals(ACTION_GARAGE_MODE_OFF)) {
- return;
- }
- if (isGarageMode) {
- mWatchdogStorage.shrinkDatabase();
- }
- try {
- mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.GARAGE_MODE,
- isGarageMode ? GarageMode.GARAGE_MODE_ON : GarageMode.GARAGE_MODE_OFF,
- /* arg2= */ -1);
- if (DEBUG) {
- Slogf.d(TAG, "Notified car watchdog daemon of garage mode(%s)",
- isGarageMode ? "ON" : "OFF");
- }
- } catch (RemoteException | RuntimeException e) {
- Slogf.w(TAG, "Notifying garage mode state change failed: %s", e);
+ switch (action) {
+ case ACTION_GARAGE_MODE_ON:
+ case ACTION_GARAGE_MODE_OFF:
+ handleGarageModeIntent(action.equals(ACTION_GARAGE_MODE_ON));
+ break;
+ case Intent.ACTION_USER_REMOVED:
+ UserHandle user = intent.getParcelableExtra(Intent.EXTRA_USER);
+ mWatchdogPerfHandler.deleteUser(user.getIdentifier());
+ break;
}
}
};
@@ -361,6 +352,23 @@
mWatchdogPerfHandler.setTimeSource(timeSource);
}
+ private void handleGarageModeIntent(boolean isOn) {
+ if (isOn) {
+ mWatchdogStorage.shrinkDatabase();
+ }
+ try {
+ mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.GARAGE_MODE,
+ isOn ? GarageMode.GARAGE_MODE_ON : GarageMode.GARAGE_MODE_OFF,
+ /* arg2= */ MISSING_ARG_VALUE);
+ if (DEBUG) {
+ Slogf.d(TAG, "Notified car watchdog daemon of garage mode(%s)",
+ isOn ? "ON" : "OFF");
+ }
+ } catch (RemoteException | RuntimeException e) {
+ Slogf.w(TAG, e, "Notifying garage mode state change failed");
+ }
+ }
+
private void postRegisterToDaemonMessage() {
CarServiceUtils.runOnMain(() -> {
synchronized (mLock) {
@@ -447,7 +455,7 @@
}
try {
mCarWatchdogDaemonHelper.notifySystemStateChange(StateType.POWER_CYCLE,
- powerCycle, /* arg2= */ -1);
+ powerCycle, /* arg2= */ MISSING_ARG_VALUE);
if (DEBUG) {
Slogf.d(TAG, "Notified car watchdog daemon of power cycle(%d)", powerCycle);
}
@@ -499,6 +507,7 @@
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_GARAGE_MODE_ON);
filter.addAction(ACTION_GARAGE_MODE_OFF);
+ filter.addAction(Intent.ACTION_USER_REMOVED);
mContext.registerReceiverForAllUsers(mBroadcastReceiver, filter, null, null);
}
diff --git a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
index 6cf7794..956161b 100644
--- a/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
+++ b/service/src/com/android/car/watchdog/WatchdogPerfHandler.java
@@ -68,7 +68,6 @@
import android.content.pm.IPackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
@@ -401,12 +400,11 @@
}
private void setPackageKillableStateForAllUsers(String packageName, boolean isKillable) {
- UserManager userManager = UserManager.get(mContext);
- List<UserInfo> userInfos = userManager.getAliveUsers();
+ int[] userIds = getAliveUserIds();
String genericPackageName = null;
synchronized (mLock) {
- for (int i = 0; i < userInfos.size(); ++i) {
- int userId = userInfos.get(i).id;
+ for (int i = 0; i < userIds.length; ++i) {
+ int userId = userIds[i];
String name = mPackageInfoHandler.getNameForUserPackage(packageName, userId);
if (name == null) {
continue;
@@ -449,11 +447,10 @@
return getPackageKillableStatesForUserId(userHandle.getIdentifier(), pm);
}
List<PackageKillableState> packageKillableStates = new ArrayList<>();
- UserManager userManager = UserManager.get(mContext);
- List<UserInfo> userInfos = userManager.getAliveUsers();
- for (int i = 0; i < userInfos.size(); ++i) {
+ int[] userIds = getAliveUserIds();
+ for (int i = 0; i < userIds.length; ++i) {
packageKillableStates.addAll(
- getPackageKillableStatesForUserId(userInfos.get(i).id, pm));
+ getPackageKillableStatesForUserId(userIds[i], pm));
}
if (DEBUG) {
Slogf.d(TAG, "Returning all package killable states for all users");
@@ -749,6 +746,21 @@
}
}
+ /** Deletes all data for specific user. */
+ public void deleteUser(@UserIdInt int userId) {
+ synchronized (mLock) {
+ for (int i = mUsageByUserPackage.size() - 1; i >= 0; --i) {
+ if (userId == mUsageByUserPackage.valueAt(i).userId) {
+ mUsageByUserPackage.removeAt(i);
+ }
+ }
+ mWatchdogStorage.syncUsers(getAliveUserIds());
+ }
+ if (DEBUG) {
+ Slogf.d(TAG, "Resource usage for user id: %d was deleted.", userId);
+ }
+ }
+
/** Sets the time source. */
public void setTimeSource(TimeSourceInterface timeSource) {
synchronized (mLock) {
@@ -801,6 +813,7 @@
}
private void readFromDatabase() {
+ mWatchdogStorage.syncUsers(getAliveUserIds());
List<WatchdogStorage.UserPackageSettingsEntry> settingsEntries =
mWatchdogStorage.getUserPackageSettings();
Slogf.i(TAG, "Read %d user package settings from database", settingsEntries.size());
@@ -1225,6 +1238,17 @@
}
}
+ private int[] getAliveUserIds() {
+ UserManager userManager = UserManager.get(mContext);
+ List<UserHandle> aliveUsers = userManager.getUserHandles(/* excludeDying= */ true);
+ int userSize = aliveUsers.size();
+ int[] userIds = new int[userSize];
+ for (int i = 0; i < userSize; ++i) {
+ userIds[i] = aliveUsers.get(i).getIdentifier();
+ }
+ return userIds;
+ }
+
private static String getUserPackageUniqueId(int userId, String genericPackageName) {
return String.valueOf(userId) + ":" + genericPackageName;
}
diff --git a/service/src/com/android/car/watchdog/WatchdogStorage.java b/service/src/com/android/car/watchdog/WatchdogStorage.java
index 78b6927..42f6313 100644
--- a/service/src/com/android/car/watchdog/WatchdogStorage.java
+++ b/service/src/com/android/car/watchdog/WatchdogStorage.java
@@ -32,6 +32,7 @@
import android.os.Process;
import android.util.ArrayMap;
import android.util.ArraySet;
+import android.util.IntArray;
import android.util.Slog;
import com.android.car.CarLog;
@@ -196,6 +197,25 @@
}
}
+ /**
+ * Deletes all user package settings and resource stats for all non-alive users.
+ *
+ * @param aliveUserIds Array of alive user ids.
+ */
+ public void syncUsers(int[] aliveUserIds) {
+ IntArray aliveUsers = IntArray.wrap(aliveUserIds);
+ for (int i = mUserPackagesByKey.size() - 1; i >= 0; --i) {
+ UserPackage userPackage = mUserPackagesByKey.valueAt(i);
+ if (aliveUsers.indexOf(userPackage.getUserId()) == -1) {
+ mUserPackagesByKey.removeAt(i);
+ mUserPackagesById.remove(userPackage.getUniqueId());
+ }
+ }
+ try (SQLiteDatabase db = mDbHelper.getWritableDatabase()) {
+ UserPackageSettingsTable.syncUserPackagesWithAliveUsers(db, aliveUsers);
+ }
+ }
+
@VisibleForTesting
boolean saveIoUsageStats(List<IoUsageStatsEntry> entries, boolean shouldCheckRetention) {
ZonedDateTime currentDate =
@@ -371,6 +391,24 @@
Slogf.i(TAG, "Deleted %d user package settings db rows for user %d and package %s",
deletedRows, userId, packageName);
}
+
+ public static void syncUserPackagesWithAliveUsers(SQLiteDatabase db, IntArray aliveUsers) {
+ StringBuilder queryBuilder = new StringBuilder();
+ for (int i = 0; i < aliveUsers.size(); ++i) {
+ if (i == 0) {
+ queryBuilder.append(COLUMN_USER_ID).append(" NOT IN (");
+ } else {
+ queryBuilder.append(", ");
+ }
+ queryBuilder.append(aliveUsers.get(i));
+ if (i == aliveUsers.size() - 1) {
+ queryBuilder.append(")");
+ }
+ }
+ int deletedRows = db.delete(TABLE_NAME, queryBuilder.toString(), new String[]{});
+ Slogf.i(TAG, "Deleted %d user package settings db rows while syncing with alive users",
+ deletedRows);
+ }
}
/** Defines the I/O usage entry stored in the IoUsageStatsTable. */
diff --git a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
index 5c5029b..3b709b5 100644
--- a/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
+++ b/tests/EmbeddedKitchenSinkApp/AndroidManifest.xml
@@ -46,6 +46,8 @@
<uses-permission android:name="android.car.permission.CAR_VENDOR_EXTENSION"/>
<!-- use for CarServiceTest -->
<uses-permission android:name="android.car.permission.CAR_UX_RESTRICTIONS_CONFIGURATION"/>
+ <!-- use for AndroidCarApiTest -->
+ <uses-permission android:name="android.car.permission.CONTROL_CAR_APP_LAUNCH"/>
<uses-permission android:name="android.car.permission.CONTROL_CAR_CLIMATE"/>
<uses-permission android:name="android.car.permission.READ_CAR_STEERING"/>
<uses-permission android:name="android.car.permission.STORAGE_MONITORING"/>
diff --git a/tests/EmbeddedKitchenSinkApp/res/layout/car_telemetry_test.xml b/tests/EmbeddedKitchenSinkApp/res/layout/car_telemetry_test.xml
index 3110d82..39c53fa 100644
--- a/tests/EmbeddedKitchenSinkApp/res/layout/car_telemetry_test.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/layout/car_telemetry_test.xml
@@ -19,47 +19,64 @@
android:layout_height="match_parent"
android:orientation="vertical">
<LinearLayout
- android:id="@+id/on_gear_change_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/gear_change_config"/>
<Button
android:id="@+id/send_on_gear_change_config"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/send_on_gear_change"/>
+ android:text="@string/add_metrics_config"/>
<Button
android:id="@+id/remove_on_gear_change_config"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/remove_on_gear_change"/>
+ android:text="@string/remove_metrics_config"/>
<Button
android:id="@+id/get_on_gear_change_report"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/get_on_gear_change"/>
+ android:text="@string/get_report"/>
</LinearLayout>
<LinearLayout
- android:id="@+id/on_process_memory_state_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/process_memory_config"/>
<Button
android:id="@+id/send_on_process_memory_config"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/send_process_memory_state_config"/>
+ android:text="@string/add_metrics_config"/>
<Button
android:id="@+id/remove_on_process_memory_config"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/remove_process_memory_state_config"/>
+ android:text="@string/remove_metrics_config"/>
<Button
android:id="@+id/get_on_process_memory_report"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:text="@string/get_report_process_memory_state_config"/>
+ android:text="@string/get_report"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+ <Button
+ android:id="@+id/show_mem_info_btn"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="@string/show_mem_info"/>
</LinearLayout>
<TextView
diff --git a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
index 77e3989..4fcb9f3 100644
--- a/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
+++ b/tests/EmbeddedKitchenSinkApp/res/values/strings.xml
@@ -383,10 +383,10 @@
<string name="cancel" translatable="false">Cancel</string>
<!-- CarTelemetryService Test -->
- <string name="send_on_gear_change" translatable="false">Send MetricsConfig on_gear_change</string>
- <string name="remove_on_gear_change" translatable="false">Remove MetricsConfig on_gear_change</string>
- <string name="get_on_gear_change" translatable="false">Get Report on_gear_change</string>
- <string name="send_process_memory_state_config" translatable="false">Send MetricsConfig process_memory</string>
- <string name="remove_process_memory_state_config" translatable="false">Remove MetricsConfig process_memory</string>
- <string name="get_report_process_memory_state_config" translatable="false">Get Report process_memory</string>
+ <string name="gear_change_config" translate="false">on_gear_change:</string>
+ <string name="process_memory_config" translate="false">process_memory:</string>
+ <string name="add_metrics_config" translatable="false">Add MetricsConfig</string>
+ <string name="remove_metrics_config" translatable="false">Remove MetricsConfig</string>
+ <string name="get_report" translatable="false">Get Report</string>
+ <string name="show_mem_info" translate="false">Show Memory Info</string>
</resources>
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/telemetry/CarTelemetryTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/telemetry/CarTelemetryTestFragment.java
index 5a76abc..5f548ed 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/telemetry/CarTelemetryTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/telemetry/CarTelemetryTestFragment.java
@@ -20,6 +20,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.app.ActivityManager;
import android.car.telemetry.CarTelemetryManager;
import android.car.telemetry.MetricsConfigKey;
import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
@@ -28,7 +29,6 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.Button;
import android.widget.TextView;
import androidx.fragment.app.Fragment;
@@ -41,6 +41,8 @@
import java.io.ByteArrayInputStream;
import java.io.IOException;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@@ -146,30 +148,30 @@
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.car_telemetry_test, container, false);
-
mOutputTextView = view.findViewById(R.id.output_textview);
- Button sendGearConfigBtn = view.findViewById(R.id.send_on_gear_change_config);
- Button getGearReportBtn = view.findViewById(R.id.get_on_gear_change_report);
- Button removeGearConfigBtn = view.findViewById(R.id.remove_on_gear_change_config);
-
- sendGearConfigBtn.setOnClickListener(this::onSendGearChangeConfigBtnClick);
- removeGearConfigBtn.setOnClickListener(this::onRemoveGearChangeConfigBtnClick);
- getGearReportBtn.setOnClickListener(this::onGetGearChangeReportBtnClick);
-
- mOutputTextView = view.findViewById(R.id.output_textview);
- Button sendProcessMemConfigBtn = view.findViewById(R.id.send_on_process_memory_config);
- Button getProcessMemReportBtn = view.findViewById(R.id.get_on_process_memory_report);
- Button removeProcessMemConfigBtn = view.findViewById(R.id.remove_on_process_memory_config);
-
- sendProcessMemConfigBtn.setOnClickListener(this::onSendProcessMemoryConfigBtnClick);
- removeProcessMemConfigBtn.setOnClickListener(this::onRemoveProcessMemoryConfigBtnClick);
- getProcessMemReportBtn.setOnClickListener(this::onGetProcessMemoryReportBtnClick);
-
+ view.findViewById(R.id.send_on_gear_change_config)
+ .setOnClickListener(this::onSendGearChangeConfigBtnClick);
+ view.findViewById(R.id.remove_on_gear_change_config)
+ .setOnClickListener(this::onRemoveGearChangeConfigBtnClick);
+ view.findViewById(R.id.get_on_gear_change_report)
+ .setOnClickListener(this::onGetGearChangeReportBtnClick);
+ view.findViewById(R.id.send_on_process_memory_config)
+ .setOnClickListener(this::onSendProcessMemoryConfigBtnClick);
+ view.findViewById(R.id.remove_on_process_memory_config)
+ .setOnClickListener(this::onRemoveProcessMemoryConfigBtnClick);
+ view.findViewById(R.id.get_on_process_memory_report)
+ .setOnClickListener(this::onGetProcessMemoryReportBtnClick);
+ view.findViewById(R.id.show_mem_info_btn).setOnClickListener(this::onShowMemInfoBtnClick);
return view;
}
private void showOutput(String s) {
- mActivity.runOnUiThread(() -> mOutputTextView.setText(s));
+ mActivity.runOnUiThread(() -> {
+ String now = LocalDateTime.now()
+ .format(DateTimeFormatter.ofPattern("HH:mm:ss.SSS"));
+ mOutputTextView.setText(
+ now + " : " + s + "\n" + mOutputTextView.getText());
+ });
}
private void onSendGearChangeConfigBtnClick(View view) {
@@ -184,13 +186,13 @@
}
private void onGetGearChangeReportBtnClick(View view) {
- showOutput("Fetching report... If nothing shows up after a few seconds, "
- + "then no result exists");
+ showOutput("Fetching report for on_gear_change... If nothing shows up within 5 seconds, "
+ + "there is no result yet");
mCarTelemetryManager.sendFinishedReports(ON_GEAR_CHANGE_KEY_V1);
}
private void onSendProcessMemoryConfigBtnClick(View view) {
- showOutput("Sending MetricsConfig that listen for process memory state...");
+ showOutput("Sending MetricsConfig that listens for process memory state...");
mCarTelemetryManager.addMetricsConfig(PROCESS_MEMORY_KEY_V1,
METRICS_CONFIG_PROCESS_MEMORY_V1.toByteArray());
}
@@ -201,11 +203,27 @@
}
private void onGetProcessMemoryReportBtnClick(View view) {
- showOutput("Fetching report for process memory state... If nothing shows up after "
- + "a few seconds, then no result exists");
+ showOutput("Fetching report for process memory state... If nothing shows up within 5 "
+ + "seconds, there is no result yet");
mCarTelemetryManager.sendFinishedReports(PROCESS_MEMORY_KEY_V1);
}
+ /** Gets a MemoryInfo object for the device's current memory status. */
+ private ActivityManager.MemoryInfo getAvailableMemory() {
+ ActivityManager activityManager = getActivity().getSystemService(ActivityManager.class);
+ ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
+ activityManager.getMemoryInfo(memoryInfo);
+ return memoryInfo;
+ }
+
+ private void onShowMemInfoBtnClick(View view) {
+ // Use android's "alloc-stress" system tool to create an artificial memory pressure.
+ ActivityManager.MemoryInfo info = getAvailableMemory();
+ showOutput("MemoryInfo availMem=" + (info.availMem / 1024 / 1024) + "/"
+ + (info.totalMem / 1024 / 1024) + "mb, isLowMem=" + info.lowMemory
+ + ", threshold=" + (info.threshold / 1024 / 1024) + "mb");
+ }
+
@Override
public void onDestroyView() {
super.onDestroyView();
@@ -227,7 +245,7 @@
} catch (IOException e) {
bundle = null;
}
- showOutput("Result is " + bundle.toString());
+ showOutput("Result for " + key.getName() + ": " + bundle.toString());
}
@Override
@@ -235,16 +253,34 @@
try {
TelemetryProto.TelemetryError telemetryError =
TelemetryProto.TelemetryError.parseFrom(error);
- showOutput("Error is " + telemetryError);
+ showOutput("Error for " + key.getName() + ": " + telemetryError);
} catch (InvalidProtocolBufferException e) {
showOutput("Unable to parse error result for MetricsConfig " + key.getName()
- + ". " + e.getMessage());
+ + ": " + e.getMessage());
}
}
@Override
public void onAddMetricsConfigStatus(@NonNull MetricsConfigKey key, int statusCode) {
- showOutput("Add MetricsConfig status: " + statusCode);
+ showOutput("Add MetricsConfig status for " + key.getName() + ": "
+ + statusCodeToString(statusCode));
+ }
+
+ private String statusCodeToString(int statusCode) {
+ switch (statusCode) {
+ case CarTelemetryManager.ERROR_METRICS_CONFIG_NONE:
+ return "SUCCESS";
+ case CarTelemetryManager.ERROR_METRICS_CONFIG_ALREADY_EXISTS:
+ return "ERROR ALREADY_EXISTS";
+ case CarTelemetryManager.ERROR_METRICS_CONFIG_VERSION_TOO_OLD:
+ return "ERROR VERSION_TOO_OLD";
+ case CarTelemetryManager.ERROR_METRICS_CONFIG_PARSE_FAILED:
+ return "ERROR PARSE_FAILED";
+ case CarTelemetryManager.ERROR_METRICS_CONFIG_SIGNATURE_VERIFICATION_FAILED:
+ return "ERROR SIGNATURE_VERIFICATION_FAILED";
+ default:
+ return "ERROR UNKNOWN";
+ }
}
}
}
diff --git a/tests/android_car_api_test/src/android/car/apitest/CarActivityManagerTest.java b/tests/android_car_api_test/src/android/car/apitest/CarActivityManagerTest.java
new file mode 100644
index 0000000..f1d90be
--- /dev/null
+++ b/tests/android_car_api_test/src/android/car/apitest/CarActivityManagerTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.apitest;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.car.Car;
+import android.car.app.CarActivityManager;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.Display;
+
+import org.junit.Before;
+import org.junit.Test;
+
+@MediumTest
+public class CarActivityManagerTest extends CarApiTestBase {
+ private static final String TAG = CarActivityManagerTest.class.getSimpleName();
+
+ // Comes from android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER
+ private static final int FEATURE_DEFAULT_TASK_CONTAINER = 1;
+
+ // Comes from android.window.DisplayAreaOrganizer.FEATURE_UNDEFINED
+ private static final int FEATURE_UNDEFINED = -1;
+
+ private CarActivityManager mCarActivityManager;
+
+ private final ComponentName mTestActivity = new ComponentName("test.pkg", "test.activity");
+
+ @Before
+ public void setUp() throws Exception {
+ mCarActivityManager = (CarActivityManager) getCar().getCarManager(Car.CAR_ACTIVITY_SERVICE);
+ assertThat(mCarActivityManager).isNotNull();
+ }
+
+ @Test
+ public void testSetPersistentActivity() {
+ // Set
+ int retSet = mCarActivityManager.setPersistentActivity(
+ mTestActivity, Display.DEFAULT_DISPLAY, FEATURE_DEFAULT_TASK_CONTAINER);
+ assertThat(retSet).isEqualTo(CarActivityManager.RESULT_SUCCESS);
+
+ // Remove
+ int retRemove = mCarActivityManager.setPersistentActivity(
+ mTestActivity, Display.DEFAULT_DISPLAY, FEATURE_UNDEFINED);
+ assertThat(retRemove).isEqualTo(CarActivityManager.RESULT_SUCCESS);
+ }
+
+ @Test
+ public void testSetPersistentActivity_throwsExceptionForInvalidDisplayId() {
+ int invalidDisplayId = 999999990;
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarActivityManager.setPersistentActivity(
+ mTestActivity, invalidDisplayId, FEATURE_DEFAULT_TASK_CONTAINER));
+ }
+
+ @Test
+ public void testSetPersistentActivity_throwsExceptionForInvalidFeatureId() {
+ int unknownFeatureId = 999999990;
+
+ assertThrows(IllegalArgumentException.class,
+ () -> mCarActivityManager.setPersistentActivity(
+ mTestActivity, Display.DEFAULT_DISPLAY, unknownFeatureId));
+ }
+
+ @Test
+ public void testSetPersistentActivity_throwsExceptionForUnknownActivity() {
+ // Tries to remove the Activity without registering it.
+ assertThrows(ActivityNotFoundException.class,
+ () -> mCarActivityManager.setPersistentActivity(
+ mTestActivity, Display.DEFAULT_DISPLAY, FEATURE_UNDEFINED));
+ }
+}
diff --git a/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java b/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
index ba01d41..9a24a7e 100644
--- a/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/watchdog/CarWatchdogServiceTest.java
@@ -77,8 +77,8 @@
private final Executor mExecutor =
InstrumentationRegistry.getInstrumentation().getTargetContext().getMainExecutor();
private final UserInfo[] mUserInfos = new UserInfo[] {
- new UserInfoBuilder(10).setName("user 1").build(),
- new UserInfoBuilder(11).setName("user 2").build()
+ new UserInfoBuilder(100).setName("user 1").build(),
+ new UserInfoBuilder(101).setName("user 2").build()
};
@Mock private Context mMockContext;
@@ -101,8 +101,8 @@
when(mServiceBinder.queryLocalInterface(anyString())).thenReturn(mCarWatchdogService);
when(mMockContext.getSystemService(Context.USER_SERVICE)).thenReturn(mUserManager);
mockUmGetAllUsers(mUserManager, mUserInfos);
- mockUmIsUserRunning(mUserManager, 10, true);
- mockUmIsUserRunning(mUserManager, 11, false);
+ mockUmIsUserRunning(mUserManager, 100, true);
+ mockUmIsUserRunning(mUserManager, 101, false);
mCarWatchdogService.init();
mWatchdogServiceForSystemImpl = registerCarWatchdogService();
@@ -224,11 +224,11 @@
}
private void expectRunningUser() {
- doReturn(10).when(() -> UserHandle.getUserId(Binder.getCallingUid()));
+ doReturn(100).when(() -> UserHandle.getUserId(Binder.getCallingUid()));
}
private void expectStoppedUser() {
- doReturn(11).when(() -> UserHandle.getUserId(Binder.getCallingUid()));
+ doReturn(101).when(() -> UserHandle.getUserId(Binder.getCallingUid()));
}
private final class TestClient {
diff --git a/tests/carservice_unit_test/src/com/android/car/AbstractICarServiceHelperStub.java b/tests/carservice_unit_test/src/com/android/car/AbstractICarServiceHelperStub.java
index 7304e23..5d387cc 100644
--- a/tests/carservice_unit_test/src/com/android/car/AbstractICarServiceHelperStub.java
+++ b/tests/carservice_unit_test/src/com/android/car/AbstractICarServiceHelperStub.java
@@ -16,6 +16,7 @@
package com.android.car;
import android.annotation.UserIdInt;
+import android.car.app.CarActivityManager;
import android.content.ComponentName;
import android.content.pm.UserInfo;
import android.os.RemoteException;
@@ -73,4 +74,11 @@
+ ", flags=" + flags + ")");
return null;
}
+
+ @Override
+ public int setPersistentActivity(ComponentName activity, int displayId, int featureId) {
+ Log.d(TAG, "setPersistentActivity(activity=" + activity.toShortString()
+ + ", displayId=" + displayId + ", featureId=" + featureId + ")");
+ return CarActivityManager.RESULT_SUCCESS;
+ }
}
diff --git a/tests/carservice_unit_test/src/com/android/car/am/CarActivityServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/am/CarActivityServiceUnitTest.java
new file mode 100644
index 0000000..4051686
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/am/CarActivityServiceUnitTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.am;
+
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.testng.Assert.assertThrows;
+
+import android.car.Car;
+import android.car.app.CarActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.RemoteException;
+import android.os.UserHandle;
+
+import com.android.car.internal.ICarServiceHelper;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class CarActivityServiceUnitTest {
+
+ // Comes from android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER
+ private static final int FEATURE_DEFAULT_TASK_CONTAINER = 1;
+
+ private CarActivityService mCarActivityService;
+
+ private final ComponentName mTestActivity = new ComponentName("test.pkg", "test.activity");
+
+ @Rule
+ public TestName mTestName = new TestName();
+ @Mock
+ private Context mContext;
+ @Mock
+ private ICarServiceHelper mICarServiceHelper;
+
+ @Before
+ public void setUp() {
+ mCarActivityService = spy(new CarActivityService(mContext));
+
+ int nonCurrentUserId = 9999990;
+ boolean isNonCurrentUserTest = mTestName.getMethodName().contains("NonCurrentUser");
+ int callerId = isNonCurrentUserTest ? nonCurrentUserId : UserHandle.USER_SYSTEM;
+ when(mCarActivityService.getCaller()).thenReturn(callerId);
+ }
+
+ @Test
+ public void setPersistentActivityThrowsException_ifICarServiceHelperIsNotSet() {
+ assertThrows(IllegalStateException.class,
+ () -> mCarActivityService.setPersistentActivity(
+ mTestActivity, DEFAULT_DISPLAY, FEATURE_DEFAULT_TASK_CONTAINER));
+ }
+
+ @Test
+ public void setPersistentActivityThrowsException_withoutPermission() {
+ mCarActivityService.setICarServiceHelper(mICarServiceHelper);
+ when(mContext.checkCallingOrSelfPermission(eq(Car.PERMISSION_CONTROL_CAR_APP_LAUNCH)))
+ .thenReturn(PackageManager.PERMISSION_DENIED);
+
+ assertThrows(SecurityException.class,
+ () -> mCarActivityService.setPersistentActivity(
+ mTestActivity, DEFAULT_DISPLAY, FEATURE_DEFAULT_TASK_CONTAINER));
+ }
+
+ @Test
+ public void setPersistentActivityInvokesICarServiceHelper() throws RemoteException {
+ int displayId = 9;
+
+ mCarActivityService.setICarServiceHelper(mICarServiceHelper);
+
+ int ret = mCarActivityService.setPersistentActivity(
+ mTestActivity, displayId, FEATURE_DEFAULT_TASK_CONTAINER);
+ assertThat(ret).isEqualTo(CarActivityManager.RESULT_SUCCESS);
+
+ ArgumentCaptor<ComponentName> activityCaptor = ArgumentCaptor.forClass(ComponentName.class);
+ ArgumentCaptor<Integer> displayIdCaptor = ArgumentCaptor.forClass(Integer.class);
+ ArgumentCaptor<Integer> featureIdCaptor = ArgumentCaptor.forClass(Integer.class);
+ verify(mICarServiceHelper).setPersistentActivity(
+ activityCaptor.capture(), displayIdCaptor.capture(), featureIdCaptor.capture());
+
+ assertThat(activityCaptor.getValue()).isEqualTo(mTestActivity);
+ assertThat(displayIdCaptor.getValue()).isEqualTo(displayId);
+ assertThat(featureIdCaptor.getValue()).isEqualTo(FEATURE_DEFAULT_TASK_CONTAINER);
+ }
+
+ @Test
+ public void setPersistentActivityReturnsErrorForNonCurrentUser() throws RemoteException {
+ mCarActivityService.setICarServiceHelper(mICarServiceHelper);
+
+ int ret = mCarActivityService.setPersistentActivity(
+ mTestActivity, DEFAULT_DISPLAY, FEATURE_DEFAULT_TASK_CONTAINER);
+ assertThat(ret).isEqualTo(CarActivityManager.RESULT_INVALID_USER);
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/MockedPowerHalService.java b/tests/carservice_unit_test/src/com/android/car/hal/MockedPowerHalService.java
index 2ab14dc..fd15435 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/MockedPowerHalService.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/MockedPowerHalService.java
@@ -49,6 +49,7 @@
mock(UserHalService.class),
mock(DiagnosticHalService.class),
mock(ClusterHalService.class),
+ mock(TimeHalService.class),
mock(HalClient.class),
CarServiceUtils.getHandlerThread(VehicleHal.class.getSimpleName()));
return mockedVehicleHal;
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/TimeHalServiceTest.java b/tests/carservice_unit_test/src/com/android/car/hal/TimeHalServiceTest.java
new file mode 100644
index 0000000..adc5153
--- /dev/null
+++ b/tests/carservice_unit_test/src/com/android/car/hal/TimeHalServiceTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car.hal;
+
+import static android.hardware.automotive.vehicle.V2_0.VehicleProperty.EPOCH_TIME;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.verify;
+
+import android.car.test.util.BroadcastingFakeContext;
+import android.car.test.util.VehicleHalTestingHelper;
+import android.content.Intent;
+import android.hardware.automotive.vehicle.V2_0.VehicleArea;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropertyStatus;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.Collections;
+
+@RunWith(MockitoJUnitRunner.class)
+public final class TimeHalServiceTest {
+
+ private static final VehiclePropConfig ANDROID_TIME_PROP =
+ VehicleHalTestingHelper.newConfig(EPOCH_TIME);
+
+ @Mock private VehicleHal mVehicleHal;
+ private BroadcastingFakeContext mFakeContext;
+
+ private TimeHalService mTimeHalService;
+
+ @Before
+ public void setUp() {
+ mFakeContext = new BroadcastingFakeContext();
+ mTimeHalService = new TimeHalService(mFakeContext, mVehicleHal);
+ }
+
+ @Test
+ public void testInitDoesNothing() {
+ mTimeHalService.takeProperties(Collections.emptyList());
+
+ mTimeHalService.init();
+
+ mFakeContext.verifyReceiverNotRegistered();
+ }
+
+ @Test
+ public void testInitRegistersBroadcastReceiver() {
+ mTimeHalService.takeProperties(Collections.singletonList(ANDROID_TIME_PROP));
+
+ mTimeHalService.init();
+
+ assertThat(mTimeHalService.isAndroidTimeSupported()).isTrue();
+ mFakeContext.verifyReceiverRegistered(Intent.ACTION_TIME_CHANGED);
+ }
+
+ @Test
+ public void testInitSendsAndroidTimeUpdate() {
+ mTimeHalService.takeProperties(Collections.singletonList(ANDROID_TIME_PROP));
+ long sysTimeMillis = System.currentTimeMillis();
+
+ mTimeHalService.init();
+
+ assertThat(mTimeHalService.isAndroidTimeSupported()).isTrue();
+ ArgumentCaptor<VehiclePropValue> captor = ArgumentCaptor.forClass(VehiclePropValue.class);
+ verify(mVehicleHal).set(captor.capture());
+ VehiclePropValue propValue = captor.getValue();
+ assertThat(propValue.prop).isEqualTo(EPOCH_TIME);
+ assertThat(propValue.areaId).isEqualTo(VehicleArea.GLOBAL);
+ assertThat(propValue.status).isEqualTo(VehiclePropertyStatus.AVAILABLE);
+ assertThat(propValue.timestamp).isAtLeast(sysTimeMillis);
+ assertThat(propValue.value.int64Values).hasSize(1);
+ assertThat(propValue.value.int64Values.get(0)).isAtLeast(sysTimeMillis);
+ }
+
+ @Test
+ public void testReleaseUnregistersBroadcastReceiver() {
+ mTimeHalService.takeProperties(Collections.singletonList(ANDROID_TIME_PROP));
+ mTimeHalService.init();
+ clearInvocations(mVehicleHal);
+
+ mTimeHalService.release();
+
+ mFakeContext.verifyReceiverNotRegistered();
+ assertThat(mTimeHalService.isAndroidTimeSupported()).isFalse();
+ }
+
+ @Test
+ public void testSendsAndroidTimeUpdateWhenBroadcast() {
+ mTimeHalService.takeProperties(Collections.singletonList(ANDROID_TIME_PROP));
+ mTimeHalService.init();
+ clearInvocations(mVehicleHal);
+ long sysTimeMillis = System.currentTimeMillis();
+
+ mFakeContext.sendBroadcast(new Intent(Intent.ACTION_TIME_CHANGED));
+
+ ArgumentCaptor<VehiclePropValue> captor = ArgumentCaptor.forClass(VehiclePropValue.class);
+ verify(mVehicleHal).set(captor.capture());
+ VehiclePropValue propValue = captor.getValue();
+ assertThat(propValue.prop).isEqualTo(EPOCH_TIME);
+ assertThat(propValue.areaId).isEqualTo(VehicleArea.GLOBAL);
+ assertThat(propValue.status).isEqualTo(VehiclePropertyStatus.AVAILABLE);
+ assertThat(propValue.timestamp).isAtLeast(sysTimeMillis);
+ assertThat(propValue.value.int64Values).hasSize(1);
+ assertThat(propValue.value.int64Values.get(0)).isAtLeast(sysTimeMillis);
+ }
+}
diff --git a/tests/carservice_unit_test/src/com/android/car/hal/VehicleHalTest.java b/tests/carservice_unit_test/src/com/android/car/hal/VehicleHalTest.java
index 4aa9a81..2a39046 100644
--- a/tests/carservice_unit_test/src/com/android/car/hal/VehicleHalTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/hal/VehicleHalTest.java
@@ -71,6 +71,7 @@
@Mock private UserHalService mUserHalService;
@Mock private DiagnosticHalService mDiagnosticHalService;
@Mock private ClusterHalService mClusterHalService;
+ @Mock private TimeHalService mTimeHalService;
@Mock private HalClient mHalClient;
private final HandlerThread mHandlerThread = CarServiceUtils.getHandlerThread(
@@ -86,7 +87,8 @@
public void setUp() throws Exception {
mVehicleHal = new VehicleHal(mPowerHalService,
mPropertyHalService, mInputHalService, mVmsHalService, mUserHalService,
- mDiagnosticHalService, mClusterHalService, mHalClient, mHandlerThread);
+ mDiagnosticHalService, mClusterHalService, mTimeHalService, mHalClient,
+ mHandlerThread);
mConfigs.clear();
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/CarTelemetrydPublisherTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/CarTelemetrydPublisherTest.java
index bc3cd47..36edb67 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/CarTelemetrydPublisherTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/CarTelemetrydPublisherTest.java
@@ -48,6 +48,8 @@
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
+import java.util.List;
+
@RunWith(MockitoJUnitRunner.class)
public class CarTelemetrydPublisherTest extends AbstractExtendedMockitoTestCase {
private static final String SERVICE_NAME = ICarTelemetryInternal.DESCRIPTOR + "/default";
@@ -66,7 +68,10 @@
@Captor private ArgumentCaptor<IBinder.DeathRecipient> mLinkToDeathCallbackCaptor;
+ // These 2 variables are set in onPublisherFailure() callback.
@Nullable private Throwable mPublisherFailure;
+ @Nullable private List<TelemetryProto.MetricsConfig> mFailedConfigs;
+
private FakeCarTelemetryInternal mFakeCarTelemetryInternal;
private CarTelemetrydPublisher mPublisher;
@@ -165,6 +170,7 @@
assertThat(mFakeCarTelemetryInternal.mSetListenerCallCount).isEqualTo(1);
assertThat(mPublisherFailure).hasMessageThat()
.contains("ICarTelemetryInternal binder died");
+ assertThat(mFailedConfigs).hasSize(1); // got all the failed configs
}
@Test
@@ -175,10 +181,13 @@
assertThat(mPublisherFailure).hasMessageThat()
.contains("Cannot set CarData listener");
+ assertThat(mFailedConfigs).hasSize(1);
}
- private void onPublisherFailure(AbstractPublisher publisher, Throwable error) {
+ private void onPublisherFailure(AbstractPublisher publisher,
+ List<TelemetryProto.MetricsConfig> affectedConfigs, Throwable error) {
mPublisherFailure = error;
+ mFailedConfigs = affectedConfigs;
}
private static class FakeCarTelemetryInternal implements ICarTelemetryInternal {
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/StatsPublisherTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/StatsPublisherTest.java
index 2ed12bd..2a3b88a 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/StatsPublisherTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/StatsPublisherTest.java
@@ -31,6 +31,8 @@
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -39,6 +41,7 @@
import android.os.Looper;
import android.os.Message;
import android.os.PersistableBundle;
+import android.os.Process;
import android.os.SystemClock;
import com.android.car.telemetry.AtomsProto.AppStartMemoryStateCaptured;
@@ -72,6 +75,7 @@
import java.io.FileInputStream;
import java.nio.file.Files;
import java.util.Arrays;
+import java.util.List;
@RunWith(MockitoJUnitRunner.class)
public class StatsPublisherTest {
@@ -103,9 +107,10 @@
.addSubscribers(SUBSCRIBER_2)
.build();
- private static final long SUBSCRIBER_1_HASH = -8101507323446050791L; // Used as ID.
- private static final long SUBSCRIBER_2_HASH = 2778197004730583271L; // Used as ID.
+ private static final long SUBSCRIBER_1_HASH = -8101507323446050791L; // Used as configKey.
+ private static final long SUBSCRIBER_2_HASH = 2778197004730583271L; // Used as configKey.
+ // This StatsdConfig is generated for SUBSCRIBER_1.
private static final StatsdConfigProto.StatsdConfig STATSD_CONFIG_1 =
StatsdConfigProto.StatsdConfig.newBuilder()
.setId(SUBSCRIBER_1_HASH)
@@ -121,6 +126,7 @@
.addAllowedLogSource("AID_SYSTEM")
.build();
+ // This StatsdConfig is generated for SUBSCRIBER_2.
private static final StatsdConfigProto.StatsdConfig STATSD_CONFIG_2 =
StatsdConfigProto.StatsdConfig.newBuilder()
.setId(SUBSCRIBER_2_HASH)
@@ -174,7 +180,7 @@
.setValueInt(234))
.build();
- private static final StatsLogProto.ConfigMetricsReportList STATS_REPORT =
+ private static final StatsLogProto.ConfigMetricsReportList METRICS_REPORT =
StatsLogProto.ConfigMetricsReportList.newBuilder()
.addReports(ConfigMetricsReport.newBuilder()
.addMetrics(StatsLogReport.newBuilder()
@@ -194,7 +200,22 @@
.setField(1))))))
.build();
- private static final StatsLogProto.ConfigMetricsReportList EMPTY_STATS_REPORT =
+ // By default the test assumes all the StatsdConfigs are valid.
+ private static final StatsLogProto.StatsdStatsReport CONFIG_STATS_REPORT =
+ StatsLogProto.StatsdStatsReport.newBuilder()
+ .addConfigStats(StatsLogProto.StatsdStatsReport.ConfigStats.newBuilder()
+ // in unit tests UID of test and app are the same
+ .setUid(Process.myUid())
+ .setId(SUBSCRIBER_1_HASH) // id is the same as configKey
+ .setIsValid(true))
+ .addConfigStats(StatsLogProto.StatsdStatsReport.ConfigStats.newBuilder()
+ // in unit tests UID of test and app are the same
+ .setUid(Process.myUid())
+ .setId(SUBSCRIBER_2_HASH) // id is the same as configKey
+ .setIsValid(true))
+ .build();
+
+ private static final StatsLogProto.ConfigMetricsReportList EMPTY_METRICS_REPORT =
StatsLogProto.ConfigMetricsReportList.newBuilder().build();
private static final DataSubscriber DATA_SUBSCRIBER_1 =
@@ -205,7 +226,10 @@
private File mRootDirectory;
private StatsPublisher mPublisher; // subject
+
+ // These 2 variables are set in onPublisherFailure() callback. Defaults to null.
private Throwable mPublisherFailure;
+ private List<TelemetryProto.MetricsConfig> mFailedConfigs;
@Mock private StatsManagerProxy mStatsManager;
@@ -215,6 +239,7 @@
public void setUp() throws Exception {
mRootDirectory = Files.createTempDirectory("telemetry_test").toFile();
mPublisher = createRestartedPublisher();
+ when(mStatsManager.getStatsMetadata()).thenReturn(CONFIG_STATS_REPORT.toByteArray());
}
/**
@@ -326,6 +351,7 @@
mPublisher.addDataSubscriber(DATA_SUBSCRIBER_1);
assertThat(mPublisherFailure).hasMessageThat().contains("Failed to add config");
+ assertThat(mFailedConfigs).hasSize(1); // got all the failed configs
}
@Test
@@ -350,7 +376,7 @@
public void testAfterDispatchItSchedulesANewPullReportTask() throws Exception {
mPublisher.addDataSubscriber(DATA_SUBSCRIBER_1);
Message firstMessage = mFakeHandlerWrapper.getQueuedMessages().get(0);
- when(mStatsManager.getReports(anyLong())).thenReturn(EMPTY_STATS_REPORT.toByteArray());
+ when(mStatsManager.getReports(anyLong())).thenReturn(EMPTY_METRICS_REPORT.toByteArray());
mFakeHandlerWrapper.dispatchQueuedMessages();
@@ -373,7 +399,7 @@
when(subscriber2.getMetricsConfig()).thenReturn(METRICS_CONFIG);
when(subscriber2.getPublisherParam()).thenReturn(SUBSCRIBER_2.getPublisher());
mPublisher.addDataSubscriber(subscriber2);
- when(mStatsManager.getReports(anyLong())).thenReturn(STATS_REPORT.toByteArray());
+ when(mStatsManager.getReports(anyLong())).thenReturn(METRICS_REPORT.toByteArray());
mFakeHandlerWrapper.dispatchQueuedMessages();
@@ -393,15 +419,43 @@
.asList().containsExactly(445678901L);
}
+ @Test
+ public void testOnInvalidConfig_notifiesPublisherFailureListener() throws Exception {
+ DataSubscriber subscriber = spy(new DataSubscriber(null, METRICS_CONFIG, SUBSCRIBER_1));
+ mPublisher.addDataSubscriber(subscriber);
+ reset(mStatsManager);
+ when(mStatsManager.getStatsMetadata()).thenReturn(
+ StatsLogProto.StatsdStatsReport.newBuilder()
+ .addConfigStats(StatsLogProto.StatsdStatsReport.ConfigStats.newBuilder()
+ // in unit tests UID of test and app are the same
+ .setUid(Process.myUid())
+ .setId(SUBSCRIBER_1_HASH) // id is the same as configKey
+ .setIsValid(false))
+ .build().toByteArray());
+ when(mStatsManager.getReports(anyLong())).thenReturn(EMPTY_METRICS_REPORT.toByteArray());
+
+ mFakeHandlerWrapper.dispatchQueuedMessages();
+
+ // subscriber shouldn't get data, because of EMPTY_METRICS_REPORT.
+ verify(subscriber, times(0)).push(any());
+ assertThat(mFailedConfigs).containsExactly(METRICS_CONFIG);
+ assertThat(mPublisherFailure).hasMessageThat().contains("Found invalid configs");
+ }
+
private PersistableBundle getSavedStatsConfigs() throws Exception {
File savedConfigsFile = new File(mRootDirectory, StatsPublisher.SAVED_STATS_CONFIGS_FILE);
+ if (!savedConfigsFile.exists()) {
+ return new PersistableBundle();
+ }
try (FileInputStream fileInputStream = new FileInputStream(savedConfigsFile)) {
return PersistableBundle.readFromStream(fileInputStream);
}
}
- private void onPublisherFailure(AbstractPublisher publisher, Throwable error) {
+ private void onPublisherFailure(AbstractPublisher publisher,
+ List<TelemetryProto.MetricsConfig> affectedConfigs, Throwable error) {
mPublisherFailure = error;
+ mFailedConfigs = affectedConfigs;
}
private static void assertThatMessageIsScheduledWithGivenDelay(Message msg, long delayMillis) {
diff --git a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/VehiclePropertyPublisherTest.java b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/VehiclePropertyPublisherTest.java
index 508120f..39009bd 100644
--- a/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/VehiclePropertyPublisherTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/telemetry/publisher/VehiclePropertyPublisherTest.java
@@ -204,5 +204,6 @@
// PersistableBundle object.
}
- private void onPublisherFailure(AbstractPublisher publisher, Throwable error) { }
+ private void onPublisherFailure(AbstractPublisher publisher,
+ List<TelemetryProto.MetricsConfig> affectedConfigs, Throwable error) { }
}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
index c1e3938..6734322 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/CarWatchdogServiceUnitTest.java
@@ -18,8 +18,8 @@
import static android.automotive.watchdog.internal.ResourceOveruseActionType.KILLED;
import static android.automotive.watchdog.internal.ResourceOveruseActionType.NOT_KILLED;
-import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetAliveUsers;
import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetAllUsers;
+import static android.car.test.mocks.AndroidMockitoHelper.mockUmGetUserHandles;
import static android.car.watchdog.CarWatchdogManager.TIMEOUT_CRITICAL;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED;
@@ -283,6 +283,15 @@
}
@Test
+ public void testUserRemovedBroadcast() throws Exception {
+ mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 101, 102);
+ mBroadcastReceiver.onReceive(mMockContext,
+ new Intent().setAction(Intent.ACTION_USER_REMOVED)
+ .putExtra(Intent.EXTRA_USER, UserHandle.of(100)));
+ verify(mMockWatchdogStorage).syncUsers(new int[] {101, 102});
+ }
+
+ @Test
public void testGetResourceOveruseStats() throws Exception {
int uid = Binder.getCallingUid();
injectPackageInfos(Collections.singletonList(
@@ -1001,7 +1010,7 @@
@Test
public void testSetKillablePackageAsUser() throws Exception {
- mockUmGetAliveUsers(mMockUserManager, 11, 12);
+ mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12);
injectPackageInfos(Arrays.asList(
constructPackageManagerPackageInfo("third_party_package", 1103456, null),
constructPackageManagerPackageInfo("vendor_package.critical", 1101278, null),
@@ -1029,7 +1038,7 @@
() -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
userHandle, /* isKillable= */ true));
- mockUmGetAliveUsers(mMockUserManager, 11, 12, 13);
+ mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12, 13);
injectPackageInfos(Collections.singletonList(
constructPackageManagerPackageInfo("third_party_package", 1303456, null)));
@@ -1049,7 +1058,7 @@
@Test
public void testSetKillablePackageAsUserWithSharedUids() throws Exception {
- mockUmGetAliveUsers(mMockUserManager, 11, 12);
+ mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12);
injectPackageInfos(Arrays.asList(
constructPackageManagerPackageInfo(
"third_party_package.A", 1103456, "third_party_shared_package.A"),
@@ -1094,7 +1103,7 @@
@Test
public void testSetKillablePackageAsUserForAllUsers() throws Exception {
- mockUmGetAliveUsers(mMockUserManager, 11, 12);
+ mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12);
injectPackageInfos(Arrays.asList(
constructPackageManagerPackageInfo("third_party_package", 1103456, null),
constructPackageManagerPackageInfo("vendor_package.critical", 1101278, null),
@@ -1121,7 +1130,7 @@
() -> mCarWatchdogService.setKillablePackageAsUser("vendor_package.critical",
UserHandle.ALL, /* isKillable= */ true));
- mockUmGetAliveUsers(mMockUserManager, 11, 12, 13);
+ mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12, 13);
injectPackageInfos(Collections.singletonList(
constructPackageManagerPackageInfo("third_party_package", 1303456, null)));
@@ -1141,7 +1150,7 @@
@Test
public void testSetKillablePackageAsUsersForAllUsersWithSharedUids() throws Exception {
- mockUmGetAliveUsers(mMockUserManager, 11, 12);
+ mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12);
injectPackageInfos(Arrays.asList(
constructPackageManagerPackageInfo(
"third_party_package.A", 1103456, "third_party_shared_package.A"),
@@ -1174,7 +1183,7 @@
new PackageKillableState("third_party_package.B", 12,
PackageKillableState.KILLABLE_STATE_NO));
- mockUmGetAliveUsers(mMockUserManager, 11, 12, 13);
+ mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12, 13);
injectPackageInfos(Arrays.asList(
constructPackageManagerPackageInfo(
"third_party_package.A", 1303456, "third_party_shared_package.A"),
@@ -1192,7 +1201,7 @@
@Test
public void testGetPackageKillableStatesAsUser() throws Exception {
- mockUmGetAliveUsers(mMockUserManager, 11, 12);
+ mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12);
injectPackageInfos(Arrays.asList(
constructPackageManagerPackageInfo("third_party_package", 1103456, null),
constructPackageManagerPackageInfo("vendor_package.critical", 1101278, null),
@@ -1210,7 +1219,7 @@
@Test
public void testGetPackageKillableStatesAsUserWithSafeToKillPackages() throws Exception {
- mockUmGetAliveUsers(mMockUserManager, 11, 12);
+ mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12);
injectPackageInfos(Arrays.asList(
constructPackageManagerPackageInfo("system_package.non_critical.A", 1102459, null),
constructPackageManagerPackageInfo("third_party_package", 1103456, null),
@@ -1242,7 +1251,7 @@
@Test
public void testGetPackageKillableStatesAsUserWithVendorPackagePrefixes() throws Exception {
- mockUmGetAliveUsers(mMockUserManager, 11);
+ mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11);
/* Package names which start with "system" are constructed as system packages. */
injectPackageInfos(Arrays.asList(
constructPackageManagerPackageInfo("system_package_as_vendor", 1102459, null)));
@@ -1273,7 +1282,7 @@
@Test
public void testGetPackageKillableStatesAsUserWithSharedUids() throws Exception {
- mockUmGetAliveUsers(mMockUserManager, 11, 12);
+ mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12);
injectPackageInfos(Arrays.asList(
constructPackageManagerPackageInfo(
"system_package.A", 1103456, "vendor_shared_package.A"),
@@ -1304,7 +1313,7 @@
@Test
public void testGetPackageKillableStatesAsUserWithSharedUidsAndSafeToKillPackages()
throws Exception {
- mockUmGetAliveUsers(mMockUserManager, 11);
+ mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11);
injectPackageInfos(Arrays.asList(
constructPackageManagerPackageInfo(
"vendor_package.non_critical.A", 1103456, "vendor_shared_package.A"),
@@ -1343,7 +1352,7 @@
@Test
public void testGetPackageKillableStatesAsUserWithSharedUidsAndSafeToKillSharedPackage()
throws Exception {
- mockUmGetAliveUsers(mMockUserManager, 11);
+ mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11);
injectPackageInfos(Arrays.asList(
constructPackageManagerPackageInfo(
"vendor_package.non_critical.A", 1103456, "vendor_shared_package.B"),
@@ -1374,7 +1383,7 @@
@Test
public void testGetPackageKillableStatesAsUserForAllUsers() throws Exception {
- mockUmGetAliveUsers(mMockUserManager, 11, 12);
+ mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12);
injectPackageInfos(Arrays.asList(
constructPackageManagerPackageInfo("third_party_package", 1103456, null),
constructPackageManagerPackageInfo("vendor_package.critical", 1101278, null),
@@ -1395,7 +1404,7 @@
@Test
public void testGetPackageKillableStatesAsUserForAllUsersWithSharedUids() throws Exception {
- mockUmGetAliveUsers(mMockUserManager, 11, 12);
+ mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 11, 12);
injectPackageInfos(Arrays.asList(
constructPackageManagerPackageInfo(
"system_package.A", 1103456, "vendor_shared_package.A"),
@@ -1897,7 +1906,7 @@
@Test
public void testPersistStatsOnShutdownEnter() throws Exception {
- mockUmGetAliveUsers(mMockUserManager, 10, 11, 12);
+ mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 10, 11, 12);
injectPackageInfos(Arrays.asList(
constructPackageManagerPackageInfo(
"third_party_package", 1103456, "vendor_shared_package.critical"),
@@ -1961,7 +1970,7 @@
@Test
public void testPersistIoOveruseStatsOnDateChange() throws Exception {
- mockUmGetAliveUsers(mMockUserManager, 10);
+ mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 10);
injectPackageInfos(Arrays.asList(
constructPackageManagerPackageInfo("system_package", 1011200, null),
constructPackageManagerPackageInfo("third_party_package", 1001100, null)));
@@ -2053,7 +2062,7 @@
@Test
public void testResetResourceOveruseStatsResetsUserPackageSettings() throws Exception {
- mockUmGetAliveUsers(mMockUserManager, 100, 101);
+ mockUmGetUserHandles(mMockUserManager, /* excludeDying= */ true, 100, 101);
injectPackageInfos(Arrays.asList(
constructPackageManagerPackageInfo("third_party_package.A", 10001278, null),
constructPackageManagerPackageInfo("third_party_package.A", 10101278, null),
@@ -2388,6 +2397,7 @@
*/
CarServiceUtils.getHandlerThread(CarWatchdogService.class.getSimpleName())
.getThreadHandler().post(() -> {});
+ verify(mMockWatchdogStorage, times(wantedInvocations)).syncUsers(any());
verify(mMockWatchdogStorage, times(wantedInvocations)).getUserPackageSettings();
verify(mMockWatchdogStorage, times(wantedInvocations)).getTodayIoUsageStats();
}
diff --git a/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogStorageUnitTest.java b/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogStorageUnitTest.java
index 3f16750..e2c12bb 100644
--- a/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogStorageUnitTest.java
+++ b/tests/carservice_unit_test/src/com/android/car/watchdog/WatchdogStorageUnitTest.java
@@ -346,6 +346,68 @@
}
@Test
+ public void testSyncUsers() throws Exception {
+ List<WatchdogStorage.UserPackageSettingsEntry> settingsEntries = sampleSettings();
+ List<WatchdogStorage.IoUsageStatsEntry> ioUsageStatsEntries = sampleStatsForToday();
+
+ assertThat(mService.saveUserPackageSettings(settingsEntries)).isTrue();
+ assertThat(mService.saveIoUsageStats(ioUsageStatsEntries)).isTrue();
+
+ mService.syncUsers(/* aliveUserIds= */ new int[] {101});
+
+ settingsEntries.removeIf((s) -> s.userId == 100);
+ ioUsageStatsEntries.removeIf((e) -> e.userId == 100);
+
+ UserPackageSettingsEntrySubject.assertThat(mService.getUserPackageSettings())
+ .containsExactlyElementsIn(settingsEntries);
+
+ IoUsageStatsEntrySubject.assertThat(mService.getTodayIoUsageStats())
+ .containsExactlyElementsIn(ioUsageStatsEntries);
+ }
+
+ @Test
+ public void testSyncUsersWithHistoricalIoOveruseStats() throws Exception {
+ List<WatchdogStorage.UserPackageSettingsEntry> settingsEntries = sampleSettings();
+
+ assertThat(mService.saveUserPackageSettings(settingsEntries)).isTrue();
+ assertThat(mService.saveIoUsageStats(sampleStatsBetweenDates(
+ /* includingStartDaysAgo= */ 1, /* excludingEndDaysAgo= */ 6))).isTrue();
+
+ mService.syncUsers(/* aliveUserIds= */ new int[] {101});
+
+ settingsEntries.removeIf((s) -> s.userId == 100);
+
+ UserPackageSettingsEntrySubject.assertThat(mService.getUserPackageSettings())
+ .containsExactlyElementsIn(settingsEntries);
+
+ IoOveruseStats actualSystemPackage = mService.getHistoricalIoOveruseStats(
+ /* userId= */ 100, "system_package.non_critical.A", /* numDaysAgo= */ 7);
+ IoOveruseStats actualVendorPackage = mService.getHistoricalIoOveruseStats(
+ /* userId= */ 100, "vendor_package.critical.C", /* numDaysAgo= */ 7);
+
+ assertWithMessage("System I/O overuse stats for deleted user")
+ .that(actualSystemPackage).isNull();
+ assertWithMessage("Vendor I/O overuse stats for deleted user")
+ .that(actualVendorPackage).isNull();
+ }
+
+ @Test
+ public void testSyncUsersWithNoDataForDeletedUser() throws Exception {
+ List<WatchdogStorage.UserPackageSettingsEntry> settingsEntries = sampleSettings();
+ List<WatchdogStorage.IoUsageStatsEntry> ioUsageStatsEntries = sampleStatsForToday();
+
+ assertThat(mService.saveUserPackageSettings(settingsEntries)).isTrue();
+ assertThat(mService.saveIoUsageStats(ioUsageStatsEntries)).isTrue();
+
+ mService.syncUsers(/* aliveUserIds= */ new int[] {100, 101});
+
+ UserPackageSettingsEntrySubject.assertThat(mService.getUserPackageSettings())
+ .containsExactlyElementsIn(settingsEntries);
+ IoUsageStatsEntrySubject.assertThat(mService.getTodayIoUsageStats())
+ .containsExactlyElementsIn(ioUsageStatsEntries);
+ }
+
+ @Test
public void testTruncateStatsOutsideRetentionPeriodOnDateChange() throws Exception {
injectSampleUserPackageSettings();
setDate(/* numDaysAgo= */ 1);
@@ -454,7 +516,7 @@
private static ArrayList<WatchdogStorage.IoUsageStatsEntry> sampleStatsForDate(
long statsDateEpoch, long duration) {
ArrayList<WatchdogStorage.IoUsageStatsEntry> entries = new ArrayList<>();
- for (int i = 100; i < 101; ++i) {
+ for (int i = 100; i <= 101; ++i) {
entries.add(constructIoUsageStatsEntry(
/* userId= */ i, "system_package.non_critical.A", statsDateEpoch, duration,
/* remainingWriteBytes= */