summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-05-10 15:46:41 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-05-10 15:46:41 +0000
commitee02976069cccb91375d06a2bcfeca3fccfc08bc (patch)
treebe9269d71941eec7871f22db4c1263682b782298
parent9e49fbb3afde2aafb36e70df56d0ba98a8c86030 (diff)
parentbcb1dc185374136d2b72d98a89c828ae063919b3 (diff)
downloadsystemlibs-busytown-mac-infra-release.tar.gz
Snap for 11819167 from bcb1dc185374136d2b72d98a89c828ae063919b3 to busytown-mac-infra-releasebusytown-mac-infra-release
Change-Id: Ia5d306fe4ad636c4b7b665caf3d23487a4de84cb
-rw-r--r--OWNERS14
-rw-r--r--car-broadcastradio-support/Android.bp1
-rw-r--r--car-broadcastradio-support/OWNERS1
-rw-r--r--car-broadcastradio-support/src/com/android/car/broadcastradio/support/platform/ProgramInfoExt.java125
-rw-r--r--car-broadcastradio-support/src/com/android/car/broadcastradio/support/platform/ProgramSelectorExt.java187
-rw-r--r--car-qc-lib/Android.bp4
-rw-r--r--car-qc-lib/OWNERS4
-rw-r--r--car-qc-lib/res/drawable/qc_toggle_background.xml4
-rw-r--r--car-qc-lib/res/drawable/qc_toggle_unavailable_background.xml4
-rw-r--r--car-qc-lib/res/layout/qc_row_view.xml10
-rw-r--r--car-qc-lib/res/layout/qc_tile_view.xml2
-rw-r--r--car-qc-lib/res/values/colors.xml10
-rw-r--r--car-qc-lib/res/values/dimens.xml3
-rw-r--r--car-qc-lib/res/values/styles.xml11
-rw-r--r--car-qc-lib/src/com/android/car/qc/QCActionItem.java62
-rw-r--r--car-qc-lib/src/com/android/car/qc/provider/BaseQCProvider.java6
-rw-r--r--car-qc-lib/src/com/android/car/qc/view/QCRowView.java17
-rw-r--r--car-qc-lib/src/com/android/car/qc/view/QCTileView.java3
-rw-r--r--car-qc-lib/src/com/android/car/qc/view/QCViewUtils.java59
-rw-r--r--car-qc-lib/tests/unit/Android.bp8
-rw-r--r--car-qc-lib/tests/unit/src/com/android/car/qc/QCActionItemTest.java21
-rw-r--r--car-qc-lib/tests/unit/src/com/android/car/qc/view/QCRowViewTest.java43
-rwxr-xr-xtools/rro/generate-overlayable.py6
-rw-r--r--tools/rro/resource_utils.py8
-rwxr-xr-xtools/rro/verify-overlayable.py6
25 files changed, 494 insertions, 125 deletions
diff --git a/OWNERS b/OWNERS
index 15d29ad..5de81be 100644
--- a/OWNERS
+++ b/OWNERS
@@ -1,14 +1,6 @@
# People who can approve changes for submission.
-
-# TLs
-ajchen@google.com
-rlagos@google.com
-stenning@google.com
-yizheng@google.com
-robertoalexis@google.com
farivar@google.com
-
-# TLMs
-johnchoi@google.com
-nicksauer@google.com
+rampara@google.com
+alexstetson@google.com
+babakbo@google.com
igorr@google.com
diff --git a/car-broadcastradio-support/Android.bp b/car-broadcastradio-support/Android.bp
index d0e24e1..007a65f 100644
--- a/car-broadcastradio-support/Android.bp
+++ b/car-broadcastradio-support/Android.bp
@@ -15,6 +15,7 @@
//
package {
+ default_team: "trendy_team_aaos_framework",
default_applicable_licenses: ["Android-Apache-2.0"],
}
diff --git a/car-broadcastradio-support/OWNERS b/car-broadcastradio-support/OWNERS
index 7d3fbc0..95b6918 100644
--- a/car-broadcastradio-support/OWNERS
+++ b/car-broadcastradio-support/OWNERS
@@ -3,4 +3,3 @@
xuweilin@google.com
oscarazu@google.com
ericjeong@google.com
-keunyoung@google.com
diff --git a/car-broadcastradio-support/src/com/android/car/broadcastradio/support/platform/ProgramInfoExt.java b/car-broadcastradio-support/src/com/android/car/broadcastradio/support/platform/ProgramInfoExt.java
index ce3d014..c96521a 100644
--- a/car-broadcastradio-support/src/com/android/car/broadcastradio/support/platform/ProgramInfoExt.java
+++ b/car-broadcastradio-support/src/com/android/car/broadcastradio/support/platform/ProgramInfoExt.java
@@ -21,6 +21,7 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.graphics.Bitmap;
import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
import android.hardware.radio.RadioManager.ProgramInfo;
import android.hardware.radio.RadioMetadata;
import android.media.MediaMetadata;
@@ -29,6 +30,8 @@ import android.util.Log;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.Comparator;
+import java.util.Objects;
/**
* Proposed extensions to android.hardware.radio.RadioManager.ProgramInfo.
@@ -71,13 +74,23 @@ public class ProgramInfoExt {
/**
* Returns program name suitable to display.
*
- * If there is no program name, it falls back to channel name. Flags related to
+ * <p>If there is no program name, it falls back to channel name. Flags related to
* the channel name display will be forwarded to the channel name generation method.
+ *
+ * @param info {@link ProgramInfo} to get name from
+ * @param flags Fallback method
+ * @param programNameOrder {@link RadioMetadata} metadata keys to pull from {@link ProgramInfo}
+ * for the program name
*/
- public static @NonNull String getProgramName(@NonNull ProgramInfo info, @NameFlag int flags) {
+ @NonNull
+ public static String getProgramName(@NonNull ProgramInfo info, @NameFlag int flags,
+ @NonNull String[] programNameOrder) {
+ Objects.requireNonNull(info, "info can not be null.");
+ Objects.requireNonNull(programNameOrder, "programNameOrder can not be null");
+
RadioMetadata meta = info.getMetadata();
if (meta != null) {
- for (String key : PROGRAM_NAME_ORDER) {
+ for (String key : programNameOrder) {
String value = meta.getString(key);
if (value != null) return value;
}
@@ -104,6 +117,17 @@ public class ProgramInfoExt {
}
/**
+ * Returns program name suitable to display.
+ *
+ * <p>If there is no program name, it falls back to channel name. Flags related to
+ * the channel name display will be forwarded to the channel name generation method.
+ */
+ @NonNull
+ public static String getProgramName(@NonNull ProgramInfo info, @NameFlag int flags) {
+ return getProgramName(info, flags, PROGRAM_NAME_ORDER);
+ }
+
+ /**
* Proposed reimplementation of {@link RadioManager#ProgramInfo#getMetadata}.
*
* As opposed to the original implementation, it never returns null.
@@ -118,12 +142,75 @@ public class ProgramInfoExt {
}
/**
- * Converts {@ProgramInfo} to {@MediaMetadata}.
+ * Converts {@link ProgramInfo} to {@link MediaMetadata} for displaying.
+ *
+ * <p>This method is meant to be used for displaying the currently playing station in
+ * {@link MediaSession}, only a subset of keys populated in {@link ProgramInfo#toMediaMetadata}
+ * will be populated in this method.
+ *
+ * <ul>
+ * The following keys will be populated in the {@link MediaMetadata}:
+ * <li>{@link MediaMetadata#METADATA_KEY_DISPLAY_TITLE}</li>
+ * <li>{@link MediaMetadata#METADATA_KEY_DISPLAY_SUBTITLE}</li>
+ * <li>{@link MediaMetadata#METADATA_KEY_ALBUM_ART}</li>
+ * <li>{@link MediaMetadata#METADATA_KEY_USER_RATING}</li>
+ * <ul/>
+ *
+ * @param info {@link ProgramInfo} to convert
+ * @param isFavorite {@code true}, if a given program is a favorite
+ * @param imageResolver metadata images resolver/cache
+ * @param programNameOrder order of keys to look for program name in {@link ProgramInfo}
+ * @return {@link MediaMetadata} object
+ */
+ @NonNull
+ public static MediaMetadata toMediaDisplayMetadata(@NonNull ProgramInfo info,
+ boolean isFavorite, @NonNull ImageResolver imageResolver,
+ @NonNull String[] programNameOrder) {
+ Objects.requireNonNull(info, "info can not be null.");
+ Objects.requireNonNull(imageResolver, "imageResolver can not be null.");
+ Objects.requireNonNull(programNameOrder, "programNameOrder can not be null.");
+
+ MediaMetadata.Builder bld = new MediaMetadata.Builder();
+
+ ProgramSelector selector;
+ ProgramSelector.Identifier logicallyTunedTo = info.getLogicallyTunedTo();
+ if (logicallyTunedTo != null && logicallyTunedTo.getType()
+ == ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY) {
+ selector = ProgramSelectorExt.createAmFmSelector(logicallyTunedTo.getValue());
+ } else {
+ selector = info.getSelector();
+ }
+ String displayTitle = ProgramSelectorExt.getDisplayName(selector, info.getChannel());
+ bld.putString(MediaMetadata.METADATA_KEY_DISPLAY_TITLE, displayTitle);
+ String subtitle = getProgramName(info, /* flags= */ 0, programNameOrder);
+ bld.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, subtitle);
+
+ Bitmap bm = resolveAlbumArtBitmap(info.getMetadata(), imageResolver);
+ if (bm != null) bld.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, bm);
+
+ bld.putRating(MediaMetadata.METADATA_KEY_USER_RATING, Rating.newHeartRating(isFavorite));
+
+ return bld.build();
+ }
+
+ /**
+ * Converts {@link ProgramInfo} to {@link MediaMetadata}.
+ *
+ * <p>This method is meant to be used for currently playing station in {@link MediaSession}.
*
- * This method is meant to be used for currently playing station in {@link MediaSession}.
+ * <ul>
+ * The following keys will be populated in the {@link MediaMetadata}:
+ * <li>{@link MediaMetadata#METADATA_KEY_DISPLAY_TITLE}</li>
+ * <li>{@link MediaMetadata#METADATA_KEY_TITLE}</li>
+ * <li>{@link MediaMetadata#METADATA_KEY_ARTIST}</li>
+ * <li>{@link MediaMetadata#METADATA_KEY_ALBUM}</li>
+ * <li>{@link MediaMetadata#METADATA_KEY_DISPLAY_SUBTITLE}</li>
+ * <li>{@link MediaMetadata#METADATA_KEY_ALBUM_ART}</li>
+ * <li>{@link MediaMetadata#METADATA_KEY_USER_RATING}</li>
+ * <ul/>
*
* @param info {@link ProgramInfo} to convert
- * @param isFavorite true, if a given program is a favorite
+ * @param isFavorite {@code true}, if a given program is a favorite
* @param imageResolver metadata images resolver/cache
* @return {@link MediaMetadata} object
*/
@@ -158,16 +245,30 @@ public class ProgramInfoExt {
}
bld.putString(MediaMetadata.METADATA_KEY_DISPLAY_SUBTITLE, subtitle);
}
- long albumArtId = RadioMetadataExt.getGlobalBitmapId(meta,
- RadioMetadata.METADATA_KEY_ART);
- if (albumArtId != 0 && imageResolver != null) {
- Bitmap bm = imageResolver.resolve(albumArtId);
- if (bm != null) bld.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, bm);
- }
+
+ Bitmap bm = resolveAlbumArtBitmap(meta, imageResolver);
+ if (bm != null) bld.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, bm);
}
bld.putRating(MediaMetadata.METADATA_KEY_USER_RATING, Rating.newHeartRating(isFavorite));
return bld.build();
}
+
+ private static Bitmap resolveAlbumArtBitmap(@NonNull RadioMetadata meta,
+ @NonNull ImageResolver imageResolver) {
+ long albumArtId = RadioMetadataExt.getGlobalBitmapId(meta, RadioMetadata.METADATA_KEY_ART);
+ if (albumArtId != 0 && imageResolver != null) {
+ return imageResolver.resolve(albumArtId);
+ }
+ return null;
+ }
+ public static class ProgramInfoComparator implements Comparator<RadioManager.ProgramInfo> {
+ @Override
+ public int compare(RadioManager.ProgramInfo info1, RadioManager.ProgramInfo info2) {
+ Comparator<ProgramSelector> selectorComparator =
+ new ProgramSelectorExt.ProgramSelectorComparator();
+ return selectorComparator.compare(info1.getSelector(), info2.getSelector());
+ }
+ }
}
diff --git a/car-broadcastradio-support/src/com/android/car/broadcastradio/support/platform/ProgramSelectorExt.java b/car-broadcastradio-support/src/com/android/car/broadcastradio/support/platform/ProgramSelectorExt.java
index 4b3583b..555176d 100644
--- a/car-broadcastradio-support/src/com/android/car/broadcastradio/support/platform/ProgramSelectorExt.java
+++ b/car-broadcastradio-support/src/com/android/car/broadcastradio/support/platform/ProgramSelectorExt.java
@@ -29,6 +29,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.text.DecimalFormat;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -81,6 +82,11 @@ public class ProgramSelectorExt {
@Retention(RetentionPolicy.SOURCE)
public @interface NameFlag {}
+ /**
+ * Invalid value for a {@link ProgramSelector.Identifier}
+ */
+ public static int INVALID_IDENTIFIER_VALUE = 0;
+
private static final String URI_SCHEME_BROADCASTRADIO = "broadcastradio";
private static final String URI_AUTHORITY_PROGRAM = "program";
private static final String URI_VENDOR_PREFIX = "VENDOR_";
@@ -251,26 +257,81 @@ public class ProgramSelectorExt {
}
/**
+ * Get frequency from a {@link ProgramSelector}.
+ *
+ * @param selector Program selector
+ * @return frequency of the first {@link ProgramSelector#IDENTIFIER_TYPE_AMFM_FREQUENCY} id in
+ * program selector if it exists, otherwise the AM/FM frequency in
+ * {@link ProgramSelector#IDENTIFIER_TYPE_HD_STATION_ID_EXT} id if it is the primary id,
+ * {@link #INVALID_IDENTIFIER_VALUE} otherwise.
+ */
+ public static int getFrequency(@NonNull ProgramSelector selector) {
+ if (ProgramSelectorExt.hasId(selector, ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY)) {
+ return (int) selector.getFirstId(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY);
+ } else if (selector.getPrimaryId().getType()
+ == ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT) {
+ return IdentifierExt.asHdPrimary(selector.getPrimaryId()).getFrequency();
+ } else if (selector.getPrimaryId().getType()
+ == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT
+ || selector.getPrimaryId().getType()
+ == ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT) {
+ try {
+ return (int) selector.getFirstId(ProgramSelector.IDENTIFIER_TYPE_DAB_FREQUENCY);
+ } catch (IllegalArgumentException e) {
+ return INVALID_IDENTIFIER_VALUE;
+ }
+ }
+ return INVALID_IDENTIFIER_VALUE;
+ }
+
+ /**
+ * Get ensemble value from a DAB-type {@link ProgramSelector}.
+ *
+ * @param selector Program selector
+ * @return Value of the first {@link ProgramSelector#IDENTIFIER_TYPE_DAB_ENSEMBLE} identifier,
+ * 0 otherwise
+ */
+ public static int getDabEnsemble(@NonNull ProgramSelector selector) {
+ try {
+ return (int) selector.getFirstId(ProgramSelector.IDENTIFIER_TYPE_DAB_ENSEMBLE);
+ } catch (IllegalArgumentException e) {
+ return INVALID_IDENTIFIER_VALUE;
+ }
+ }
+
+ /**
* Returns a channel name that can be displayed to the user.
*
- * It's implemented only for radio technologies where the channel is meant
- * to be presented to the user.
+ * <p>It's implemented only for radio technologies where the channel is meant
+ * to be presented to the user, such as FM/AM and HD radio.
+ *
+ * <p>For HD radio, the display name is prefix with "-HD[NUMBER]" where the number is the
+ * sub channel.
*
* @param sel the program selector
- * @return Channel name or null, if radio technology doesn't present channel names to the user.
+ * @return Channel name or {@code null}, if radio technology doesn't present channel names to
+ * the user.
*/
public static @Nullable String getDisplayName(@NonNull ProgramSelector sel,
@NameFlag int flags) {
boolean noProgramTypeFallback = (flags & NAME_NO_PROGRAM_TYPE_FALLBACK) != 0;
if (isAmFmProgram(sel)) {
- if (!hasId(sel, ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY)) {
+ long freq;
+ String hdSuffix = "";
+ if (sel.getPrimaryId().getType()
+ == ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT) {
+ IdentifierExt.HdPrimary hdIdExt = IdentifierExt.asHdPrimary(sel.getPrimaryId());
+ freq = hdIdExt.getFrequency();
+ hdSuffix = "-HD" + (hdIdExt.getSubchannel() + 1);
+ } else if (hasId(sel, ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY)) {
+ freq = sel.getFirstId(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY);
+ } else {
if (noProgramTypeFallback) return null;
// if there is no frequency assigned, let's assume it's a malformed RDS selector
return "FM";
}
- long freq = sel.getFirstId(ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY);
- return formatAmFmFrequency(freq, flags);
+ return formatAmFmFrequency(freq, flags) + hdSuffix;
}
if ((flags & NAME_MODULATION_ONLY) != 0) return null;
@@ -285,7 +346,8 @@ public class ProgramSelectorExt {
switch (sel.getPrimaryId().getType()) {
case ProgramSelector.IDENTIFIER_TYPE_SXM_SERVICE_ID:
return "SXM";
- case ProgramSelector.IDENTIFIER_TYPE_DAB_SIDECC:
+ case ProgramSelector.IDENTIFIER_TYPE_DAB_SID_EXT:
+ case ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT:
return "DAB";
case ProgramSelector.IDENTIFIER_TYPE_DRMO_SERVICE_ID:
return "DRMO";
@@ -442,10 +504,10 @@ public class ProgramSelectorExt {
*/
public static class IdentifierExt {
/**
- * Decode {@link ProgramSelector#IDENTIFIER_TYPE_HD_STATION_ID_EXT} value.
+ * Decoder of {@link ProgramSelector#IDENTIFIER_TYPE_HD_STATION_ID_EXT} value.
*
- * @param id identifier to decode
- * @return value decoder
+ * When pushed to the framework, it will be non-static class referring
+ * to the original value.
*/
public static @Nullable HdPrimary asHdPrimary(@NonNull Identifier id) {
if (id.getType() == ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT) {
@@ -482,5 +544,110 @@ public class ProgramSelectorExt {
return (int) ((mValue >>> (32 + 4)) & 0x3FFFF);
}
}
+
+ /**
+ * Decoder of {@link ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT} value.
+ *
+ * <p>When pushed to the framework, it will be non-static class referring
+ * to the original value.
+ * @param id Identifier to be decoded
+ * @return {@link DabPrimary} object if the identifier is DAB-type, {@code null} otherwise
+ */
+ public static @Nullable DabPrimary asDabPrimary(@NonNull Identifier id) {
+ if (id.getType() == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) {
+ return new DabPrimary(id.getValue());
+ }
+ return null;
+ }
+
+ /**
+ * Decoder of {@link ProgramSelector#IDENTIFIER_TYPE_DAB_DMB_SID_EXT} value.
+ *
+ * <p>When pushed to the framework, it will be non-static class referring
+ * to the original value.
+ */
+ public static class DabPrimary {
+ private final long mValue;
+
+ private DabPrimary(long value) {
+ mValue = value;
+ }
+
+ /**
+ * Get Service Identifier (SId).
+ * @return SId value
+ */
+ public int getSId() {
+ return (int) (mValue & 0xFFFFFFFF);
+ }
+
+ /**
+ * Get Extended Country Code (ECC)
+ * @return Extended Country Code
+ */
+ public int getEcc() {
+ return (int) ((mValue >>> 32) & 0xFF);
+ }
+
+ /**
+ * Get SCIdS (Service Component Identifier within the Service) value
+ * @return SCIdS value
+ */
+ public int getSCIdS() {
+ return (int) ((mValue >>> (32 + 8)) & 0xF);
+ }
+ }
+ }
+
+ public static class ProgramSelectorComparator implements Comparator<ProgramSelector> {
+ @Override
+ public int compare(ProgramSelector selector1, ProgramSelector selector2) {
+ int type1 = selector1.getPrimaryId().getType();
+ int type2 = selector2.getPrimaryId().getType();
+ int frequency1 = getFrequency(selector1);
+ int frequency2 = getFrequency(selector2);
+ if (isAmFmProgram(selector1) && isAmFmProgram(selector2)) {
+ if (frequency1 != frequency2) {
+ return frequency1 > frequency2 ? 1 : -1;
+ }
+ int subchannel1 = selector1.getPrimaryId().getType()
+ == ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT
+ ? IdentifierExt.asHdPrimary(selector1.getPrimaryId()).getSubchannel() : 0;
+ int subchannel2 = selector2.getPrimaryId().getType()
+ == ProgramSelector.IDENTIFIER_TYPE_HD_STATION_ID_EXT
+ ? IdentifierExt.asHdPrimary(selector2.getPrimaryId()).getSubchannel() : 0;
+ if (subchannel1 != subchannel2) {
+ return subchannel1 > subchannel2 ? 1 : -1;
+ }
+ return selector1.getPrimaryId().getType() - selector2.getPrimaryId().getType();
+ } else if (type1 == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT
+ && type2 == ProgramSelector.IDENTIFIER_TYPE_DAB_DMB_SID_EXT) {
+ if (frequency1 != frequency2) {
+ return frequency1 > frequency2 ? 1 : -1;
+ }
+ IdentifierExt.DabPrimary dabPrimary1 = IdentifierExt.asDabPrimary(
+ selector1.getPrimaryId());
+ IdentifierExt.DabPrimary dabPrimary2 = IdentifierExt.asDabPrimary(
+ selector2.getPrimaryId());
+ int ecc1 = dabPrimary1.getEcc();
+ int ecc2 = dabPrimary2.getEcc();
+ if (ecc1 != ecc2) {
+ return ecc1 > ecc2 ? 1 : -1;
+ }
+ int sId1 = dabPrimary1.getSId();
+ int sId2 = dabPrimary2.getSId();
+ if (sId1 != sId2) {
+ return sId1 > sId2 ? 1 : -1;
+ }
+ int sCIds1 = dabPrimary1.getSCIdS();
+ int sCIds2 = dabPrimary2.getSCIdS();
+ if (sCIds1 != sCIds2) {
+ return sCIds1 > sCIds2 ? 1 : -1;
+ }
+ return getDabEnsemble(selector1) > getDabEnsemble(selector2) ? 1 : -1;
+ }
+ return type1 > type2 || (type1 == type2 && selector1.getPrimaryId().getValue()
+ > selector2.getPrimaryId().getValue()) ? 1 : -1;
+ }
}
}
diff --git a/car-qc-lib/Android.bp b/car-qc-lib/Android.bp
index c12fd28..4d3a297 100644
--- a/car-qc-lib/Android.bp
+++ b/car-qc-lib/Android.bp
@@ -19,13 +19,13 @@ package {
android_library {
name: "car-qc-lib",
- platform_apis: true,
srcs: ["src/**/*.java"],
optimize: {
enabled: false,
},
static_libs: [
"androidx.annotation_annotation",
- "car-ui-lib"
+ "car-ui-lib-no-overlayable",
+ "car-resource-common",
],
}
diff --git a/car-qc-lib/OWNERS b/car-qc-lib/OWNERS
index 7f8081c..9613cf4 100644
--- a/car-qc-lib/OWNERS
+++ b/car-qc-lib/OWNERS
@@ -4,5 +4,5 @@
alexstetson@google.com
# Secondary (only if people in Primary are unreachable)
-hseog@google.com
-nehah@google.com
+babakbo@google.com
+igorr@google.com \ No newline at end of file
diff --git a/car-qc-lib/res/drawable/qc_toggle_background.xml b/car-qc-lib/res/drawable/qc_toggle_background.xml
index 3688175..4e30cf4 100644
--- a/car-qc-lib/res/drawable/qc_toggle_background.xml
+++ b/car-qc-lib/res/drawable/qc_toggle_background.xml
@@ -16,8 +16,8 @@
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background"
- android:width="@dimen/qc_toggle_size"
- android:height="@dimen/qc_toggle_size"
+ android:width="@dimen/qc_toggle_background_size"
+ android:height="@dimen/qc_toggle_background_size"
android:start="@dimen/qc_toggle_background_padding"
android:top="@dimen/qc_toggle_background_padding"
android:drawable="@drawable/qc_toggle_button_background">
diff --git a/car-qc-lib/res/drawable/qc_toggle_unavailable_background.xml b/car-qc-lib/res/drawable/qc_toggle_unavailable_background.xml
index e9f2e12..185b801 100644
--- a/car-qc-lib/res/drawable/qc_toggle_unavailable_background.xml
+++ b/car-qc-lib/res/drawable/qc_toggle_unavailable_background.xml
@@ -16,8 +16,8 @@
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background"
- android:width="@dimen/qc_toggle_size"
- android:height="@dimen/qc_toggle_size"
+ android:width="@dimen/qc_toggle_background_size"
+ android:height="@dimen/qc_toggle_background_size"
android:start="@dimen/qc_toggle_background_padding"
android:top="@dimen/qc_toggle_background_padding">
<shape android:shape="rectangle">
diff --git a/car-qc-lib/res/layout/qc_row_view.xml b/car-qc-lib/res/layout/qc_row_view.xml
index 6656b29..9976ac6 100644
--- a/car-qc-lib/res/layout/qc_row_view.xml
+++ b/car-qc-lib/res/layout/qc_row_view.xml
@@ -69,7 +69,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="end"
- app:constraint_referenced_ids="qc_icon"
app:barrierAllowsGoneWidgets="false"/>
<com.android.car.ui.uxr.DrawableStateTextView
@@ -78,7 +77,7 @@
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:singleLine="true"
- android:textAppearance="@style/TextAppearance.QC.Title"
+ style="@style/TextAppearance.QC.Title"
app:layout_constraintStart_toEndOf="@+id/barrier1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/qc_summary"
@@ -90,7 +89,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
- android:textAppearance="@style/TextAppearance.QC.Subtitle"
+ style="@style/TextAppearance.QC.Subtitle"
app:layout_constraintStart_toEndOf="@+id/barrier1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/qc_title"
@@ -101,8 +100,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:barrierDirection="top"
- app:constraint_referenced_ids="qc_seekbar_wrapper"
- app:barrierAllowsGoneWidgets="false"/>
+ app:constraint_referenced_ids="qc_seekbar_wrapper"/>
<androidx.preference.UnPressableLinearLayout
android:id="@+id/qc_seekbar_wrapper"
@@ -121,7 +119,7 @@
app:layout_constraintTop_toBottomOf="@+id/barrier2"
app:layout_constraintBottom_toBottomOf="parent">
<com.android.car.qc.view.QCSeekBarView
- android:id="@+id/seekbar"
+ android:id="@+id/qc_seekbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.QC.SeekBar"/>
diff --git a/car-qc-lib/res/layout/qc_tile_view.xml b/car-qc-lib/res/layout/qc_tile_view.xml
index 7fb0884..c7b7511 100644
--- a/car-qc-lib/res/layout/qc_tile_view.xml
+++ b/car-qc-lib/res/layout/qc_tile_view.xml
@@ -37,5 +37,5 @@
android:id="@android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:textAppearance="@style/TextAppearance.QC.Subtitle"/>
+ style="@style/TextAppearance.QC.Subtitle"/>
</com.android.car.ui.uxr.DrawableStateLinearLayout> \ No newline at end of file
diff --git a/car-qc-lib/res/values/colors.xml b/car-qc-lib/res/values/colors.xml
index 62bcfdc..a2b85ff 100644
--- a/car-qc-lib/res/values/colors.xml
+++ b/car-qc-lib/res/values/colors.xml
@@ -20,4 +20,12 @@
<color name="qc_toggle_unavailable_background_color">@android:color/transparent</color>
<color name="qc_toggle_unavailable_color">#37FFFFFF</color>
<color name="qc_toggle_rotary_shadow_color">#C7000000</color>
-</resources> \ No newline at end of file
+ <!-- The SeekBar thumb color. -->
+ <color name="qc_seekbar_thumb">#FFFFFF</color>
+ <!-- The SeekBar thumb color when disabled. Use for the dark theme. -->
+ <color name="qc_seekbar_thumb_disabled_on_dark">#757575</color>
+ <!-- The Switch thumb color. -->
+ <color name="qc_switch_thumb_color">#FFFFFF</color>
+ <!-- The Switch thumb color when disabled. Use for the dark theme. -->
+ <color name="qc_switch_thumb_color_disabled_on_dark">#757575</color>
+</resources>
diff --git a/car-qc-lib/res/values/dimens.xml b/car-qc-lib/res/values/dimens.xml
index b973774..912891f 100644
--- a/car-qc-lib/res/values/dimens.xml
+++ b/car-qc-lib/res/values/dimens.xml
@@ -24,7 +24,8 @@
<dimen name="qc_row_content_margin">16dp</dimen>
<dimen name="qc_action_items_horizontal_margin">32dp</dimen>
- <dimen name="qc_toggle_size">72dp</dimen>
+ <dimen name="qc_toggle_size">80dp</dimen>
+ <dimen name="qc_toggle_background_size">72dp</dimen>
<dimen name="qc_toggle_margin">12dp</dimen>
<dimen name="qc_row_horizontal_margin">16dp</dimen>
<dimen name="qc_toggle_background_radius">16dp</dimen>
diff --git a/car-qc-lib/res/values/styles.xml b/car-qc-lib/res/values/styles.xml
index 587b522..51a7d35 100644
--- a/car-qc-lib/res/values/styles.xml
+++ b/car-qc-lib/res/values/styles.xml
@@ -16,16 +16,15 @@
<resources>
<style name="TextAppearance.QC" parent="android:TextAppearance.DeviceDefault">
- <item name="android:textColor">@color/car_ui_text_color_primary</item>
+ <item name="android:textColor">@color/car_on_surface</item>
</style>
- <style name="TextAppearance.QC.Title">
- <item name="android:textSize">@dimen/car_ui_body1_size</item>
+ <style name="TextAppearance.QC.Title" parent="android:TextAppearance.DeviceDefault.Large">
+ <item name="android:textColor">@color/car_on_surface</item>
</style>
- <style name="TextAppearance.QC.Subtitle">
- <item name="android:textColor">@color/car_ui_text_color_secondary</item>
- <item name="android:textSize">@dimen/car_ui_body3_size</item>
+ <style name="TextAppearance.QC.Subtitle" parent="android:TextAppearance.DeviceDefault.Small">
+ <item name="android:textColor">@color/car_on_surface_variant</item>
</style>
<style name="Widget.QC" parent="android:Widget.DeviceDefault"/>
diff --git a/car-qc-lib/src/com/android/car/qc/QCActionItem.java b/car-qc-lib/src/com/android/car/qc/QCActionItem.java
index c476e09..f4e92d1 100644
--- a/car-qc-lib/src/com/android/car/qc/QCActionItem.java
+++ b/car-qc-lib/src/com/android/car/qc/QCActionItem.java
@@ -17,11 +17,13 @@
package com.android.car.qc;
import android.app.PendingIntent;
+import android.content.Context;
import android.graphics.drawable.Icon;
import android.os.Parcel;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
/**
* Quick Control Action that are includes as either start or end actions in {@link QCRow}
@@ -29,17 +31,22 @@ import androidx.annotation.Nullable;
public class QCActionItem extends QCItem {
private final boolean mIsChecked;
private final boolean mIsAvailable;
+ private final boolean mIsClickable;
private Icon mIcon;
private PendingIntent mAction;
private PendingIntent mDisabledClickAction;
+ private String mContentDescription;
public QCActionItem(@NonNull @QCItemType String type, boolean isChecked, boolean isEnabled,
- boolean isAvailable, boolean isClickableWhileDisabled, @Nullable Icon icon,
+ boolean isAvailable, boolean isClickable, boolean isClickableWhileDisabled,
+ @Nullable Icon icon, @Nullable String contentDescription,
@Nullable PendingIntent action, @Nullable PendingIntent disabledClickAction) {
super(type, isEnabled, isClickableWhileDisabled);
mIsChecked = isChecked;
mIsAvailable = isAvailable;
+ mIsClickable = isClickable;
mIcon = icon;
+ mContentDescription = contentDescription;
mAction = action;
mDisabledClickAction = disabledClickAction;
}
@@ -48,10 +55,15 @@ public class QCActionItem extends QCItem {
super(in);
mIsChecked = in.readBoolean();
mIsAvailable = in.readBoolean();
+ mIsClickable = in.readBoolean();
boolean hasIcon = in.readBoolean();
if (hasIcon) {
mIcon = Icon.CREATOR.createFromParcel(in);
}
+ boolean hasContentDescription = in.readBoolean();
+ if (hasContentDescription) {
+ mContentDescription = in.readString();
+ }
boolean hasAction = in.readBoolean();
if (hasAction) {
mAction = PendingIntent.CREATOR.createFromParcel(in);
@@ -67,11 +79,17 @@ public class QCActionItem extends QCItem {
super.writeToParcel(dest, flags);
dest.writeBoolean(mIsChecked);
dest.writeBoolean(mIsAvailable);
+ dest.writeBoolean(mIsClickable);
boolean includeIcon = getType().equals(QC_TYPE_ACTION_TOGGLE) && mIcon != null;
dest.writeBoolean(includeIcon);
if (includeIcon) {
mIcon.writeToParcel(dest, flags);
}
+ boolean hasContentDescription = mContentDescription != null;
+ dest.writeBoolean(hasContentDescription);
+ if (hasContentDescription) {
+ dest.writeString(mContentDescription);
+ }
boolean hasAction = mAction != null;
dest.writeBoolean(hasAction);
if (hasAction) {
@@ -102,11 +120,20 @@ public class QCActionItem extends QCItem {
return mIsAvailable;
}
+ public boolean isClickable() {
+ return mIsClickable;
+ }
+
@Nullable
public Icon getIcon() {
return mIcon;
}
+ @Nullable
+ public String getContentDescription() {
+ return mContentDescription;
+ }
+
public static Creator<QCActionItem> CREATOR = new Creator<QCActionItem>() {
@Override
public QCActionItem createFromParcel(Parcel source) {
@@ -127,10 +154,12 @@ public class QCActionItem extends QCItem {
private boolean mIsChecked;
private boolean mIsEnabled = true;
private boolean mIsAvailable = true;
+ private boolean mIsClickable = true;
private boolean mIsClickableWhileDisabled = false;
private Icon mIcon;
private PendingIntent mAction;
private PendingIntent mDisabledClickAction;
+ private String mContentDescription;
public Builder(@NonNull @QCItemType String type) {
if (!isValidType(type)) {
@@ -164,6 +193,15 @@ public class QCActionItem extends QCItem {
}
/**
+ * Sets whether the action is clickable. This differs from available in that the style will
+ * remain as if it's enabled/available but click actions will not be processed.
+ */
+ public Builder setClickable(boolean clickable) {
+ mIsClickable = clickable;
+ return this;
+ }
+
+ /**
* Sets whether or not an action item should be clickable while disabled.
*/
public Builder setClickableWhileDisabled(boolean clickable) {
@@ -180,6 +218,23 @@ public class QCActionItem extends QCItem {
}
/**
+ * Sets the content description
+ */
+ public Builder setContentDescription(@Nullable String contentDescription) {
+ mContentDescription = contentDescription;
+ return this;
+ }
+
+ /**
+ * Sets the string resource to use for content description
+ */
+ public Builder setContentDescription(@NonNull Context context,
+ @StringRes int contentDescriptionResId) {
+ mContentDescription = context.getString(contentDescriptionResId);
+ return this;
+ }
+
+ /**
* Sets the PendingIntent to be sent when the action item is clicked.
*/
public Builder setAction(@Nullable PendingIntent action) {
@@ -199,8 +254,9 @@ public class QCActionItem extends QCItem {
* Builds the final {@link QCActionItem}.
*/
public QCActionItem build() {
- return new QCActionItem(mType, mIsChecked, mIsEnabled, mIsAvailable,
- mIsClickableWhileDisabled, mIcon, mAction, mDisabledClickAction);
+ return new QCActionItem(mType, mIsChecked, mIsEnabled, mIsAvailable, mIsClickable,
+ mIsClickableWhileDisabled, mIcon, mContentDescription, mAction,
+ mDisabledClickAction);
}
private boolean isValidType(String type) {
diff --git a/car-qc-lib/src/com/android/car/qc/provider/BaseQCProvider.java b/car-qc-lib/src/com/android/car/qc/provider/BaseQCProvider.java
index 61db361..8e9e550 100644
--- a/car-qc-lib/src/com/android/car/qc/provider/BaseQCProvider.java
+++ b/car-qc-lib/src/com/android/car/qc/provider/BaseQCProvider.java
@@ -172,7 +172,11 @@ public abstract class BaseQCProvider extends ContentProvider {
try {
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectAll()
- .penaltyDeath()
+
+ // TODO(268275789): Revert back to penaltyDeath and ensure it works in
+ // presubmit
+ .penaltyLog()
+
.build());
return onBind(uri);
} finally {
diff --git a/car-qc-lib/src/com/android/car/qc/view/QCRowView.java b/car-qc-lib/src/com/android/car/qc/view/QCRowView.java
index 1e10e4b..2045fd2 100644
--- a/car-qc-lib/src/com/android/car/qc/view/QCRowView.java
+++ b/car-qc-lib/src/com/android/car/qc/view/QCRowView.java
@@ -193,7 +193,7 @@ public class QCRowView extends FrameLayout {
mStartItemsContainer = findViewById(R.id.qc_row_start_items);
mEndItemsContainer = findViewById(R.id.qc_row_end_items);
mSeekBarContainer = findViewById(R.id.qc_seekbar_wrapper);
- mSeekBar = findViewById(R.id.seekbar);
+ mSeekBar = findViewById(R.id.qc_seekbar);
}
void setActionListener(QCActionListener listener) {
@@ -308,10 +308,12 @@ public class QCRowView extends FrameLayout {
CarUiUtils.makeAllViewsEnabled(switchView, action.isEnabled());
boolean shouldEnableView =
- (action.isEnabled() || action.isClickableWhileDisabled()) && action.isAvailable();
+ (action.isEnabled() || action.isClickableWhileDisabled()) && action.isAvailable()
+ && action.isClickable();
switchView.setOnCheckedChangeListener(null);
switchView.setEnabled(shouldEnableView);
switchView.setChecked(action.isChecked());
+ switchView.setContentDescription(action.getContentDescription());
switchView.setOnTouchListener((v, event) -> {
if (!action.isEnabled()) {
if (event.getActionMasked() == MotionEvent.ACTION_UP) {
@@ -338,13 +340,14 @@ public class QCRowView extends FrameLayout {
}
DrawableStateToggleButton toggleButton = tmpToggleButton; // must be effectively final
boolean shouldEnableView =
- (action.isEnabled() || action.isClickableWhileDisabled()) && action.isAvailable();
+ (action.isEnabled() || action.isClickableWhileDisabled()) && action.isAvailable()
+ && action.isClickable();
toggleButton.setText(null);
toggleButton.setTextOn(null);
toggleButton.setTextOff(null);
toggleButton.setOnCheckedChangeListener(null);
- Drawable icon = QCViewUtils.getInstance(mContext).getToggleIcon(
- action.getIcon(), action.isAvailable());
+ Drawable icon = QCViewUtils.getToggleIcon(mContext, action.getIcon(), action.isAvailable());
+ toggleButton.setContentDescription(action.getContentDescription());
toggleButton.setButtonDrawable(icon);
toggleButton.setChecked(action.isChecked());
toggleButton.setEnabled(shouldEnableView);
@@ -397,13 +400,15 @@ public class QCRowView extends FrameLayout {
// remove current action view
root.removeView(actionView);
}
- actionView = mLayoutInflater.inflate(resId, /* root= */ null);
+ actionView = mLayoutInflater.inflate(resId, root, /* attachToRoot= */ false);
root.addView(actionView);
return actionView;
}
private void initSlider(QCSlider slider) {
mQCSlider = slider;
+ CarUiUtils.makeAllViewsEnabled(mSeekBar, slider.isEnabled());
+
mSeekBar.setOnSeekBarChangeListener(null);
mSeekBar.setMin(slider.getMin());
mSeekBar.setMax(slider.getMax());
diff --git a/car-qc-lib/src/com/android/car/qc/view/QCTileView.java b/car-qc-lib/src/com/android/car/qc/view/QCTileView.java
index 33c0eff..4173e25 100644
--- a/car-qc-lib/src/com/android/car/qc/view/QCTileView.java
+++ b/car-qc-lib/src/com/android/car/qc/view/QCTileView.java
@@ -125,8 +125,7 @@ public class QCTileView extends FrameLayout implements Observer<QCItem> {
}
mToggleButton.toggle();
});
- Drawable icon = QCViewUtils.getInstance(mContext).getToggleIcon(
- qcTile.getIcon(), qcTile.isAvailable());
+ Drawable icon = QCViewUtils.getToggleIcon(mContext, qcTile.getIcon(), qcTile.isAvailable());
mToggleButton.setButtonDrawable(icon);
mToggleButton.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
diff --git a/car-qc-lib/src/com/android/car/qc/view/QCViewUtils.java b/car-qc-lib/src/com/android/car/qc/view/QCViewUtils.java
index 366c724..ca0f877 100644
--- a/car-qc-lib/src/com/android/car/qc/view/QCViewUtils.java
+++ b/car-qc-lib/src/com/android/car/qc/view/QCViewUtils.java
@@ -16,7 +16,6 @@
package com.android.car.qc.view;
-import android.annotation.ColorInt;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
@@ -32,64 +31,44 @@ import com.android.car.qc.R;
* Utility class used by {@link QCTileView} and {@link QCRowView}
*/
public class QCViewUtils {
- private static QCViewUtils sInstance;
-
- private final Context mContext;
- private final Drawable mDefaultToggleBackground;
- private final Drawable mUnavailableToggleBackground;
- private final ColorStateList mDefaultToggleIconTint;
- @ColorInt
- private final int mUnavailableToggleIconTint;
- private final int mToggleForegroundIconInset;
-
- private QCViewUtils(@NonNull Context context) {
- mContext = context.getApplicationContext();
- mDefaultToggleBackground = mContext.getDrawable(R.drawable.qc_toggle_background);
- mUnavailableToggleBackground = mContext.getDrawable(
- R.drawable.qc_toggle_unavailable_background);
- mDefaultToggleIconTint = mContext.getColorStateList(R.color.qc_toggle_icon_fill_color);
- mUnavailableToggleIconTint = mContext.getColor(R.color.qc_toggle_unavailable_color);
- mToggleForegroundIconInset = mContext.getResources()
- .getDimensionPixelSize(R.dimen.qc_toggle_foreground_icon_inset);
- }
-
- /**
- * Get an instance of {@link QCViewUtils}
- */
- public static QCViewUtils getInstance(@NonNull Context context) {
- if (sInstance == null) {
- sInstance = new QCViewUtils(context);
- }
- return sInstance;
- }
/**
* Create a return a Quick Control toggle icon - used for tiles and action toggles.
*/
- public Drawable getToggleIcon(@Nullable Icon icon, boolean available) {
+ public static Drawable getToggleIcon(@NonNull Context context, @Nullable Icon icon,
+ boolean available) {
+ Drawable defaultToggleBackground = context.getDrawable(R.drawable.qc_toggle_background);
+ Drawable unavailableToggleBackground = context.getDrawable(
+ R.drawable.qc_toggle_unavailable_background);
+ int toggleForegroundIconInset = context.getResources()
+ .getDimensionPixelSize(R.dimen.qc_toggle_foreground_icon_inset);
+
Drawable background = available
- ? mDefaultToggleBackground.getConstantState().newDrawable().mutate()
- : mUnavailableToggleBackground.getConstantState().newDrawable().mutate();
+ ? defaultToggleBackground.getConstantState().newDrawable().mutate()
+ : unavailableToggleBackground.getConstantState().newDrawable().mutate();
if (icon == null) {
return background;
}
- Drawable iconDrawable = icon.loadDrawable(mContext);
+ Drawable iconDrawable = icon.loadDrawable(context);
if (iconDrawable == null) {
return background;
}
if (!available) {
- iconDrawable.setTint(mUnavailableToggleIconTint);
+ int unavailableToggleIconTint = context.getColor(R.color.qc_toggle_unavailable_color);
+ iconDrawable.setTint(unavailableToggleIconTint);
} else {
- iconDrawable.setTintList(mDefaultToggleIconTint);
+ ColorStateList defaultToggleIconTint = context.getColorStateList(
+ R.color.qc_toggle_icon_fill_color);
+ iconDrawable.setTintList(defaultToggleIconTint);
}
Drawable[] layers = {background, iconDrawable};
LayerDrawable drawable = new LayerDrawable(layers);
- drawable.setLayerInsetRelative(/* index= */ 1, mToggleForegroundIconInset,
- mToggleForegroundIconInset, mToggleForegroundIconInset,
- mToggleForegroundIconInset);
+ drawable.setLayerInsetRelative(/* index= */ 1, toggleForegroundIconInset,
+ toggleForegroundIconInset, toggleForegroundIconInset,
+ toggleForegroundIconInset);
return drawable;
}
}
diff --git a/car-qc-lib/tests/unit/Android.bp b/car-qc-lib/tests/unit/Android.bp
index b1f107a..3af82b4 100644
--- a/car-qc-lib/tests/unit/Android.bp
+++ b/car-qc-lib/tests/unit/Android.bp
@@ -14,6 +14,7 @@
// limitations under the License.
package {
+ default_team: "trendy_team_system_experience",
default_applicable_licenses: ["Android-Apache-2.0"],
}
@@ -39,9 +40,12 @@ android_test {
"androidx.test.ext.truth",
"mockito-target-extended-minus-junit4",
"platform-test-annotations",
- "truth-prebuilt",
+ "truth",
"testng",
],
- jni_libs: ["libdexmakerjvmtiagent", "libstaticjvmtiagent"],
+ jni_libs: [
+ "libdexmakerjvmtiagent",
+ "libstaticjvmtiagent",
+ ],
}
diff --git a/car-qc-lib/tests/unit/src/com/android/car/qc/QCActionItemTest.java b/car-qc-lib/tests/unit/src/com/android/car/qc/QCActionItemTest.java
index ff70540..c945301 100644
--- a/car-qc-lib/tests/unit/src/com/android/car/qc/QCActionItemTest.java
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/QCActionItemTest.java
@@ -33,32 +33,34 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class QCActionItemTest extends QCItemTestCase<QCActionItem> {
+ private static final String TEST_CONTENT_DESCRIPTION = "test_content_description";
@Test
public void onCreate_invalidType_throwsException() {
assertThrows(IllegalArgumentException.class,
() -> createAction("INVALID_TYPE", /* action= */ null,
- /* disabledAction= */ null, /* icon= */ null));
+ /* disabledAction= */ null, /* icon= */ null, /* contentDescription=*/
+ null));
}
@Test
public void onCreateSwitch_hasCorrectType() {
QCActionItem action = createAction(QC_TYPE_ACTION_SWITCH, /* action= */ null,
- /* disabledAction= */ null, /* icon= */null);
+ /* disabledAction= */ null, /* icon= */null, /* contentDescription=*/ null);
assertThat(action.getType()).isEqualTo(QC_TYPE_ACTION_SWITCH);
}
@Test
public void onCreateToggle_hasCorrectType() {
QCActionItem action = createAction(QC_TYPE_ACTION_TOGGLE, /* action= */ null,
- /* disabledAction= */ null, /* icon= */ null);
+ /* disabledAction= */ null, /* icon= */ null, /* contentDescription=*/ null);
assertThat(action.getType()).isEqualTo(QC_TYPE_ACTION_TOGGLE);
}
@Test
public void onBundle_nullActions_noCrash() {
QCActionItem action = createAction(QC_TYPE_ACTION_TOGGLE, /* action= */ null,
- /* disabledAction= */ null, mDefaultIcon);
+ /* disabledAction= */ null, mDefaultIcon, /* contentDescription=*/ null);
writeAndLoadFromBundle(action);
// Test passes if this doesn't crash
}
@@ -66,7 +68,7 @@ public class QCActionItemTest extends QCItemTestCase<QCActionItem> {
@Test
public void onBundle_nullIcon_noCrash() {
QCActionItem action = createAction(QC_TYPE_ACTION_TOGGLE, mDefaultAction,
- mDefaultDisabledAction, /* icon= */ null);
+ mDefaultDisabledAction, /* icon= */ null, /* contentDescription=*/ null);
writeAndLoadFromBundle(action);
// Test passes if this doesn't crash
}
@@ -74,7 +76,7 @@ public class QCActionItemTest extends QCItemTestCase<QCActionItem> {
@Test
public void onBundle_switch_accurateData() {
QCActionItem action = createAction(QC_TYPE_ACTION_SWITCH, mDefaultAction,
- mDefaultDisabledAction, /* icon= */ null);
+ mDefaultDisabledAction, /* icon= */ null, TEST_CONTENT_DESCRIPTION);
QCActionItem newAction = writeAndLoadFromBundle(action);
assertThat(newAction.getType()).isEqualTo(QC_TYPE_ACTION_SWITCH);
assertThat(newAction.isChecked()).isTrue();
@@ -82,12 +84,13 @@ public class QCActionItemTest extends QCItemTestCase<QCActionItem> {
assertThat(newAction.isClickableWhileDisabled()).isFalse();
assertThat(newAction.getPrimaryAction()).isNotNull();
assertThat(newAction.getIcon()).isNull();
+ assertThat(newAction.getContentDescription()).isEqualTo(TEST_CONTENT_DESCRIPTION);
}
@Test
public void onBundle_toggle_accurateDate() {
QCActionItem action = createAction(QC_TYPE_ACTION_TOGGLE, mDefaultAction,
- mDefaultDisabledAction, mDefaultIcon);
+ mDefaultDisabledAction, mDefaultIcon, TEST_CONTENT_DESCRIPTION);
QCActionItem newAction = writeAndLoadFromBundle(action);
assertThat(newAction.getType()).isEqualTo(QC_TYPE_ACTION_TOGGLE);
assertThat(newAction.isChecked()).isTrue();
@@ -95,16 +98,18 @@ public class QCActionItemTest extends QCItemTestCase<QCActionItem> {
assertThat(newAction.isClickableWhileDisabled()).isFalse();
assertThat(newAction.getPrimaryAction()).isNotNull();
assertThat(newAction.getIcon()).isNotNull();
+ assertThat(newAction.getContentDescription()).isEqualTo(TEST_CONTENT_DESCRIPTION);
}
private QCActionItem createAction(String type, PendingIntent action,
- PendingIntent disabledAction, Icon icon) {
+ PendingIntent disabledAction, Icon icon, String contentDescription) {
return new QCActionItem.Builder(type)
.setChecked(true)
.setEnabled(true)
.setAction(action)
.setDisabledClickAction(disabledAction)
.setIcon(icon)
+ .setContentDescription(contentDescription)
.build();
}
}
diff --git a/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCRowViewTest.java b/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCRowViewTest.java
index 647317a..9c1aa79 100644
--- a/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCRowViewTest.java
+++ b/car-qc-lib/tests/unit/src/com/android/car/qc/view/QCRowViewTest.java
@@ -30,12 +30,14 @@ import static org.mockito.Mockito.verify;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
+import android.content.res.ColorStateList;
import android.graphics.drawable.Icon;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.SeekBar;
+import android.widget.Switch;
import android.widget.TextView;
import androidx.test.annotation.UiThreadTest;
@@ -203,11 +205,50 @@ public class QCRowViewTest {
.addSlider(new QCSlider.Builder().setInputAction(action).build())
.build();
mView.setRow(row);
- SeekBar seekBar = mView.findViewById(R.id.seekbar);
+ SeekBar seekBar = mView.findViewById(R.id.qc_seekbar);
seekBar.setProgress(50);
MotionEvent motionEvent = ExtendedMockito.mock(MotionEvent.class);
ExtendedMockito.when(motionEvent.getAction()).thenReturn(MotionEvent.ACTION_UP);
seekBar.onTouchEvent(motionEvent);
verify(action).send(any(Context.class), anyInt(), any(Intent.class));
}
+
+ @Test
+ @UiThreadTest
+ public void setRow_switchViewThumbTintList() {
+ PendingIntent action = mock(PendingIntent.class);
+ QCRow row = new QCRow.Builder()
+ .addEndItem(
+ new QCActionItem.Builder(QC_TYPE_ACTION_SWITCH).setAction(action).build())
+ .build();
+ mView.setRow(row);
+ LinearLayout endContainer = mView.findViewById(R.id.qc_row_end_items);
+ assertThat(endContainer.getChildCount()).isEqualTo(1);
+ Switch switchView = (Switch) endContainer.getChildAt(0);
+ assertThat(switchView.getThumbTintList()).isNotNull();
+
+ ColorStateList switchColorStateList = switchView.getThumbTintList();
+ int[] enabledState = {android.R.attr.state_enabled};
+ int[] disabledState = {-android.R.attr.state_enabled};
+ assertThat(switchColorStateList.getColorForState(enabledState, 0)).isNotEqualTo(
+ switchColorStateList.getColorForState(disabledState, 0));
+ }
+
+ @Test
+ @UiThreadTest
+ public void setRow_sliderViewThumbTintList() {
+ PendingIntent action = mock(PendingIntent.class);
+ QCRow row = new QCRow.Builder()
+ .addSlider(new QCSlider.Builder().setInputAction(action).build())
+ .build();
+ mView.setRow(row);
+ SeekBar seekBar = mView.findViewById(R.id.qc_seekbar);
+ assertThat(seekBar.getThumbTintList()).isNotNull();
+
+ ColorStateList seekBarColorStateList = seekBar.getThumbTintList();
+ int[] enabledState = {android.R.attr.state_enabled};
+ int[] disabledState = {-android.R.attr.state_enabled};
+ assertThat(seekBarColorStateList.getColorForState(enabledState, 0)).isNotEqualTo(
+ seekBarColorStateList.getColorForState(disabledState, 0));
+ }
}
diff --git a/tools/rro/generate-overlayable.py b/tools/rro/generate-overlayable.py
index 2f76a2d..b814118 100755
--- a/tools/rro/generate-overlayable.py
+++ b/tools/rro/generate-overlayable.py
@@ -49,10 +49,12 @@ def main():
optional_args.add_argument('-o', '--outputFile', default='', help='Output file path (absolute or relative to cwd). If empty, output to stdout')
required_args = parser.add_argument_group('required arguments')
required_args.add_argument('-n', '--targetName', help='Overlayable name for the overlay.', required=True)
- required_args.add_argument('-r', '--resourcePath', help='Path to resource directory (absolute or relative to cwd)', required=True)
+ required_args.add_argument('-r', '--resourcePath', help='Path to resource directory (absolute or relative to cwd)', required=True, action='append')
args = parser.parse_args()
- resources = get_all_resources(args.resourcePath, args.excludeFiles)
+ resources = set()
+ for path in args.resourcePath:
+ resources |= get_all_resources(path, args.excludeFiles)
generate_overlayable_file(resources, args.targetName, args.policyType, args.outputFile)
def generate_overlayable_file(resources, target_name, policy_type, output_file):
diff --git a/tools/rro/resource_utils.py b/tools/rro/resource_utils.py
index 5f35e1a..003c69e 100644
--- a/tools/rro/resource_utils.py
+++ b/tools/rro/resource_utils.py
@@ -15,7 +15,13 @@
import os
import re
-import lxml.etree as etree
+import sys
+try:
+ import lxml.etree as etree
+except ImportError:
+ print("Please install 'lxml' python package and retry. \n"
+ + "E.g., you can use 'sudo apt-get install python3-lxml'.")
+ sys.exit(1)
class ResourceLocation:
def __init__(self, file, line=None):
diff --git a/tools/rro/verify-overlayable.py b/tools/rro/verify-overlayable.py
index 2625310..ef99aa4 100755
--- a/tools/rro/verify-overlayable.py
+++ b/tools/rro/verify-overlayable.py
@@ -30,11 +30,13 @@ def main():
optional_args.add_argument('-e', '--excludeFiles', nargs='*', help='File paths (absolute or relative to cwd) that should be excluded when generating overlayable.xml')
optional_args.add_argument('-m', '--errorMessage', nargs='*', help='Custom error message if resources are added or removed.')
required_args = parser.add_argument_group('required arguments')
- required_args.add_argument('-r', '--resourcePath', help='Path to resource directory (absolute or relative to cwd)', required=True)
+ required_args.add_argument('-r', '--resourcePath', help='Path to resource directory (absolute or relative to cwd)', required=True, action='append')
required_args.add_argument('-o', '--overlayableFilePath', help='Filepath to overlayable.xml (absolute or relative to cwd).', required=True)
args = parser.parse_args()
- resources = get_all_resources(args.resourcePath, args.excludeFiles)
+ resources = set()
+ for path in args.resourcePath:
+ resources |= get_all_resources(path, args.excludeFiles)
old_mapping = get_resources_from_single_file(args.overlayableFilePath)
compare_resources(old_mapping, resources, args.overlayableFilePath, args.errorMessage)